Skip to content

8.3 因子数据清洗与标准化 ​

课前导读 ​

本节目标

学完能解决什么困惑:理解为什么原始因子值不能直接比较,学会用去极值、缺失处理、标准化、方向统一和可得性记录,做一张因子清洗记录表。

难度:进阶入门

用时:约 45 分钟

前两节我们已经知道,因子是股票身上的可记录标签。

但是,标签写进表格之后,还不能马上拿来排序。

原因很简单:原始数据会脏、会缺、会极端、会口径不一致。

第 7 章你学过缺失值、异常值和清洗日志。

这一节把那些数据处理能力放到因子选股里。

你会看到,专业因子研究的一半功夫,不在公式多复杂,而在清洗记录是否诚实。


知识正文 ​

一、先用班级成绩做类比 ​

想象一个班要评"综合学习表现"。

语文满分 150,数学满分 150,体育满分 100。

有个同学语文缺考。

另一个同学数学成绩写成了 1500。

还有一个同学体育分很高,但体育和语文的分值范围本来就不一样。

如果你把这些原始分数直接相加,结果一定不公平。

正确做法通常要分几步。

先确认缺考怎么处理。

再检查 1500 这种明显离谱的数字。

然后把不同科目的分数放到可比较的尺度上。

最后记录处理过程,让别人能看懂你为什么这么做。

因子数据也是一样。

PE、ROE、近 20 日涨跌幅、波动率的单位完全不同。

有的越高越好,有的越低越好。

有的会缺失,有的会出现极端值。

如果不清洗,排序结果可能被一个错误数字带偏。

二、原始因子不能直接比较的四个原因 ​

第一,单位不同。

PE 可能是 8、20、60。

ROE 可能是 5%、15%、25%。

近 20 日涨跌幅可能是 -10%、3%、18%。

这些数放在一起,大小没有直接意义。

第二,方向不同。

ROE 通常越高越好。

波动率对稳健新手来说通常越低越稳。

PE 在很多场景下是越低越便宜,但亏损公司可能出现负 PE,不能简单当成更便宜。

第三,缺失不同。

新上市公司历史数据较短。

停牌期间价格数据可能断开。

财务字段可能尚未披露。

导出表格也可能因为格式问题出现空值。

第四,极端值会影响排序。

一个 PE 为 500 的股票,可能会让平均值失真。

一个错误录入的涨幅 800%,可能让动量排序完全跑偏。

数据提醒

因子研究里,"算得出来"不等于"算得对"。先解释数据,再相信排序。

三、清洗流程总览 ​

入门阶段可以把因子清洗记成六步。

mermaid
flowchart TD
    A["原始因子表"] --> B["检查数据可得性"]
    B --> C["处理缺失值"]
    C --> D["识别异常和极端值"]
    D --> E["去极值"]
    E --> F["标准化"]
    F --> G["统一方向"]
    G --> H["写入清洗记录表"]

这六步看起来像技术流程。

但它背后其实是投资纪律。

你每处理一次数据,都在回答一个问题:我有没有偷偷让结果变得更好看?

四、第一步:可得性检查 ​

可得性是第 6 章已经出现过的重要概念。

意思是:站在当时那个日期,投资者是否已经能合理看到这条数据。

价格数据通常在交易后就能看到。

财务报表数据只有在公告披露后才能看到。

如果你在 2024 年 1 月做历史验证,却使用了 2024 年 4 月才披露的年报数据,就相当于偷看未来。

在因子清洗里,可得性检查要写进记录表。

例如:

字段数据日期可使用日期说明
close2024-03-292024-03-29 收盘后当日收盘后可见
roe2023 年报年报公告日之后不能提前用于公告日前的判断
ret_20d2024-03-292024-03-29 收盘后由过去价格计算

新手最容易忽略的不是公式,而是时间。

因子表必须知道每个字段什么时候才真正可用。

五、第二步:缺失处理 ​

缺失值不是垃圾。

缺失值是一条信息。

它可能表示停牌。

可能表示公司还没披露。

可能表示数据源导出失败。

也可能表示这个指标对某些行业不适用。

所以处理缺失前,先问原因。

常见处理方式有三种。

第一,保留缺失并加标记。

例如新增一列 pe_missing,记录 PE 是否为空。

第二,用同类股票的中位数填补。

中位数可以理解成排序后站在中间的数,它比平均数不容易被极端值带偏。

第三,暂时剔除这条记录。

剔除要谨慎,因为剔除太多可能改变样本。

