4. 探索性数据分析(EDA)基础

数据拿到手,别急着建模。我见过太多人上来就调参,结果模型跑出来一塌糊涂——回头一看,原始数据里全是坑。说白了,EDA 就是给数据做个体检,先把毛病找出来。

这一章我带大家过一遍 EDA 的关键操作。嗯,都是我在实际项目中反复踩过的坑,你想想看,要是能提前避开,能省多少事。

4.1 数据概览,先摸清家底

第一步,看看数据长什么样。我个人习惯先跑几个最基础的方法:

  • shape — 行数和列数,心里有个数
  • dtypes — 每个字段的类型,数值型、类别型、文本型,分清
  • describe() — 数值列的统计摘要:均值、标准差、分位数
  • head() / tail() — 头尾几行,感受一下数据格式

别的先别管,先把这几样过一遍。我在某次信贷评分项目里,就靠 describe() 发现了一个奇怪的年收入字段——最小值是 0,最大值是 9.99 亿。你想想看,正常打工族年收入能到 9 亿吗?明显是脏数据。

import pandas as pd

df = pd.read_csv('credit_data.csv')
print("Shape:", df.shape)
print("\nDtypes:\n", df.dtypes)
print("\nDescribe:\n", df.describe(include='all'))
我的小习惯:describe() 里加个 include='all',把类别列也带出来,省得漏掉。

4.2 缺失值,最容易被忽视的敌人

缺失值几乎每份数据都有。但怎么处理,取决于缺失的原因和比例。

我是这么干的:先画一张缺失值矩阵图,一目了然。再计算每列的缺失比例,做个排序。

missing_ratio = df.isnull().sum() / len(df)
missing_ratio = missing_ratio[missing_ratio > 0].sort_values(ascending=False)
print(missing_ratio)

然后按比例分三档处理:

缺失比例 建议处理方式 踩过的坑
< 5% 直接删除该行,或均值/中位数填充 别填 0,会拉偏分布
5% – 30% 做缺失值插补,比如 KNN、回归法 插补前先检查是否与目标变量相关
> 30% 删除该变量,它信息量太少 有一次我保留了 40% 缺失的变量,结果模型权重几乎都给了它,全模型废了
避坑指南:我曾经犯过一个低级错误——用全体均值填充分类变量的缺失值。结果把“婚姻状况”字段填成了 0.5,你想想看,这个人算未婚还是已婚?数值型变量才能用均值填充,类别变量请用众数。

4.3 异常值,别让一个离群点带偏整个模型

异常值在信用评分里特别要命。一个收入异常高的申请人,可能只是录入时多打了两个零,但模型会以为他是优质客户——这就麻烦了。

我常用的检测方法:

  • 箱线图 (boxplot) — 看四分位距,超过 1.5 倍 IQR 的点就是异常
  • Z-score — 超过 3 个标准差的点,基本可以怀疑是异常
  • 业务逻辑阈值 — 比如年龄必须 18–100 岁,收入不能为负

举个例子,你算算 Z-score:

from scipy import stats
import numpy as np

z_scores = np.abs(stats.zscore(df['age']))
outliers = df[z_scores > 3]
print(f"发现 {len(outliers)} 个年龄异常值")

怎么处理?分情况:

  • 明显录入错误(年龄 200 岁)→ 直接删除或设为 NaN 再填充
  • 真实但极端(年收入 2000 万,但确实是企业家)→ 做截尾处理,比如把 99% 分位数以上的值替换为 99% 分位数

记住一句话:异常值不一定是错误,但必须先识别出来,再结合业务判断。

4.4 分布分析,看看数据长啥样

了解每个变量的分布,对后续特征工程和模型选择都至关重要。

我一般画两类图:

  • 直方图 + 核密度估计 — 看是不是正态分布,有没有偏态、双峰
  • Q-Q 图 — 直观对比数据分布 vs 理论正态分布

为什么关心分布?因为很多模型(比如线性回归)假设特征是正态的。如果你的数据是长尾分布,可以试试 log 变换或 Box-Cox 变换。

import matplotlib.pyplot as plt
import seaborn as sns

fig, ax = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(df['loan_amount'], kde=True, ax=ax[0])
ax[0].set_title('贷款金额分布')
stats.probplot(df['loan_amount'], dist='norm', plot=ax[1])
ax[1].set_title('Q-Q 图')
plt.tight_layout()
plt.show()
个人经验:信用评分数据里,贷款金额、收入、负债比这三个变量几乎永远是右偏的。别犹豫,直接做对数变换,模型对数值型特征的敏感性会下降,AUC 能提 2–3 个百分点。

4.5 相关性分析,别给模型喂一堆重复信息

特征之间高度相关,会导致多重共线性,影响模型稳定性和可解释性。

我习惯先算皮尔逊相关系数矩阵,再用热力图可视化:

corr_matrix = df.corr()
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm', center=0)
plt.title('特征相关性热力图')
plt.show()

发现相关系数大于 0.8 的成对特征——比如“月收入”和“年收入”——只保留一个。我个人倾向保留信息更丰富、缺失更少的那个。

避坑指南:我曾经遇到过两个特征相关系数高达 0.95——仔细一查,原来是同一个字段重复存了两列。所以 EDA 也要查一下是不是有重复列。

4.6 EDA 全流程一张图

下面是我做 EDA 的脑图,每次开工前扫一眼,稳:

EDA 标准流程 原始数据 1. 数据概览 (shape, dtype, describe) 2. 缺失值分析 & 填充 3. 异常值检测 & 处理 4. 分布分析 & 变换 5. 相关性分析 & 降维 清洗后的干净数据 → 建模 说明:每一步都是可迭代的,检测到异常后可能需要重新审核缺失值处理策略

4.7 别忘了业务验证

最后一点,技术指标做完了,记得拿给业务人员看一眼。EDA 不只是跑代码,更是和数据背后的业务逻辑对话。

比如,你发现“逾期天数”字段里居然有负数?业务方告诉你那是因为系统提前还款会录成负值——这就不算异常,而是业务标签。

我见过最极端的案例:某份数据里“性别”字段的 0 和 1 定义反了,但 EDA 时没人发现。模型上线后,男性申请人违约率被低估了 30%。

所以,多做一步交叉验证:把分布图和业务常识核对一遍。你想想看,如果数据告诉你本科生比博士生收入还高,那八成是数据有毛病。