四、数据清洗与预处理:让原始数据真正可用

数据清洗,说白了就是“去伪存真”。很多刚入行的朋友,拿到数据就着急跑模型,结果效果差得离谱——你自己想想看,一堆空值、极端值跑出来的评分卡,谁敢用?我以前也吃过这个亏。

这一节,我重点讲缺失值和异常值怎么处理。我会把个人踩过的坑、总结出来的经验都塞进去。嗯,先看整体流程。

数据清洗与预处理流程 原始数据 缺失值处理 均值填充 中位数填充 模型预测 异常值处理 分位数截断 业务规则删除 清洗后数据

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 本章工作流小结

  • 缺失值:轻量用均值/中位数填充,重度缺失用模型预测;优先分组填充。
  • 异常值:连续变量用分位数截断,逻辑错误用业务规则删除。
  • 关键原则:多看业务、多问业务、多对比处理前后的分布。