缺失场景入门处理建议记录重点
财务数据未披露不提前填未来值写清公告可得日期
停牌导致价格缺口保留并标记写清停牌区间
单个字段偶发空值查原因后再考虑填补写清填补方法
指标对行业不适用不强行比较写清行业限制

缺失处理原则

先解释缺失,再决定处理。不要为了让表格好看,把所有空值一键填成 0。

六、第三步:识别异常和极端值 ​

异常值和极端值不完全一样。

异常值更像录入错误或口径错误。

例如股票价格出现负数,成交量突然写成文字,PE 多了一个零。

极端值可能是真的,但非常少见。

例如某只股票因为利润很低,PE 变成几百倍。

又比如某只小盘股短期涨跌幅特别大。

异常值要优先查源头。

如果确认是错误,就应该修正或剔除,并写入日志。

极端值不一定删除。

更常见的做法是去极值。

七、第四步:去极值 ​

去极值可以理解成"给离谱分数加护栏"。

还是班级成绩的例子。

如果数学满分 150,有人被录成 1500。

你不能让这个 1500 直接参与平均。

在因子中,常见去极值方法是把过高或过低的数限制在一个合理边界内。

这个动作也常叫 winsorize。

你不需要先记英文。

先记住直觉:极端值不一定全删,但不能让它一个数字拉歪全班。

例如,一个学习样本里 PE 如下:

股票原始 PE说明
示例 A12正常范围
示例 B28正常范围
示例 C500极端偏高,需要检查
示例 D-8亏损导致,不能当作更便宜
示例 E空值缺失,需要说明原因

对 PE 这种因子,负数通常不能直接放进"越低越便宜"的排序。

因为负 PE 往往意味着公司亏损。

这不是便宜,而是口径需要单独处理。

八、第五步:标准化 ​

去极值之后,还要标准化。

标准化的目的,是把不同单位的因子放到同一把尺子上。

班级成绩里,语文 120 分和体育 90 分不能直接说谁更强。

因为满分不同、全班分布也不同。

因子也是一样。

PE、ROE、动量、波动率的数字范围不同。

标准化之后,我们更关心一个值相对同组股票处在什么位置。

常见方法之一是 z-score。

它的直觉是:

先看这个值离平均水平有多远。

再除以这组数正常晃动的幅度。

公式可以写成:

z=原始值−平均值标准差

这里的标准差可以先理解成"一组数据平时散开的程度"。

如果 z 大于 0,说明它高于平均水平。

如果 z 小于 0,说明它低于平均水平。

如果 z 接近 0,说明它接近平均水平。

另一种更适合新手理解的方法是排名标准化。

把同一天的股票按某个因子排队,再把名次变成分数。

排名标准化不太受单位影响,也更容易解释。

九、第六步:方向统一 ​

标准化之后,还要处理方向。

因为不同因子的"好方向"不同。

ROE 通常越高越好。

近 20 日涨跌幅如果用作动量,通常越高代表近期越强。

波动率如果用来控制稳健性,通常越低越稳。

PE 在价值因子里常常越低越便宜,但亏损和极端 PE 要单独处理。

方向统一的意思是:最后让分数都变成"越高越符合当前规则"。

例如:

ROE 的 z-score 可以直接使用。

波动率的 z-score 可以乘以 -1,让低波动得到更高分。

PE 的 z-score 也常需要反向,但必须先处理亏损和极端值。

风险提示

方向没统一时,不要把多个因子分数相加。一个因子越高越好,另一个越低越好,直接相加会把含义搅乱。

十、用 pandas 做一个最小清洗示例 ​

下面代码只使用手工构造的学习数据。

它的重点不是找到好股票,而是演示清洗动作如何写清楚。

python
import pandas as pd

df = pd.DataFrame(
    {
        "stock": ["示例 A", "示例 B", "示例 C", "示例 D", "示例 E"],
        "pe": [12, 28, 500, -8, None],
        "roe": [0.15, 0.10, 0.04, -0.03, 0.12],
    }
)

df["pe_missing"] = df["pe"].isna()
df.loc[df["pe"] <= 0, "pe"] = pd.NA

lower = df["pe"].quantile(0.05)
upper = df["pe"].quantile(0.95)
df["pe_clean"] = df["pe"].clip(lower=lower, upper=upper)

pe_mean = df["pe_clean"].mean()
pe_std = df["pe_clean"].std(ddof=0)
df["pe_z"] = (df["pe_clean"] - pe_mean) / pe_std
df["pe_score"] = -df["pe_z"]

print(df)

代码里有几个关键动作。

先记录 pe_missing,而不是悄悄忽略空值。

