2. 数据预处理:缺失值分析与填充策略归纳总结
2.1 缺失值:信用评分里的"隐形刺客"
做信用评分模型,最烦人的不是模型选不对,而是数据本身就不干净。我刚入行那会儿,拿到一份某平台的借贷数据,心想"这不直接建模嘛"。结果一跑代码,报警了一堆NaN。后来才发现——缺失值如果不处理,模型会偷偷把"不知道"当"没有",后果很严重。
说白了,缺失值就是样本里某些字段没录进去。比如客户年龄没填、收入字段空着、或者历史违约次数干脆是null。这和零值完全不是一回事:零表示"有,且为零";缺失表示"不知道,没记录"。模型可不会自动区分这两者,你得帮它辨别。
为什么会缺失?我总结了三种常见来源:
- 系统原因:数据库导出的bug、采集设备故障、数据接口中途断了。我在一个项目中遇到过,某合作方提供的"近半年逾期天数"字段,三分之一都是空——后来发现是他们内部系统升级漏写了日志。
- 用户原因:客户不愿意填敏感信息(比如年收入、职业);或者填表时选项里没有适合自己的(比如"其他"类)。
- 逻辑原因:某些字段只有在特定条件下才有意义。比如"失业时长"只对有工作经历的人才有值,未婚人士没有"配偶收入"。
核心原则:缺失值处理不是"填了就完事"。你得先弄清楚它为什么缺失,再去选方法。不同原因对应的处理策略完全不同。
2.2 缺失值分析的三个步骤
我个人习惯按这个流程来——不绕弯子,直接说:
- 统计每列的缺失率:用
df.isnull().mean()看一眼,心里有个数。 - 判断缺失机制:完全随机缺失(MCAR)、随机缺失(MAR)、非随机缺失(MNAR)。
- 决定处理策略:删除、填补、或者作为独立特征引入模型。
第2步最难。说实话,完全随机缺失在真实数据里很少见。更多是"高收入人群更不愿意填收入"这种选择性缺失。你想想看,如果模型自动补了一个平均收入,而实际上缺失的都是高收入样本——那预测就会严重偏倚。
下面这张图我常用在培训课上,把缺失值分析的决策路径画清楚:
2.3 常见填充方法对比
处理缺失值,说白了就两类思路:删掉,或者填上。但怎么删、怎么填,里面门道不少。我整理了一个表格,把最常用的几种方法拎出来对比:
| 方法 | 适用场景 | 优点 | 缺点 | Python实现 |
|---|---|---|---|---|
| 直接删除(行/列) | 缺失率 < 5% 且随机缺失 | 简单快速,不引入偏差 | 样本量骤减,可能丢失信息 | df.dropna() |
| 均值/中位数填充 | 连续变量,低缺失率 | 保持均值不变 | 缩小方差,扭曲分布 | df.fillna(df.median()) |
| 众数填充 | 分类变量 | 保持众数频率 | 可能引入偏态 | df['col'].fillna(df['col'].mode()[0]) |
| KNN插补 | 变量间有线性关系 | 利用相似样本信息 | 计算量大,需调K值 | from sklearn.impute import KNNImputer |
| 回归/模型预测填充 | 缺失变量与其他变量强相关 | 预测精度高 | 容易过拟合,需交叉验证 | 自定义模型 + predict() |
| 多重插补(MICE) | MCAR或MAR,需统计推断 | 考虑不确定性,结果稳健 | 实现复杂,不适用于海量数据 | from sklearn.experimental import enable_iterative_imputer |
我的小习惯:对于信用评分场景,我一般先看缺失率。如果某字段缺失超过40%,我会直接丢弃,因为填补的风险太大。如果缺失率在5%~30%之间,我会先用中位数或KNN试一下,再用影子特征(即新增一个0/1列标记是否原本缺失)来观察模型对填补值的依赖程度。
2.4 避坑指南:这些坑我替你踩过
坑1:用全局均值填充分组结构明显的变量
我曾经处理过一份"月收入"数据,低收入客户缺失率低,高收入客户缺失率高。用全样本均值去填——结果低收入客户的收入被拉高了,模型训练出来居然判定"收入越高违约风险越大",完全反了。后来我改成按职业分组的均值填充,才纠正过来。
坑2:分类变量用众数填充后忘记做独热编码
嗯,这个错误我犯过一次。众数填充后字段里仍然有缺失值转成的"其他"类别,但后续的独热编码把"其他"当成了新的一列,模型理解错了语义。正确做法是先填充再编码,或者把缺失本身作为一个独立类别。
坑3:缺失特征作为"负向信号"直接丢弃
有时缺失本身就是一个信息。比如,客户没填工作单位,可能暗示他工作不稳定。我个人习惯在填补的同时,生成一个is_missing的0/1标志列,让模型自己学是否应该重视这个"缺失"信号。
特别注意:不要在测试集上使用训练集的统计数据以外的信息来填补。正确做法是:先把训练集和测试集分开,在训练集上计算填充值(均值、中位数等),然后应用到测试集。否则会造成数据泄露(data leakage),模型评估结果虚高,上真实环境就崩。
2.5 代码实战:快速实现缺失值分析
这部分我直接给核心代码,你跑一遍就能看到效果:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
# 假数据示例
np.random.seed(42)
df = pd.DataFrame({
'age': np.random.randint(20, 60, 1000).astype(float),
'income': np.random.normal(50000, 15000, 1000).astype(float),
'default': np.random.choice([0,1], 1000)
})
# 人为制造缺失
df.loc[df.sample(200).index, 'income'] = np.nan
df.loc[df.sample(100).index, 'age'] = np.nan
# 第一步:缺失率概览
missing_ratio = df.isnull().mean() * 100
print("缺失率(%):\n", missing_ratio)
# 第二步:按是否缺失分组看分布差异
df['income_missing'] = df['income'].isnull().astype(int)
print("\n收入缺失 vs 非缺失的违约率对比:")
print(df.groupby('income_missing')['default'].mean())
# 第三步:训练集上计算中位数,填充
train, test = train_test_split(df, test_size=0.2, random_state=42)
income_median = train['income'].median()
train['income_filled'] = train['income'].fillna(income_median)
test['income_filled'] = test['income'].fillna(income_median)
# 检查填充后分布
print("\n填充后的收入统计:")
print(train['income_filled'].describe())
这段代码跑完后,你会看到收入缺失的样本违约率往往更高——这正是我之前踩过的那个坑的雏形。如果直接填充均值而不保留缺失标志,模型就会丢失这个重要信号。
2.6 归纳总结:适合信用评分的填充策略
信用评分数据通常缺失率不高,但一旦缺失往往带有信息。我最后给个推荐方案:
- 连续变量:先用中位数填充(稳健),再生成
is_missing标志。如果缺失率>20%,考虑KNN或回归插补。 - 分类变量:把缺失本身当作一个类别(如
UNKNOWN),不特意填充。信用评分里"不确定"本身就有风险含义。 - 如果有时间序列:使用前向填充(forward fill),但要注意业务逻辑是否允许。
- 验证流程:一定要在训练集上计算填充参数,测试集只应用,不重新计算。
做缺失值分析,其实是在和"信息不完整"打交道。别怕花时间。我做了几年项目,发现最耗时的不是调模型,而是理解数据为什么缺失。等你把这一步做扎实了,后续特征筛选和建模才会顺。