3、数据采集与清洗:数据源类型、缺失值处理与异常值检测
风险归因模型的预测能力高度依赖输入数据的质量与广度。本章聚焦于数据采集阶段的多源异构数据整合,以及清洗环节中缺失值与异常值的系统性处理策略。高质量的特征工程始于对数据源特性的深刻理解与严格的清洗流程。
3.1 数据源类型与特征
风险归因模型通常融合三类数据源:市场数据、财务数据与另类数据。下表对比了各类数据的关键属性与典型应用场景。
| 数据源类型 | 更新频率 | 结构化程度 | 典型字段 | 风险归因用途 |
|---|---|---|---|---|
| 市场数据 | 高频(Tick/日频) | 高 | 价格、成交量、波动率、收益率曲线 | 计算市场因子暴露、流动性风险 |
| 财务数据 | 低频(季/年频) | 高 | 营收、净利润、资产负债率、现金流 | 基本面因子构建、信用风险评分 |
| 另类数据 | 中频(日/周频) | 中低 | 舆情情感、卫星图像、信用卡交易、供应链网络 | 事件驱动因子、非传统风险信号 |
数据源选择原则:优先保证市场数据的完整性与时效性;财务数据需对齐报告期并处理会计准则差异;另类数据应验证其与风险因子的因果逻辑,避免伪相关。
3.2 缺失值处理策略
缺失值在金融时间序列中普遍存在,处理不当会导致因子计算偏差或模型训练失败。处理流程需区分缺失机制与数据结构。
3.2.1 缺失机制分类
- 完全随机缺失(MCAR):缺失与观测数据无关,可直接删除或简单插补。
- 随机缺失(MAR):缺失依赖于其他观测变量,需使用模型插补(如MICE)。
- 非随机缺失(MNAR):缺失与未观测因素相关,需引入外部信息或敏感性分析。
3.2.2 常用处理方法对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 删除法(行/列) | 缺失率 < 5% 且 MCAR | 简单、无偏差 | 样本量损失、信息浪费 |
| 均值/中位数填充 | 数值型、低缺失率 | 计算快、保持均值 | 降低方差、扭曲分布 |
| 前向/后向填充 | 时间序列、短期缺失 | 保留时序趋势 | 不适用于跳跃性数据 |
| 线性插值 | 连续型、趋势明显 | 平滑、利用前后信息 | 对非线性序列效果差 |
| KNN插补 | 中小规模、特征相关 | 利用特征相似性 | 计算成本高、对异常值敏感 |
| 多重插补(MICE) | MAR、中等缺失率 | 保留不确定性、统计严谨 | 实现复杂、需迭代 |
实践建议:在风险归因场景中,对关键因子(如波动率、信用利差)的缺失值优先使用线性插补或MICE;对辅助特征(如行业分类)可使用众数填充。始终记录缺失模式,作为后续特征筛选的参考。
3.3 异常值检测与处理
异常值会扭曲因子载荷估计与协方差矩阵,导致风险归因结果失真。检测方法需结合数据分布特性与业务逻辑。
3.3.1 检测方法体系
- 统计方法:Z-score(正态分布)、IQR(箱线图,适用于偏态分布)、Grubbs检验。
- 距离方法:马氏距离(考虑特征相关性)、LOF(局部离群因子,适用于密度不均数据)。
- 模型方法:孤立森林(高维有效)、DBSCAN(基于密度聚类)、Autoencoder(非线性重构误差)。
3.3.2 处理策略
| 策略 | 操作方式 | 适用条件 |
|---|---|---|
| 截尾(Winsorize) | 将极端值替换为指定分位数(如1%/99%) | 保留样本量、控制极端影响 |
| 删除 | 直接移除异常样本 | 确认数据录入错误或不可修复 |
| 标记 | 增加二值特征标识异常点 | 保留信息、允许模型学习异常模式 |
| 变换 | 对数/Box-Cox变换压缩尺度 | 长尾分布、异方差性 |
风险提示:在金融数据中,极端收益率或信用事件本身可能是重要风险信号。切勿机械删除所有异常值,应结合业务背景判断:交易错误、数据录入错误可删除;市场崩盘、违约事件应保留并标记。
3.4 数据清洗流程架构
下图展示了从多源数据采集到清洗完成的标准化流程,涵盖缺失值与异常值的并行处理路径。
3.5 代码示例:缺失值与异常值处理
以下Python代码演示了使用pandas和scikit-learn对金融时间序列数据进行清洗的典型流程。
import pandas as pd
import numpy as np
from sklearn.impute import KNNImputer
from scipy import stats
# 加载示例数据(假设包含价格、成交量、财务比率)
df = pd.read_csv('risk_factor_data.csv', parse_dates=['date'])
df.set_index('date', inplace=True)
# ========== 缺失值处理 ==========
# 1. 前向填充(时间序列短期缺失)
df['price'].fillna(method='ffill', inplace=True)
# 2. 线性插值(连续型特征)
df['volume'] = df['volume'].interpolate(method='linear')
# 3. KNN插补(多特征联合,适用于MAR)
imputer = KNNImputer(n_neighbors=5)
cols_to_impute = ['pe_ratio', 'debt_ratio', 'roe']
df[cols_to_impute] = imputer.fit_transform(df[cols_to_impute])
# ========== 异常值检测与处理 ==========
# 1. IQR方法检测
Q1 = df['daily_return'].quantile(0.25)
Q3 = df['daily_return'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_iqr = (df['daily_return'] < lower_bound) | (df['daily_return'] > upper_bound)
# 2. Z-score方法(假设近似正态)
z_scores = np.abs(stats.zscore(df['daily_return'].dropna()))
outliers_z = z_scores > 3
# 3. 截尾处理(Winsorize)
df['daily_return_winsorized'] = df['daily_return'].clip(
lower=df['daily_return'].quantile(0.01),
upper=df['daily_return'].quantile(0.99)
)
# 4. 标记异常点(保留信息)
df['is_outlier'] = outliers_iqr.astype(int)
print(f"缺失值处理完成,剩余样本数: {len(df)}")
print(f"检测到异常值: {outliers_iqr.sum()} 个 (IQR方法)")
关键要点:
- 时间序列数据优先使用前向填充或线性插值,避免引入未来信息。
- 异常值检测应结合多种方法(如IQR+Z-score),降低误判率。
- 在风险归因中,对极端事件(如市场崩盘)建议标记而非删除,以保留尾部风险信息。