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
  • 超出上下界的值视为异常值。
IQR异常值检测示意图 中位数 (Q2) Q1 Q3 下界 Q1-1.5×IQR 上界 Q3+1.5×IQR 异常值 异常值 IQR = Q3 - Q1 超出 [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 数据清洗流程总结

将三类清洗操作整合为标准化流程,确保每次建模前数据质量可控:

  1. 缺失值诊断:计算每列缺失率,分类处理(低缺失填充,高缺失删除)。
  2. 重复值清理:基于业务主键去重,保留最新或最完整记录。
  3. 异常值检测:对数值型特征应用IQR法,结合业务规则决定截尾或删除。
  4. 质量验证:检查清洗后数据量、分布形态,确保无信息泄露。
贷后数据清洗标准流程 原始数据 缺失值处理 重复值处理 异常值处理 截尾(Winsorize) 删除异常行
关键提醒:数据清洗操作必须在训练集和测试集上分别执行,且测试集应使用训练集计算出的填充值、边界值,避免数据泄露。LightGBM对缺失值有原生处理能力(默认将缺失值分到增益最大的方向),但在特征筛选前完成清洗仍有助于保持特征分布的一致性。