再把小于等于 0 的 PE 单独处理,因为它们通常代表亏损公司,不适合和正 PE 一起排"便宜"。

然后用 clip 做去极值。

最后用 z-score 标准化,并把 PE 分数反向。

这段代码没有处理所有现实细节。

但它展示了正确习惯:每一步都能说出原因。

十一、清洗日志比漂亮结果更重要 ​

专业老师会看你是否理解每一步。

投资者会关心这些处理会不会让自己低估风险。

量化开发会关心别人能不能复现你的结果。

因此,每次清洗都应该留下记录。

至少记录这些内容:

记录项为什么重要
因子名称知道处理的是哪一列
原始含义防止字段名看懂了、经济含义没看懂
数据日期知道数据对应哪一天或哪一期
可使用日期防止偷看未来
缺失处理防止空值被随意填补
去极值方法防止极端值扭曲结果
标准化方法知道分数如何变成同一尺度
方向处理确认最终分数越高代表什么
样本变化知道清洗前后股票数量有没有变化

如果以后你的研究结论变了,清洗日志能帮助你回到现场。

它会告诉你:是数据变了,方法变了,还是市场环境变了。

十二、本节可执行练习:先清洗,再排序 ​

请不要一拿到因子表就排序。

先完成下面四个动作。

第一,选一个因子,比如 PE 或 ROE。

第二,写下它的方向。

PE 通常越低越便宜,但亏损公司不能简单当成更便宜。

ROE 通常越高越好,但要注意是否可持续。

第三,写下缺失和极端值处理办法。

例如 PE 为空时保留缺失标记,PE 小于等于 0 单独处理,极端高 PE 做去极值。

第四,写下标准化方法。

入门阶段可以选择 z-score 或排名标准化。

做到这一步,你再排序,结果才更值得信任。


交互实验室 ​

因子清洗记录表 ​

请把下面这张表复制到你的学习笔记中。

这就是本节的交互产物。

你可以先用 PE 填一遍,再用 ROE 填一遍。

记录项我的填写
因子名称
因子类别价值、质量、成长、动量或波动
原始含义
原始字段名
数据日期
可使用日期
因子方向越高越好、越低越好或需要反向
缺失处理方法
异常值判断
去极值方法
标准化方法
方向统一后的含义分数越高代表什么
清洗前样本数
清洗后样本数
备注与风险

示例填写:PE 因子 ​

记录项示例
因子名称PE
因子类别价值
原始含义当前价格相对每股盈利贵不贵
原始字段名pe
数据日期某交易日收盘后
可使用日期同一交易日收盘后或数据源确认时间
因子方向一般越低越便宜,但亏损公司单独处理
缺失处理方法保留缺失标记,不填 0
异常值判断PE 小于等于 0、极端高 PE 需要检查
去极值方法使用分位数边界做 clip
标准化方法z-score 或排名标准化
方向统一后的含义分数越高代表估值越符合低估值规则
清洗前样本数记录原始股票数量
清洗后样本数记录可用于排序的股票数量
备注与风险低 PE 可能是盈利下滑或风险补偿

自检问题 ​

  1. 为什么不能把 PE 为空的股票直接填成 0?

  2. 为什么 PE 小于 0 不能简单当成"更便宜"?

  3. 如果 ROE 和波动率要合成一个观察分数,为什么必须先统一方向?

  4. 可得性记录能防止哪一种常见错误?

参考思路 ​

PE 填成 0 会让它看起来极端便宜,但这通常不符合真实含义。

PE 小于 0 往往代表公司亏损,和低估值不是一回事。

ROE 越高通常越好,波动率越低通常越稳,如果方向不统一,相加会混乱。

可得性记录主要防止偷看未来,尤其是财务报告尚未披露就被用于历史判断。


核心复盘 ​

知识清单:

  • [ ] 原始因子值不能直接比较,因为单位、方向、缺失和极端值都可能不同
  • [ ] 可得性检查能防止把未来才知道的数据提前用于判断
  • [ ] 缺失值要先解释原因,再决定保留、填补或剔除
  • [ ] 去极值是给极端值加护栏,不是为了美化结果
  • [ ] 标准化是把不同单位的因子放到同一把尺子上
  • [ ] 方向统一后,分数越高才代表越符合当前规则
  • [ ] 因子清洗记录表是复现和复盘的关键证据

金句总结

"因子排序前先清洗;没有清洗记录的结果,只是看起来很精确。"

本文档仅供学习交流用途,不构成任何投资建议。股市有风险,入市需谨慎。