第4章 数据质量检查与清洗归纳总结
信用评分模型说白了就是“垃圾进,垃圾出”。你调参再猛、算法再花哨,遇到烂数据也是白搭。我自己做过好几个项目,最惨的一次是接手一套银行流水数据,拿来就直接建模,结果评分分布跟实际违约情况完全对不上。折腾了两周才发现,原始数据里有个字段的“空值”被填成了“-999”,模型当有效值算了。所以,数据质量检查这一步,我从来不跳。
4.1 数据质量检查的五个维度
我习惯把数据质量拆成五个维度来审视。每个维度的检查重点和常见问题,我整理了一张表:
| 维度 | 检查内容 | 常见问题 | 我的处理建议 |
|---|---|---|---|
| 完整性 | 字段是否为空、缺失比例 | 空值过多、占位符填充(如“0”、“-1") | 先统计缺失率,高于30%的字段考虑删除 |
| 一致性 | 同一属性在不同表中是否冲突 | 客户性别在A表是“男/女”,B表是“M/F” | 建立字典映射,统一编码 |
| 准确性 | 值是否落在合理范围 | 年龄200岁、收入-5000 | 设置上下界,超界标记为异常 |
| 时效性 | 时间戳是否合理、是否过时 | 贷款日期在出生日期之前 | 做业务规则校验 |
| 唯一性 | 是否有重复记录 | 同一笔申请出现多次 | 按主键去重,保留最新一条 |
我个人经验是,完整性和一致性出问题的概率最高,也是最能影响模型稳定性的因素。检查阶段多花一小时,建模阶段就能省一天。
4.2 常见垃圾数据及其“解毒”方法
现实中遇到的数据,远没有教科书那么干净。我来列几个我踩过坑的真问题:
缺失值处理
缺失值是老生常谈了,但处理方式不能一概而论:
- 删除:缺失率极高(>70%)或缺失值完全随机的样本,直接丢掉。但注意不要因为删除破坏分布。
- 填充:连续变量用中位数或均值,离散变量用众数。我建议优先用中位数,它不受极端值影响。
- 建模预测:用其他字段训练轻量模型来填充。我在某大额信贷项目里就试过,效果比均值填充好15%的KS。
- 单独标记:给缺失值设一个特殊取值(如-1),并保留缺失指示变量。这招对树模型特别管用。
import pandas as pd
import numpy as np
# 假设df已经加载
# 检查缺失比例
missing_ratio = df.isnull().sum() / len(df)
print(missing_ratio[missing_ratio > 0.3]) # 高缺失率字段
# 中位数填充连续变量
for col in ['age', 'income', 'loan_amount']:
df[col].fillna(df[col].median(), inplace=True)
# 众数填充离散变量
df['education'].fillna(df['education'].mode()[0], inplace=True)
# 创建缺失指示变量
df['income_missing'] = df['income'].isnull().astype(int)
我的习惯:填充前先看一眼缺失值的业务含义。比如“年收入”为空,有时候是因为客户是学生或无业,那填充中位数反而是错误的。这种情况下,我宁可单独分一个“收入未知”的类别。
异常值识别与处理
异常值会严重拉偏统计指标,尤其对线性模型影响大。常用方法:
- 3σ原则:数值超出均值±3倍标准差,视为异常。适合近似正态分布的数据。
- 箱线图法:IQR(四分位距)的1.5倍以外定义为异常。更稳健,适合偏态数据。我一般用这个。
- 业务规则:比如“年龄<18”或“贷款金额大于1亿”,直接按业务常识判异常。
# 箱线图法剔除异常值
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df = df[(df['income'] >= lower_bound) & (df['income'] <= upper_bound)]
警告:不要一刀切剔除所有异常值。信用评分里某些“异常”可能是高风险的信号(比如短期内多次借贷),删掉反而会丢失重要模式。我建议做两层标记:先保留异常标识,建模时尝试用分箱或截断来处理,而不是直接删除。
重复记录与字段冲突
重复数据通常来自多源合并或系统bug。我在一个联合贷项目里遇到过一个借款人被登记了两次,仅身份证号最后一位不同。处理步骤:
- 按主键(身份证号、申请编号)去重,保留时间最近的一条。
- 若主键相同但其他字段不同,取字段的众数或最新值。
- 去掉完全相同的重复行。
# 按身份证号去重,保留最后一条记录
df = df.sort_values('apply_date').drop_duplicates(subset='id_card', keep='last')
4.3 数据清洗流程图
下面这张图是我每次做项目都会贴在白板上的数据清洗流程,分成四步——抽取、检查、处理、验证。每一步都有对应的动作和产出物。
4.4 避坑指南——我亲历的三个教训
讲完方法,聊点真心话。下面三个坑我全踩过,现在每次都会多看一眼:
- 别忽略数字型字段里的“假缺失”。曾经一个数据源把收入为0表示失业,0不是缺失。但我的缺失检测脚本直接把0当成了正常值,导致失业群体被当作高收入处理。现在我都会先用
value_counts()看一下字段的取值分布,确认有没有特殊占位符。 - 质量检查报告必须过业务方。有一次我独立清理完数据,以为完美了,结果业务同事说:“这个字段‘婚姻状况’的单身和未婚是不同意思,你怎么合并了?”我也没辙,只能回炉重做。所以我现在每步清洗都会写一个简单的质量报告,让业务确认。
- 清洗后的数据集要保留原始备份。别直接覆盖源数据。我在一个Kaggle比赛里因为原地修改数据,后面发现异常值删多了,想回退却发现原始数据没了。现在我的习惯是用
df_raw保存原始副本,所有变换都新建字段。
重要提醒:数据清洗不是一次性的。在建模过程中,如果发现模型某些特征表现异常,很可能需要回溯到清洗步骤,检查是否有遗漏的质量问题。所以,我建议把清洗流程写成可复用的函数或脚本,方便随时重新跑。
4.5 本章核心要点
最后,用一个清单来收尾:
- 检查完整性(缺失率)、一致性(编码冲突)、准确性(范围)、时效性(时间逻辑)、唯一性(重复记录)。
- 缺失值处理:删除、填充、预测、单独标记——根据业务场景选。
- 异常值处理:箱线法比3σ更稳健,保留异常标识比直接删除更安全。
- 重复数据:按主键去重,冲突字段按最新值或众数处理。
- 永远保留原始数据备份,清洗过程可复现。
- 质量报告要经业务方确认,避免领域知识误解。