4. 探索性数据分析(EDA)基础:数据概览与分布分析
拿到数据后,别急着建模。我见过太多人上来就调包跑模型,结果跑出来的东西根本没法解释。先花时间做 EDA,这是对自己项目负责。
EDA 说白了,就是用各种手段去「认识」你的数据。它长什么样?有没有坑?分布是否合理?这些搞清楚了,后面的路才走得稳。
4.1 数据概览:先摸清家底
拿到 DataFrame 后,我习惯先敲三行代码:df.shape、df.info()、df.describe()。这三板斧下去,数据的基本情况就心里有数了。
4.1.1 shape:数据有多大?
直接告诉你行数和列数。比如 (10000, 25),就是 1 万条样本,25 个特征。这个数字决定了你后续选什么模型、要不要做采样。
4.1.2 info:每列是什么类型?
这个函数会列出每列的名称、非空数量、数据类型。我特别关注两件事:
- 类型对不对——比如「年龄」列如果是 object 类型,那肯定有问题。
- 非空数量——如果某列的非空数远小于总行数,说明缺失严重。
# 我的三板斧
import pandas as pd
df = pd.read_csv('customer_data.csv')
print('Shape:', df.shape)
print('---')
df.info()
print('---')
df.describe()
4.1.3 describe:数值列的统计摘要
它会给出计数、均值、标准差、四分位数等。我一般先看「count」有没有明显偏少,再看「min」和「max」是否合理。
4.2 缺失值分析:别让空值坑了你
真实数据几乎没有完美的。缺失值处理不当,模型效果会大打折扣。我曾经在一个风控项目里,因为没仔细处理缺失值,导致模型上线后误判率飙升……后来排查才发现,某个关键字段的缺失值被默认填充成了 0,但实际应该是「未知」。
4.2.1 如何检测缺失值?
用 df.isnull().sum() 可以快速统计每列的缺失数量。我还会算一下缺失比例:
# 缺失值检测
missing_count = df.isnull().sum()
missing_ratio = missing_count / len(df) * 100
missing_df = pd.DataFrame({
'缺失数量': missing_count,
'缺失比例(%)': missing_ratio.round(2)
})
print(missing_df[missing_df['缺失数量'] > 0])
4.2.2 缺失值处理策略
| 缺失比例 | 建议处理方式 | 注意事项 |
|---|---|---|
| < 5% | 直接删除该行,或用均值/中位数填充 | 删除前确认样本量是否充足 |
| 5% - 30% | 用中位数、众数或模型预测填充 | 分类变量用众数,连续变量用中位数 |
| > 30% | 考虑删除该特征,或单独编码为「缺失」类别 | 如果特征很重要,尝试用其他特征预测填充 |
4.3 异常值初步检测:揪出「害群之马」
异常值可能是录入错误,也可能是真实的极端情况。我的原则是:先检测,再判断,最后决定处理方式。
4.3.1 箱线图法(IQR)
这是最常用的方法。基于四分位距(IQR)来界定异常值:
- 下界 = Q1 - 1.5 * IQR
- 上界 = Q3 + 1.5 * IQR
- 超出上下界的点视为异常值
# IQR 法检测异常值
Q1 = df['age'].quantile(0.25)
Q3 = df['age'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['age'] < lower_bound) | (df['age'] > upper_bound)]
print(f'异常值数量: {len(outliers)}')
4.3.2 Z-Score 法
适用于数据近似正态分布的情况。Z-Score 绝对值大于 3 的点通常被视为异常值。
4.4 单变量分布分析:每个特征都有自己的脾气
了解每个特征的分布形态,能帮你决定要不要做标准化、要不要做变换。我一般从两个角度入手:
4.4.1 数值型变量
用直方图 + KDE 曲线看分布形态:
- 正态分布——理想情况,很多模型假设数据服从正态分布。
- 偏态分布——比如收入数据,右偏严重,可能需要做对数变换。
- 双峰分布——可能数据来自两个不同的群体,需要进一步分析。
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
sns.histplot(df['age'], kde=True, bins=30)
plt.title('年龄分布')
plt.subplot(1, 2, 2)
sns.histplot(df['income'], kde=True, bins=30)
plt.title('收入分布')
plt.tight_layout()
plt.show()
4.4.2 类别型变量
用柱状图看频数分布。我特别关注类别是否过于集中(比如 90% 都是同一个类别),或者类别数量是否过多(超过 20 个类别就需要考虑合并了)。
# 类别变量频数统计
category_counts = df['education'].value_counts()
print(category_counts)
# 可视化
sns.countplot(data=df, x='education')
plt.xticks(rotation=45)
plt.show()
嗯,到这里,数据的基本面貌应该已经清楚了。你知道了它有多大、哪里有空缺、哪些点不对劲、每个特征长什么样。这些信息,就是接下来做特征工程和建模的「地图」。带着这张地图走,不容易迷路。