3、特征工程基础:单因子标准化、中性化处理、正交化方法(PCA、Gram-Schmidt)
各位好,今天我们聊点实在的。
因子挖掘出来之后,能不能直接用?不能。我早年刚入行时,把一堆原始因子直接扔进模型,结果回测曲线漂亮得不像话,一上实盘就崩。后来才发现,因子之间量纲不同、相关性太高、还有各种行业和市值暴露。说白了,你不做特征工程,模型学到的全是噪音。
这一章,我就把三个最基础也最关键的预处理手段讲清楚:标准化、中性化、正交化。这三板斧用好了,因子质量能上一个台阶。
3.1 单因子标准化:让因子站在同一起跑线
标准化,说白了就是把不同量纲的因子拉到同一个尺度上。比如一个因子取值在0.01~0.05,另一个在100~1000,模型会天然偏向数值大的那个——这不合理。
我个人习惯用两种方法:
- Z-score 标准化:减去均值,除以标准差。适用于因子近似正态分布的场景。
- Rank 标准化:把因子值映射到0~1之间的分位数。对异常值更鲁棒。
举个例子,假设我们有一个因子 factor_a:
import pandas as pd
import numpy as np
# 原始因子
factor = pd.Series([1.2, 3.4, 5.6, 100.0, 2.3])
# Z-score 标准化
z_score = (factor - factor.mean()) / factor.std()
# Rank 标准化
rank = factor.rank(pct=True)
print("Z-score:", z_score.tolist())
print("Rank:", rank.tolist())
3.2 中性化处理:剔除你不想要的暴露
因子常常跟某些风险因子(比如市值、行业)高度相关。举个例子,你发现一个因子在小盘股上表现特别好,但这不是因子本身的能力,而是小盘股风格在起作用。中性化就是把这些“脏东西”剥离掉。
怎么做?回归取残差。
把因子作为因变量,把你要中性化的变量(比如市值、行业哑变量)作为自变量,做线性回归。残差就是中性化后的因子。
import statsmodels.api as sm
# 假设我们有因子值和市值
factor = np.array([1.2, 3.4, 5.6, 2.3, 4.5])
market_cap = np.array([10, 100, 50, 200, 30])
# 加截距项
X = sm.add_constant(market_cap)
model = sm.OLS(factor, X).fit()
residual = model.resid
print("中性化后的因子:", residual)
3.3 正交化方法:让因子之间不再“打架”
多个因子之间如果高度相关,模型会不稳定,甚至出现多重共线性。正交化就是让因子之间相互独立。
这里介绍两种主流方法:PCA 和 Gram-Schmidt。
3.3.1 PCA:主成分分析
PCA 把原始因子投影到一组正交的主成分上。这些主成分彼此不相关,而且按方差大小排序。我一般用 PCA 做降维——比如你有50个因子,可能前10个主成分就解释了90%的方差。
from sklearn.decomposition import PCA
# 假设我们有3个因子,100个样本
np.random.seed(42)
factors = np.random.randn(100, 3)
pca = PCA(n_components=3)
pca_factors = pca.fit_transform(factors)
print("主成分方差解释比例:", pca.explained_variance_ratio_)
print("正交化后的因子形状:", pca_factors.shape)
3.3.2 Gram-Schmidt:顺序正交化
Gram-Schmidt 过程是逐次正交化。你先选一个基准因子,然后把后续因子依次投影到前面因子的正交补空间上。这样做的好处是:你可以控制正交化的顺序。
比如,你有一个你非常信任的因子 F1,你想让其他因子都跟它正交,但保留 F1 本身不变。Gram-Schmidt 就能做到。
def gram_schmidt(factors):
"""
factors: (n_samples, n_factors) 的 numpy 数组
返回正交化后的因子矩阵
"""
n_samples, n_factors = factors.shape
ortho = np.zeros_like(factors)
for i in range(n_factors):
v = factors[:, i].copy()
for j in range(i):
# 减去在前一个因子上的投影
proj = np.dot(v, ortho[:, j]) / np.dot(ortho[:, j], ortho[:, j])
v -= proj * ortho[:, j]
ortho[:, i] = v
return ortho
# 示例
factors = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]], dtype=float)
ortho_factors = gram_schmidt(factors)
print("正交化后的因子:\n", ortho_factors)
3.4 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到,从原始因子到最终可用因子,中间经历了三个关键步骤:
3.5 实际应用中的顺序问题
这三个步骤的顺序有讲究吗?有。
我个人建议的顺序是:先标准化,再中性化,最后正交化。
- 标准化放在最前面,因为中性化和正交化都依赖于数值的尺度一致性。
- 中性化放在正交化之前,因为中性化本质上是回归,回归对因子间的相关性有一定容忍度。
- 正交化放在最后,因为它是“最终清洗”,确保因子之间彻底独立。
好了,这一章的内容就到这里。标准化、中性化、正交化,这三步是因子预处理的核心。你想想看,如果连这些基础都没做好,后面再复杂的模型也是白搭。下一章我们会聊到因子合成与筛选,到时候这些预处理后的因子就能派上大用场了。
公众号:蓝海资料掘金营,微信deep3321