4. 数据清洗基础:处理缺失值、重复值与异常值
在贷后风控建模中,原始数据通常包含大量噪声。缺失值、重复记录和异常值会严重干扰LightGBM的特征学习过程,导致模型偏差或过拟合。本章将系统讲解三类基础清洗操作,确保进入特征筛选环节的数据质量可靠。
4.1 缺失值处理
缺失值在贷后数据中极为常见,例如客户收入字段缺失、历史逾期天数未记录等。处理策略需结合缺失率与业务含义。
4.1.1 处理策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 均值填充 | 数值型特征,数据近似正态分布 | 保持样本量,计算简单 | 降低方差,可能引入偏差 |
| 中位数填充 | 数值型特征,存在偏态或异常值 | 鲁棒性强,不受极端值影响 | 忽略分布尾部信息 |
| 删除缺失行 | 缺失率低(<5%)且随机缺失 | 保留原始分布 | 损失样本量,可能引入选择偏差 |
| 删除高缺失列 | 缺失率极高(>70%) | 减少噪声维度 | 可能丢失重要业务特征 |
贷后实践建议:对于收入、负债等偏态分布字段,优先使用中位数填充;对于逾期天数、催收次数等业务含义明确的字段,可考虑用0或业务默认值填充,而非机械使用统计量。
4.1.2 代码实现
import pandas as pd
import numpy as np
# 示例数据
df = pd.DataFrame({
'income': [5000, 6000, np.nan, 8000, 5500],
'overdue_days': [0, 5, 2, np.nan, 1],
'age': [25, 30, 35, np.nan, 28]
})
# 均值填充
df['income_filled_mean'] = df['income'].fillna(df['income'].mean())
# 中位数填充
df['income_filled_median'] = df['income'].fillna(df['income'].median())
# 删除缺失行(阈值:至少2个非空值)
df_dropped = df.dropna(thresh=2)
# 删除高缺失列(缺失率 > 50%)
threshold = 0.5
df_cleaned = df.loc[:, df.isnull().mean() < threshold]
4.2 重复值处理
重复记录通常由数据采集异常、多源合并或人工录入错误导致。在贷后场景中,重复的客户ID或订单号会放大特定样本的权重,干扰模型训练。
4.2.1 检测与去重
- 完全重复:所有字段值相同,直接删除保留一条。
- 部分重复:关键字段(如客户ID、合同号)重复,需按时间戳或数据源优先级保留最新记录。
# 检测完全重复
duplicates = df.duplicated()
print(f"完全重复行数: {duplicates.sum()}")
# 删除完全重复
df_unique = df.drop_duplicates()
# 按关键字段去重,保留最后一条记录
df_deduplicated = df.drop_duplicates(subset=['customer_id'], keep='last')
注意:不要盲目删除所有重复值。在贷后数据中,同一客户在不同时间点的多条记录(如多次催收记录)属于有效时序数据,应基于业务逻辑判断是否保留。
4.3 异常值处理(IQR法)
异常值会拉偏特征分布,影响LightGBM的分裂点选择。IQR(四分位距)法是一种基于统计分布的稳健检测方法,不假设数据服从正态分布。
4.3.1 IQR原理
IQR = Q3 - Q1,其中Q1为第一四分位数(25%分位),Q3为第三四分位数(75%分位)。异常值定义为:
- 下界:Q1 - 1.5 × IQR
- 上界:Q3 + 1.5 × IQR
- 超出上下界的值视为异常值。
4.3.2 代码实现与处理策略
def detect_outliers_iqr(df, column):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
return outliers, lower_bound, upper_bound
# 示例:检测收入字段异常值
outliers, lb, ub = detect_outliers_iqr(df, 'income')
print(f"异常值数量: {len(outliers)}")
print(f"正常范围: [{lb:.2f}, {ub:.2f}]")
# 处理策略1:截尾(Winsorize)——将异常值替换为边界值
df['income_winsorized'] = df['income'].clip(lower=lb, upper=ub)
# 处理策略2:删除异常值
df_no_outliers = df[(df['income'] >= lb) & (df['income'] <= ub)]
贷后场景特殊处理:对于逾期天数、催收次数等字段,业务上可能存在真实的高值(如逾期180天),不应简单视为异常值删除。建议先与业务团队确认阈值,或使用分位数截尾(如99%分位)替代固定1.5×IQR规则。
4.4 数据清洗流程总结
将三类清洗操作整合为标准化流程,确保每次建模前数据质量可控:
- 缺失值诊断:计算每列缺失率,分类处理(低缺失填充,高缺失删除)。
- 重复值清理:基于业务主键去重,保留最新或最完整记录。
- 异常值检测:对数值型特征应用IQR法,结合业务规则决定截尾或删除。
- 质量验证:检查清洗后数据量、分布形态,确保无信息泄露。
关键提醒:数据清洗操作必须在训练集和测试集上分别执行,且测试集应使用训练集计算出的填充值、边界值,避免数据泄露。LightGBM对缺失值有原生处理能力(默认将缺失值分到增益最大的方向),但在特征筛选前完成清洗仍有助于保持特征分布的一致性。