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 越高通常越好,波动率越低通常越稳,如果方向不统一,相加会混乱。

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


核心复盘

知识清单

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

金句总结

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

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