4. 探索性数据分析(EDA)基础:数据概览与分布分析

拿到数据后,别急着建模。我见过太多人上来就调包跑模型,结果跑出来的东西根本没法解释。先花时间做 EDA,这是对自己项目负责。

EDA 说白了,就是用各种手段去「认识」你的数据。它长什么样?有没有坑?分布是否合理?这些搞清楚了,后面的路才走得稳。

核心目标: 在建模前,通过统计和可视化手段,全面了解数据的质量、分布和潜在问题。
EDA 核心模块 探索性数据分析 数据概览 缺失值分析 异常值检测 单变量分布 shape info describe 统计量 箱线图 直方图 KDE

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」是否合理。

我的习惯: 如果某个特征的 max 比 75% 分位数大出好几个数量级,那基本可以断定有异常值。

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 的点通常被视为异常值。

避坑指南: 我曾经在一个电商数据集里用 Z-Score 检测「购买金额」的异常值,结果发现所有大额订单都被标记为异常。后来才意识到,这个字段是长尾分布,根本不适合用 Z-Score。对于偏态分布,IQR 法更稳妥。

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()
关键总结: EDA 不是一次性工作。每做完一个处理步骤(比如填充缺失值、处理异常值),都应该重新做一次分布分析,看看你的处理有没有引入新的问题。

嗯,到这里,数据的基本面貌应该已经清楚了。你知道了它有多大、哪里有空缺、哪些点不对劲、每个特征长什么样。这些信息,就是接下来做特征工程和建模的「地图」。带着这张地图走,不容易迷路。