跳到主要内容

因子正交化与多重共线性

直觉

几个因子往往互相相关(比如「动量」和「质量」、或「价值」和「低波」)。把高度相关的因子同时塞进回归,会出现多重共线性——系数估计变得不稳定、难以解释。正交化就是先把一个因子里「能被其他因子解释」的部分剔除,留下它独有的信息。

正交化(残差法)

把因子 BB 对因子 AA 回归,残差就是 BB 中与 AA 无关的部分 BB^{\perp}

B=γ0+γ1A+ε,B=εB = \gamma_0 + \gamma_1 A + \varepsilon,\qquad B^{\perp} = \varepsilon

正交后 corr(B,A)=0\text{corr}(B^{\perp}, A) = 0

多重共线性与 VIF

衡量因子间共线程度用方差膨胀因子(VIF):把因子 jj 对其余因子回归得 Rj2R^2_j

VIFj=11Rj2\text{VIF}_j = \frac{1}{1 - R^2_j}
「人话」解释:VIF 多大算严重?
  • VIF 接近 1:该因子和其他因子几乎无关,信息「干净」;
  • VIF 大于 5~10:严重共线——回归系数会剧烈摇摆,标准误膨胀,算出来的 β\beta 不可信
  • 解法:剔除冗余因子,或对其正交化。本数据集动量/质量/低波相关性不高(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()

小结

  • 因子间相关 → 多重共线性 → 回归系数不稳、标准误膨胀;
  • 正交化:因子 BBAA 回归取残差,得到与 AA 无关的 BB^{\perp}
  • VIF 诊断共线程度,VIF 过大时剔除冗余因子或先正交化。

至此模块 5 完成——从因子构造、IC 检验、分层回测到多因子模型。下一模块进入时间序列建模