4. 数据清洗:缺失值处理、异常值检测、数据类型转换与标准化

原始回测数据通常包含噪声、缺失字段、异常跳变以及不一致的格式。在将数据送入漂移哨兵(Drift Monitoring)系统之前,必须执行标准化的清洗流程。本章将围绕四个核心环节展开:缺失值处理、异常值检测、数据类型转换与标准化。

核心原则:清洗策略必须可复现、可审计。每次清洗操作都应记录转换日志,确保回测结果可追溯。

4.1 缺失值处理

缺失值在回测数据中常见于逾期天数、还款金额、用户信用分等字段。处理不当会导致统计偏差或模型训练失败。

缺失类型 常见字段 推荐处理方式 适用场景
完全随机缺失 用户收入、职业 删除(行删除) 缺失比例 < 5%
随机缺失 逾期天数、还款金额 中位数 / 均值填充 连续型变量,分布较对称
非随机缺失 信用评分、坏账标签 模型预测填充 / 标记缺失 缺失与目标变量相关
时间序列缺失 日收益率、交易量 前向填充 / 插值 时间依赖性强
实践建议:对于坏账率哨兵系统,坏账标签字段严禁随意填充。若缺失比例超过 10%,应单独标记为“未知”并评估其对回测结果的影响。
import pandas as pd
import numpy as np

# 示例:缺失值处理
df = pd.read_csv('loan_data.csv')

# 1. 删除缺失比例过高的列
df.dropna(thresh=len(df) * 0.8, axis=1, inplace=True)

# 2. 连续变量用中位数填充
df['overdue_days'].fillna(df['overdue_days'].median(), inplace=True)

# 3. 分类变量用众数填充
df['employment_status'].fillna(df['employment_status'].mode()[0], inplace=True)

# 4. 时间序列前向填充
df['daily_return'].fillna(method='ffill', inplace=True)

print(f"清洗后缺失值总数: {df.isnull().sum().sum()}")

4.2 异常值检测

异常值会严重扭曲回测的坏账率统计,导致哨兵系统产生误报。常用的检测方法包括统计阈值法、箱线图法和孤立森林。

  • Z-Score 法:适用于近似正态分布的数据,通常取阈值 3。
  • IQR 法:适用于偏态分布,定义异常为超出 Q1 - 1.5×IQR 或 Q3 + 1.5×IQR 的值。
  • 孤立森林:适用于高维数据,无需假设分布。
from scipy import stats
import numpy as np

# Z-Score 异常检测
z_scores = np.abs(stats.zscore(df['loan_amount']))
outliers_z = df[z_scores > 3]
print(f"Z-Score 检测异常数: {len(outliers_z)}")

# IQR 异常检测
Q1 = df['loan_amount'].quantile(0.25)
Q3 = df['loan_amount'].quantile(0.75)
IQR = Q3 - Q1
outliers_iqr = df[(df['loan_amount'] < Q1 - 1.5 * IQR) | (df['loan_amount'] > Q3 + 1.5 * IQR)]
print(f"IQR 检测异常数: {len(outliers_iqr)}")
注意:不要机械地删除所有异常值。在回测坏账率场景中,某些“异常”可能代表真实的极端风险事件(如突发性大规模逾期)。建议先标记,再结合业务逻辑判断是否剔除。
异常值检测与处理流程 原始回测数据 Z-Score 法 IQR 箱线图法 孤立森林 业务逻辑复核与标记 清洗后数据集

4.3 数据类型转换

回测数据常因来源不同导致类型混乱:日期存为字符串、金额存为对象、分类变量存为整数。类型转换是后续计算和建模的基础。

原始类型 目标类型 转换方法 常见问题
字符串日期 datetime64 pd.to_datetime() 格式不一致、时区缺失
对象型金额 float64 pd.to_numeric() 含逗号、货币符号
整数分类 category astype('category') 编码顺序错误
布尔型 int (0/1) astype(int) 缺失值转为 -1
# 数据类型转换示例
df['loan_date'] = pd.to_datetime(df['loan_date'], errors='coerce')
df['loan_amount'] = pd.to_numeric(df['loan_amount'].str.replace(',', ''), errors='coerce')
df['credit_rating'] = df['credit_rating'].astype('category')
df['is_default'] = df['is_default'].astype(int)

print(df.dtypes)

4.4 标准化与归一化

标准化(Standardization)和归一化(Normalization)用于消除量纲影响,使不同尺度的特征在哨兵模型中具有可比性。坏账率回测中,标准化更常用,因为它对异常值不敏感。

  • Z-Score 标准化:\( x' = \frac{x - \mu}{\sigma} \),适用于数据近似正态分布。
  • Min-Max 归一化:\( x' = \frac{x - min}{max - min} \),将数据缩放到 [0, 1] 区间。
  • Robust 标准化:使用中位数和 IQR,对异常值鲁棒。
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# Z-Score 标准化
scaler_std = StandardScaler()
df['amount_std'] = scaler_std.fit_transform(df[['loan_amount']])

# Min-Max 归一化
scaler_minmax = MinMaxScaler()
df['amount_norm'] = scaler_minmax.fit_transform(df[['loan_amount']])

# Robust 标准化
scaler_robust = RobustScaler()
df['amount_robust'] = scaler_robust.fit_transform(df[['loan_amount']])

print("标准化后统计量:")
print(df[['amount_std', 'amount_norm', 'amount_robust']].describe())
选择建议:如果后续使用线性模型或距离度量(如 KNN、K-Means),优先使用标准化;如果使用树模型(如 XGBoost、LightGBM),通常不需要标准化,但建议对数值型特征做分箱或排序转换。
三种标准化方法对比 原始数据分布 Z-Score 标准化 Min-Max 归一化 Robust 标准化 均值=0, 标准差=1 范围 [0, 1] 中位数=0, IQR 缩放 选择依据:数据分布形态、异常值比例、下游模型需求

4.5 清洗流程总结

完整的数据清洗流水线应按照以下顺序执行,以确保每一步的输出质量可控:

  1. 加载与预览:检查数据形状、字段列表、缺失概况。
  2. 缺失值处理:按字段类型和缺失机制选择填充或删除策略。
  3. 异常值检测:至少使用两种方法交叉验证,标记而非直接删除。
  4. 数据类型转换:统一日期、数值、分类字段的 dtype。
  5. 标准化/归一化:根据模型需求选择合适方法,保存 scaler 对象用于回测。
  6. 质量验证:检查清洗后数据的统计分布、相关性、缺失归零。
重要提醒:在回测坏账率哨兵系统中,清洗参数(如填充值、异常阈值、scaler 参数)必须与训练集保持一致。严禁在回测阶段使用未来数据计算填充值或标准化参数,否则将导致前视偏差,使回测结果失真。