四、数据清洗与预处理:让原始数据真正可用
数据清洗,说白了就是“去伪存真”。很多刚入行的朋友,拿到数据就着急跑模型,结果效果差得离谱——你自己想想看,一堆空值、极端值跑出来的评分卡,谁敢用?我以前也吃过这个亏。
这一节,我重点讲缺失值和异常值怎么处理。我会把个人踩过的坑、总结出来的经验都塞进去。嗯,先看整体流程。
4.1 缺失值处理——不能扔,也别瞎填
缺失值在信贷数据里太常见了。比如年收入字段,有些用户没填;工作年限可能漏录。我的原则是:先分析缺失原因,再选填充方法。
4.1.1 均值填充(Mean Imputation)
最简单粗暴的方法。对数值型变量,用整体均值(或分组均值)去填。我习惯先做个分布检查——如果数据接近正态分布,均值填充效果还可以;如果有偏态,就会把均值拉偏。
💡 个人经验:在信用评分卡里,收入字段通常偏态严重。直接用整体均值填充,会导致中等收入段被高估。我一般先按职业或学历分组,再算组内均值填充。
# 均值填充示例
import pandas as pd
df['income'].fillna(df['income'].mean(), inplace=True) # 整体均值
# 分组均值更稳妥
df['income'] = df.groupby('education')['income'].transform(lambda x: x.fillna(x.mean()))
4.1.2 中位数填充(Median Imputation)
比均值更稳健。遇到数据分布不对称、或存在极端值时,我首选它。举个例子,信贷数据里“负债比率”往往有长尾——少数人负债超高,均值会被拉大,填出来失真。中位数就不会被带跑。
适用场景对照表
| 填充方法 | 适用数据分布 | 对异常值敏感度 | 常见信贷字段 |
|---|---|---|---|
| 均值填充 | 正态分布或近似正态 | 高 | 年龄、收入(分组后) |
| 中位数填充 | 偏态分布、有离群点 | 低 | 负债比率、贷款金额、逾期天数 |
| 模型预测 | 任何分布(需特征充足) | 中等(视模型) | 缺失率高的变量 |
4.1.3 模型预测填充(Model-based Imputation)
这个就有点“技术流”了。把缺失变量当作目标,用其他完整字段训练回归或分类模型(简单点用线性回归,复杂点用随机森林),然后预测缺失值。
我做过一个项目:客户“年收入”缺失率30%,用“职业”“学历”“地区”“家庭月支出”做预测填充,效果比均值好很多。不过要注意——别过度拟合,也别把目标变量偷偷放进去当特征。我曾经收过五年前的旧账,写代码时不小心把“信用评分”也当特征了,结果填充完的数据评分卡效果虚高,上线后直接崩了。嗯,惨痛教训。
# 模型预测填充(线性回归为例)
from sklearn.linear_model import LinearRegression
from sklearn.impute import SimpleImputer
# 拆出缺失行和非缺失行
miss = df[df['income'].isna()]
valid = df[df['income'].notna()]
X_train = valid[['education', 'occupation', 'region']] # one-hor编码略
y_train = valid['income']
X_pred = miss[['education', 'occupation', 'region']]
model = LinearRegression()
model.fit(X_train, y_train)
pred_income = model.predict(X_pred)
df.loc[miss.index, 'income'] = pred_income
⚠️ 避坑:模型预测填充的前提是其他特征与缺失字段存在相关性。如果特征与目标几乎无关,预测结果近似随机填充,还不如均值。建议先做相关性分析,r < 0.3 就别用模型了。
4.2 异常值处理——该砍就砍,该留就留
异常值在信贷评分卡里特别要小心。你想想看,一个客户年收入填了1亿,很明显是误填。但信用良好的客户中也可能有真实的高收入者,不能一刀切删光。
4.2.1 分位数截断(Quantile Capping / Winsorizing)
把极端值压缩到某个分位数上。比如把小于1%分位数的值替换为1%分位数,把大于99%分位数的值替换为99%分位数。
我习惯对连续变量(如收入、负债比、贷款金额)做这个处理。为什么不用直接删除?因为保留样本量。信用评分卡模型对样本数量很敏感,删多了数据稀疏,分箱也不够稳。
# 分位数截断(Winsorizing)
def winsorize_series(ser, lower=0.01, upper=0.99):
low = ser.quantile(lower)
high = ser.quantile(upper)
return ser.clip(low, high)
df['income'] = winsorize_series(df['income'], 0.01, 0.99)
df['debt_ratio'] = winsorize_series(df['debt_ratio'], 0.005, 0.995)
4.2.2 业务规则删除
有些异常值不是数据“异常”,而是绝对错误。比如年龄为 -1,职业为“无”但月收入10万(逻辑矛盾)。这些必须根据业务规则直接删除。
我通常列一个规则清单:
- 年龄 < 18 或 > 100 —— 删除
- 年收入 < 0 —— 删除
- 贷款金额 > 年收入的10倍 —— 可疑,先手动复核,批量处理时删除
- 逾期天数 < 0 —— 删除
⚠️ 注意业务规则的范围:曾经有一家小贷公司的数据,“月收入”字段里出现了0元,业务人员说是“学生用户没填”。但规则直接删了,后来发现这批人其实是优质代偿人群(有担保)。所以规则删除前一定要跟业务方沟通确认,别拍脑袋。
4.3 处理顺序与注意事项
我建议的顺序是:先处理异常值,再处理缺失值。为什么?因为异常值会污染均值、中位数乃至模型预测的填充结果。先截断或删除,缺失值填充时数据更干净。
当然,如果你是模型预测填充,可以在填充前先用中位数暂时填补缺失,然后做异常值处理,最后把临时填的去掉用真实预测值覆盖。但有点绕——初学者就按“异常→缺失”做,简单可靠。
4.4 本章工作流小结
- 缺失值:轻量用均值/中位数填充,重度缺失用模型预测;优先分组填充。
- 异常值:连续变量用分位数截断,逻辑错误用业务规则删除。
- 关键原则:多看业务、多问业务、多对比处理前后的分布。