数据获取与清洗:构建稳健多因子模型的基石
做多因子模型,有个残酷的现实:你花在数据清洗上的时间,往往比建模本身还多。我刚开始做量化那会儿,总觉得策略牛逼就行,结果被数据坑过好几次。后来学乖了——数据质量不过关,再漂亮的模型也是空中楼阁。
今天咱们就聊聊数据获取与清洗这四板斧:数据源选择、缺失值处理、去极值、标准化。每一步都有坑,我一个个说。
一、数据源选择:别在源头就输了
数据源的选择,直接决定了你后面所有工作的质量。我个人习惯把数据源分成三类:
| 类型 | 代表 | 优点 | 缺点 |
|---|---|---|---|
| 商业数据库 | Wind、Bloomberg | 数据全、质量高、有售后 | 贵,个人用户不友好 |
| 开源平台 | Tushare、AKShare | 免费、社区活跃 | 偶有数据缺失、接口不稳定 |
| 量化平台 | 聚宽、米筐 | 自带清洗、回测方便 | 数据范围受限 |
这里有个关键点:不同数据源对同一只股票的同一指标,可能给出不同值。我在项目中遇到过,用Wind和Tushare分别取某只股票的市盈率,差了0.3。原因可能是复权方式不同,也可能是数据源更新频率不一样。所以,同一个因子,全程只用同一个数据源,别混着用。
二、缺失值处理:别让NaN毁了你的模型
拿到数据后第一件事——检查缺失值。你想想看,如果某个因子在30%的股票上都是NaN,那这个因子基本废了。
处理缺失值,我一般按这个优先级来:
- 删除法:缺失比例超过50%的因子,直接扔掉。别心疼。
- 填充法:缺失比例在10%-50%之间,用行业均值填充。为什么用行业均值?因为同行业的公司财务特征更接近。
- 插值法:时间序列数据,用前后值线性插值。比如某天停牌导致数据缺失,用前后交易日的数据补上。
代码实现其实很简单:
import pandas as pd
import numpy as np
# 假设df是因子数据,index是股票,columns是时间
# 1. 删除缺失率超过50%的因子
df = df.dropna(thresh=len(df)*0.5, axis=1)
# 2. 用行业均值填充
# 假设有行业标签列 'industry'
df = df.groupby('industry').transform(lambda x: x.fillna(x.mean()))
# 3. 时间序列插值
df = df.interpolate(method='linear', axis=0)
三、去极值:别让异常值带偏了
因子数据里经常有极端值。比如某只股票因为重组,市盈率突然飙到1000倍。这种值如果不处理,会严重扭曲后续的标准化和模型结果。
常用的去极值方法有三种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| MAD法 | 基于中位数绝对偏差 | 数据分布偏态严重时 |
| 百分位法 | 截断上下1%/5% | 简单粗暴,快速有效 |
| 3σ法 | 超过均值±3倍标准差 | 数据近似正态分布时 |
我个人最常用的是MAD法。为什么?因为中位数对异常值不敏感,比均值稳健得多。你想想看,如果数据里有几个极端值,均值会被拉偏,但中位数基本不受影响。
代码示例:
def winsorize_mad(series, n=5):
"""MAD法去极值"""
median = series.median()
mad = np.abs(series - median).median()
upper = median + n * mad
lower = median - n * mad
return series.clip(lower, upper)
# 应用
df_clean = df.apply(winsorize_mad, axis=0)
四、标准化:让不同因子站在同一起跑线
做完去极值,下一步就是标准化。为什么要标准化?因为不同因子的量纲不一样。比如市盈率可能是几十倍,而换手率是百分之几。如果不标准化,模型会天然偏向数值大的因子。
常用的标准化方法:
- Z-score标准化:(x - mean) / std。假设数据服从正态分布,标准化后均值为0,标准差为1。
- Min-Max标准化:(x - min) / (max - min)。把数据压缩到[0,1]区间,但容易受极端值影响。
- Rank标准化:把数值转换成排名,再映射到[0,1]。对异常值最鲁棒,但丢失了数值间的距离信息。
代码实现:
def standardize_zscore(df):
"""截面Z-score标准化"""
mean = df.mean(axis=0)
std = df.std(axis=0)
return (df - mean) / std
# 每个时间点独立标准化
df_standardized = df.groupby(level='date').apply(standardize_zscore)
五、完整流程:从原始数据到可用因子
好了,把上面四步串起来,就是一个完整的数据清洗流水线:
def clean_factor_pipeline(df_raw):
"""
完整的数据清洗流程
输入:原始因子数据
输出:清洗后的因子数据
"""
# 1. 删除缺失过多的因子
df = df_raw.dropna(thresh=len(df_raw)*0.5, axis=1)
# 2. 行业均值填充缺失值
df = df.groupby('industry').transform(lambda x: x.fillna(x.mean()))
# 3. MAD法去极值
df = df.apply(winsorize_mad, axis=0)
# 4. Z-score标准化
df = df.groupby(level='date').apply(standardize_zscore)
return df
嗯,到这里,数据清洗的核心内容就讲完了。记住一句话:数据清洗不是锦上添花,而是生死攸关。我见过太多人花几个月建模型,结果因为数据没洗干净,回测曲线漂亮得不行,实盘一跑就崩。别走那条弯路。
公众号:蓝海资料掘金营,微信deep3321