第1章:因子数据处理——从原始数据到干净信号

做量化投资这些年,我最大的体会就是:因子数据决定了模型的上限。你模型再花哨,数据是脏的,结果就是垃圾进垃圾出。今天咱们就聊聊因子数据处理的五个核心步骤——数据清洗、缺失值处理、去极值、标准化、中性化。

说白了,这五步就是把原始数据「洗」成能用的信号。我刚开始做多因子模型时,跳过过其中几步,结果回测曲线漂亮得不行,实盘一跑就崩。嗯,教训深刻。

核心观点:因子数据处理不是可选项,是必选项。跳过任何一步,你的模型都可能存在隐患。

因子数据处理流程 数据清洗 去重·格式统一 缺失值处理 删除·填充·插值 去极值 MAD·百分位·标准差 标准化 Z-score·Rank 中性化 行业·市值回归 顺序执行,每一步的输出是下一步的输入 输出:干净、可用的因子信号

1.1 数据清洗——先把垃圾倒掉

数据清洗是第一步,也是最容易被忽视的一步。很多人拿到数据就直接开始算因子,结果跑出来的信号全是错的。

我个人习惯先做三件事:

  • 去重:同一只股票在同一天出现两条记录?删掉一条。我在项目中遇到过因为数据源重复推送,导致因子值翻倍的情况,回测收益直接虚高了一倍。
  • 格式统一:股票代码有的是600001.SH,有的是600001,还有的是600001.ss。不统一的话,后面合并数据时会出大问题。
  • 异常值初步筛查:比如市盈率出现负数,或者市值突然变成0。这些明显不合理的数据,先标记出来。

小技巧:数据清洗时,我习惯先做一份「数据质量报告」,统计每列的缺失率、唯一值数量、最大值最小值。这样能快速发现异常。

1.2 缺失值处理——别让空值毁了你的模型

因子数据里出现缺失值太正常了。新股上市第一天没有历史数据,停牌期间没有交易数据,这些都是缺失值的来源。

处理缺失值,常用的方法有三种:

方法 适用场景 注意事项
直接删除 缺失比例很小(<5%) 删除后样本量不能太少
均值/中位数填充 缺失比例适中(5%-20%) 会降低因子方差,影响区分度
插值法 时间序列数据 线性插值或前向填充

我曾经犯过一个错误:对市盈率因子用均值填充,结果填充后的因子完全失去了区分高估值和低估值股票的能力。后来我改用行业均值填充,效果好了很多。

为什么会这样?因为不同行业的市盈率天然不同,用全市场均值填充,相当于把银行股和科技股混在一起处理,信息全丢了。

# 示例:用行业均值填充缺失值
import pandas as pd

def fill_missing_by_industry(df, factor_col, industry_col):
    """
    按行业均值填充缺失值
    """
    industry_mean = df.groupby(industry_col)[factor_col].transform('mean')
    df[factor_col].fillna(industry_mean, inplace=True)
    return df

1.3 去极值——别让极端值牵着鼻子走

因子数据里经常会出现极端值。比如某只股票因为重组,一天涨了1000%,市盈率瞬间变成天文数字。这种极端值如果不处理,会严重扭曲后续的标准化结果。

常用的去极值方法有三种:

  • MAD法(中位数绝对偏差):我比较推荐的方法。它基于中位数,对极端值不敏感。一般取3倍MAD作为阈值。
  • 百分位法:直接砍掉上下1%或5%的数据。简单粗暴,但可能会丢失有用信息。
  • 标准差法:超过均值±3倍标准差的都截断。但均值本身容易被极端值影响,所以效果不如MAD法。

注意:去极值不是删除数据,而是把极端值「拉」回到阈值边界。比如超过3倍MAD的值,直接设为3倍MAD的值。这样既保留了样本量,又消除了极端影响。

# 示例:MAD法去极值
def winsorize_mad(series, n=3):
    median = series.median()
    mad = (series - median).abs().median()
    upper = median + n * mad
    lower = median - n * mad
    return series.clip(lower, upper)

1.4 标准化——让不同因子站在同一起跑线

标准化这一步,说白了就是把不同量纲的因子拉到同一个尺度上。比如市盈率可能是几十到几百,换手率可能是0.01到0.1,不标准化的话,大数值的因子会主导模型。

常用的标准化方法:

  • Z-score标准化:(值 - 均值) / 标准差。处理后均值为0,标准差为1。
  • Rank标准化:把因子值排序,然后映射到[0,1]区间。对极端值更鲁棒。

我个人习惯在去极值之后再做标准化。你想想看,如果先标准化再去极值,极端值会拉偏均值和标准差,标准化效果就差了。

# 示例:Z-score标准化
def zscore_standardize(series):
    return (series - series.mean()) / series.std()

1.5 中性化——剔除你不想要的「噪音」

中性化是很多新手容易忽略的一步。它的目的是剔除因子中与某些「干扰变量」相关的部分。

最常见的两种中性化:

  • 行业中性化:剔除行业因素对因子的影响。比如市盈率因子,银行股普遍低市盈率,科技股普遍高市盈率。如果不做行业中性化,模型选出来的可能全是银行股或全是科技股。
  • 市值中性化:剔除市值大小对因子的影响。大市值股票和小市值股票在很多因子上天然不同。

具体做法是用因子值对行业哑变量和市值做回归,取残差作为中性化后的因子值。

# 示例:行业和市值中性化
import statsmodels.api as sm

def neutralize(df, factor_col, industry_dummies, market_cap_col):
    X = pd.concat([industry_dummies, df[market_cap_col]], axis=1)
    X = sm.add_constant(X)
    y = df[factor_col]
    model = sm.OLS(y, X).fit()
    df['neutralized_factor'] = model.resid
    return df

关键点:中性化不是必须的,取决于你的模型设计。如果你在模型中已经包含了行业和市值作为控制变量,那因子层面可以不做中性化。但如果你用的是简单的加权打分模型,中性化就很有必要。

小结

这五步处理下来,因子数据基本就「干净」了。数据清洗去掉明显的错误,缺失值处理补上空白,去极值消除极端干扰,标准化统一尺度,中性化剔除噪音。

每一步都有它的道理,也都有坑。我建议你在实盘之前,先用历史数据跑一遍完整的处理流程,看看每一步对因子分布的影响。多花点时间在数据预处理上,后面建模和回测会省心很多。

我的经验:因子数据处理占整个多因子模型构建工作量的60%以上。别嫌麻烦,数据质量决定了模型的天花板。

专注资料整理