第1章:探索性数据分析(EDA)—— 先别急着建模,看看数据长什么样

说实话,我见过太多人一上来就调包跑模型。结果呢?模型效果差,还找不到原因。其实,EDA 才是建模中最值钱的一步。我个人习惯,拿到数据后至少花 40% 的时间做探索性分析。今天我们就聊聊 EDA 中的几个核心环节:单变量分析、双变量分析、相关性矩阵,以及一些可视化技巧。

1. 单变量分析:先摸清每个特征的脾气

单变量分析,说白了就是单独看每个变量长什么样。我一般会从两个角度入手:分布形态统计量

1.1 统计量:快速定位异常

对于连续型变量,我会先看均值、中位数、标准差、最小值、最大值。嗯,这里有个小技巧:如果均值远大于中位数,说明数据右偏,可能有极端大值。我在项目中遇到过一笔信贷数据,年收入字段的最大值竟然是 99999999,一看就是录入错误。

避坑指南: 我曾经因为没检查最大值,导致模型把亿万富翁和普通工薪族混在一起训练,结果违约概率预测完全失效。所以,先跑个 describe() 看看
import pandas as pd
import numpy as np

# 加载数据
df = pd.read_csv('credit_data.csv')

# 连续变量统计量
print(df[['age', 'income', 'loan_amount', 'credit_score']].describe())

对于分类变量,我会看频数和占比。比如“教育水平”这个字段,如果某个类别占比不到 1%,我通常会考虑合并到“其他”类别中。

1.2 分布可视化:一眼看出问题

直方图 + 箱线图是我最常用的组合。直方图看整体形状,箱线图看异常点。

import matplotlib.pyplot as plt
import seaborn as sns

fig, axes = plt.subplots(1, 2, figsize=(12, 4))

# 直方图
sns.histplot(df['income'], bins=50, kde=True, ax=axes[0])
axes[0].set_title('收入分布')

# 箱线图
sns.boxplot(x=df['income'], ax=axes[1])
axes[1].set_title('收入箱线图')

plt.tight_layout()
plt.show()

你想想看,如果收入分布图右侧拖了一条长长的尾巴,那大概率有高收入群体,或者数据有问题。我一般会结合业务逻辑判断:年收入 500 万以上的人来申请小额贷款?这不太合理吧。

2. 双变量分析:违约率和特征到底啥关系?

单变量分析只是热身。真正关键的是双变量分析——看每个特征和违约率(target)之间的关系。

2.1 连续变量 vs 违约率

我习惯用分箱违约率图。把连续变量分成 10 组,计算每组的违约率,然后画折线图。这样能直观看到:收入越高,违约率是不是越低?

# 对收入分箱
df['income_bin'] = pd.qcut(df['income'], q=10, labels=False)

# 计算每箱违约率
bin_default = df.groupby('income_bin')['default'].mean()

# 画图
plt.figure(figsize=(10, 5))
plt.plot(bin_default.index, bin_default.values, marker='o')
plt.xlabel('收入分箱(从低到高)')
plt.ylabel('违约率')
plt.title('收入与违约率的关系')
plt.grid(True, alpha=0.3)
plt.show()
我的经验: 如果折线图呈现单调递减趋势,说明这个特征区分度很好。如果曲线像过山车一样上下波动,那可能这个特征和违约率没有线性关系,或者样本量不够。

2.2 分类变量 vs 违约率

对于分类变量,直接画分组柱状图。比如看不同婚姻状况下的违约率差异。

# 分组柱状图
default_rate_by_marital = df.groupby('marital_status')['default'].mean().sort_values()

plt.figure(figsize=(8, 4))
default_rate_by_marital.plot(kind='bar', color='steelblue')
plt.ylabel('违约率')
plt.title('不同婚姻状况的违约率')
plt.xticks(rotation=45)
plt.show()

我记得有一次分析发现,“离异”群体的违约率明显高于“已婚”群体。业务方一开始不信,后来调出详细数据一看,确实如此。这就是 EDA 的价值——用数据说话

3. 相关性矩阵:别让特征“互相打架”

建模时最怕什么?多重共线性。两个特征高度相关,模型会变得不稳定。我一般用相关系数矩阵 + 热力图来排查。

# 计算相关系数矩阵
corr_matrix = df[['age', 'income', 'loan_amount', 'credit_score', 'default']].corr()

# 热力图
plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt='.2f', linewidths=0.5)
plt.title('特征相关性热力图')
plt.show()
注意: 相关系数绝对值超过 0.7 就要警惕了。我曾经在建模时没处理“贷款金额”和“月还款额”的强相关性(0.85),结果逻辑回归的系数符号都反了。后来删掉一个特征,模型才恢复正常。

另外,不要只看数值相关性。分类变量可以用卡方检验,或者用 Cramer's V 系数。我一般会写个函数,一次性输出所有特征对的相关性强度。

4. 可视化技巧:让数据自己说话

好的可视化,能让你一眼看出问题。我总结了几个实用技巧:

  • 颜色别太花哨:用 2-3 种颜色就够了。我常用蓝色系和红色系,蓝色代表正常,红色代表违约。
  • 添加参考线:比如在违约率图上画一条整体平均违约率的水平线,方便对比。
  • 分面绘图:如果特征很多,用 FacetGrid 一次性画多个图,效率高。
# 分面绘图示例:看不同教育水平下,收入与违约率的关系
g = sns.FacetGrid(df, col='education', col_wrap=3, height=4)
g.map(sns.scatterplot, 'income', 'default', alpha=0.3)
plt.show()

说实话,可视化不是为了好看,而是为了发现模式。我经常盯着图看几分钟,然后突然发现某个特征在某个区间内违约率异常高——这就是建模的突破口。

5. 本章知识体系总览

下面这张图,是我自己总结的 EDA 核心流程。你可以把它当作一个检查清单:

EDA 核心流程 1. 数据加载与清洗 2. 单变量分析(分布 + 统计量) 3. 双变量分析(特征 vs 违约率) 4. 相关性矩阵(排查共线性) 5. 生成可视化报告 & 总结

嗯,这张图把 EDA 的五个关键步骤串起来了。你每次建模前,都可以对照着走一遍。别偷懒,这一步做好了,后面建模会顺畅很多。

核心总结:
  • 单变量分析:用 describe() 和直方图,快速发现异常值和分布形态。
  • 双变量分析:分箱违约率图和分组柱状图,找到特征与目标的关系。
  • 相关性矩阵:热力图排查多重共线性,相关系数超过 0.7 要处理。
  • 可视化技巧:简洁、有参考线、分面绘图,让数据自己说话。

好了,这一章的内容就到这里。记住,EDA 不是走过场,而是你和数据之间的深度对话。下一章我们会聊特征工程,到时候见。


专注资料整理