因子正交化与多重共线性
直觉
几个因子往往互相相关(比如「动量」和「质量」、或「价值」和「低波」)。把高度相关的因子同时塞进回归,会出现多重共线性——系数估计变得不稳定、难以解释。正交化就是先把一个因子里「能被其他因子解释」的部分剔除,留下它独有的信息。
正交化(残差法)
把因子 对因子 回归,残差就是 中与 无关的部分 :
正交后 。
多重共线性与 VIF
衡量因子间共线程度用方差膨胀因子(VIF):把因子 对其余因子回归得 ,
「人话」解释:VIF 多大算严重?
- VIF 接近 1:该因子和其他因子几乎无关,信息「干净」;
- VIF 大于 5~10:严重共线——回归系数会剧烈摇摆,标准误膨胀,算出来的 不可信;
- 解法:剔除冗余因子,或对其正交化。本数据集动量/质量/低波相关性不高(VIF≈1),所以我们故意造一个「动量的噪声副本」来演示 VIF 爆炸。
可运行案例:正交化 + VIF 诊断
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
prices = pd.read_csv('/data/sp500_universe_daily.csv', parse_dates=['date']).set_index('date')
meta = pd.read_csv('/data/sp500_metadata.csv').set_index('ticker').loc[prices.columns]
quality = meta['quality']
momentum = np.log(prices / prices.shift(126)).iloc[-1]
# —— 正交化: 动量对质量回归取残差 ——
x, y = quality.values, momentum.values
slope, intercept = np.polyfit(x, y, 1) # y = slope*x + intercept
mom_orth = pd.Series(y - (slope * x + intercept), index=momentum.index)
corr_before = np.corrcoef(momentum, quality)[0, 1]
corr_after = np.corrcoef(mom_orth, quality)[0, 1]
print(f"动量 vs 质量 相关: 正交前={corr_before:+.3f} 正交后={corr_after:+.3f} (→≈0)")
# —— VIF 诊断: 故意加入动量的噪声副本制造共线 ——
rng = np.random.default_rng(1)
lowvol = 1.0 / np.log(prices / prices.shift(1)).tail(60).std(ddof=0)
dfc = pd.DataFrame({'动量': momentum, '质量': quality, '低波': lowvol,
'动量副本': momentum + rng.normal(0, 0.01, len(momentum))})
def vif(col, df):
others = df.drop(columns=col).values
X = np.column_stack([np.ones(len(df)), others])
b, *_ = np.linalg.lstsq(X, df[col].values, rcond=None)
resid = df[col].values - X @ b
r2 = 1 - resid.var(ddof=0) / df[col].var(ddof=0)
return 1 / (1 - r2) if r2 < 0.9999 else float('inf')
print("\nVIF (>5~10 为严重共线):")
for c in dfc.columns:
print(f" {c}: {vif(c, dfc):.2f}")
pd.plotting.scatter_matrix(dfc.drop(columns=['动量副本']), figsize=(5.5, 5.5), s=10)
plt.suptitle("因子两两散点(看共线方向)", y=0.94); plt.tight_layout(); plt.show()
小结
- 因子间相关 → 多重共线性 → 回归系数不稳、标准误膨胀;
- 正交化:因子 对 回归取残差,得到与 无关的 ;
- 用 VIF 诊断共线程度,VIF 过大时剔除冗余因子或先正交化。
至此模块 5 完成——从因子构造、IC 检验、分层回测到多因子模型。下一模块进入时间序列建模。