4. 因子相关性分析:皮尔逊相关系数、斯皮尔曼秩相关系数、因子共线性问题

做因子组合优化,最怕什么?

怕你辛辛苦苦挖出来的两个因子,其实是一回事。

我早年刚入行时,就犯过这个错。把动量因子和反转因子同时放进模型,结果回测曲线漂亮得不行,一到实盘就崩。后来一查,两个因子在周频上的相关性高达0.85。说白了,它们描述的是同一类市场行为,只是换了件马甲。

所以,因子相关性分析,是组合优化前必须做的一道安检。今天我们就来聊聊怎么给因子做「体检」。

4.1 皮尔逊相关系数:最常用的线性关系度量

皮尔逊相关系数,大家应该都不陌生。它衡量的是两个变量之间的线性相关程度,取值范围在[-1, 1]之间。

公式长这样:

ρ(X, Y) = Cov(X, Y) / (σ_X * σ_Y)

其中Cov是协方差,σ是标准差。说白了,就是看两个因子是否在同一条直线上一起波动。

实际应用中的坑:

  • 对异常值极其敏感:一个极端值就能把相关系数从0.1拉到0.8。我在处理高频因子时遇到过,某天某只股票因为乌龙指出现极端收益,结果当天所有因子跟它的相关性都异常高。
  • 只捕捉线性关系:如果两个因子是U型关系(比如一个先升后降,另一个先降后升),皮尔逊系数可能接近0,但它们其实高度相关。
  • 要求数据正态分布:虽然实际中很少严格满足,但偏离太远时结果会失真。

经验之谈:我个人习惯在计算皮尔逊系数前,先做一次极值处理(比如MAD截断),把异常值压一压。否则一个离群点就能毁掉整个相关性矩阵。

Python实现很简单:

import numpy as np
import pandas as pd

# 假设 factor1 和 factor2 是两个因子序列
corr_pearson = np.corrcoef(factor1, factor2)[0, 1]
print(f"皮尔逊相关系数: {corr_pearson:.4f}")

# 或者用pandas一步到位
df = pd.DataFrame({'f1': factor1, 'f2': factor2})
print(df.corr(method='pearson'))

4.2 斯皮尔曼秩相关系数:非线性关系的救星

皮尔逊搞不定的情况,就该斯皮尔曼上场了。

斯皮尔曼秩相关系数,说白了就是把原始数据换成排名,再算皮尔逊系数。它不关心具体数值,只关心谁大谁小。

ρ_s = 1 - (6 * Σd_i²) / (n * (n² - 1))

其中d_i是每个样本在两个变量上的排名差,n是样本数。

什么时候用斯皮尔曼?

  • 因子分布明显偏态(比如市值因子,天然就是右偏的)
  • 存在非线性单调关系(比如一个因子涨,另一个因子也涨,但涨速不同)
  • 数据中有较多异常值(排名天然对异常值不敏感)

避坑指南:我曾经在分析基本面因子时,发现市盈率因子和市净率因子的皮尔逊系数只有0.3,但斯皮尔曼系数高达0.72。为什么?因为有几个极端高市盈率的股票把线性关系拉偏了。换成排名后,它们其实高度一致。所以,我建议两个系数都算一下,对比着看。

代码实现:

# 斯皮尔曼相关系数
corr_spearman, p_value = scipy.stats.spearmanr(factor1, factor2)
print(f"斯皮尔曼秩相关系数: {corr_spearman:.4f}")

# pandas也支持
print(df.corr(method='spearman'))

4.3 因子共线性问题:为什么它是个大麻烦?

因子共线性,就是多个因子之间高度相关。这会导致什么问题?

我举个例子。假设你建了一个多因子模型,里面有三个因子:动量、反转、波动率。结果动量和反转的相关系数是0.85。那么模型在分配权重时,就会陷入「左右互搏」——它不知道该给动量多一点,还是给反转多一点。最终结果就是:

  • 权重不稳定:稍微换一段样本数据,权重就剧烈变化
  • 模型解释力下降:你分不清哪个因子真正在起作用
  • 过拟合风险飙升:模型记住了噪声,而不是信号

注意:共线性不等于相关性高。三个因子两两相关性只有0.4,但它们的线性组合可能高度共线。这就是所谓的「多重共线性」——需要用方差膨胀因子(VIF)来检测。

VIF的计算方法:

VIF_i = 1 / (1 - R_i²)

其中R_i²是把第i个因子作为因变量,其他因子作为自变量做回归得到的R方。VIF大于10(严格一点是5)就说明存在严重共线性。

Python实现:

from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant

# 假设 factors 是一个DataFrame,包含所有因子
X = add_constant(factors)
vif_data = pd.DataFrame()
vif_data["因子"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
print(vif_data)

4.4 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

因子相关性分析知识体系 因子相关性分析 皮尔逊相关系数 斯皮尔曼秩相关系数 因子共线性问题 线性关系 · 正态假设 · 异常值敏感 排名相关 · 非线性 · 异常值鲁棒 VIF检测 · 权重不稳 · 过拟合 核心原则:先检测,后组合,避免「假多元」 皮尔逊 + 斯皮尔曼 + VIF 三管齐下

4.5 实战中的处理策略

检测出共线性后怎么办?我一般按这个优先级处理:

  1. 剔除冗余因子:如果两个因子相关性超过0.8,且逻辑相似(比如都是动量类),直接去掉一个。保留解释力更强、更稳定的那个。
  2. 因子合成:如果两个因子逻辑不同但高度相关(比如估值因子和盈利因子),可以考虑做PCA或等权合成,变成一个复合因子。
  3. 正则化回归:如果实在舍不得丢,用Lasso或Ridge回归,让模型自动压缩共线因子的权重。

一个小技巧:我习惯在因子入库前,先跑一遍相关性矩阵热力图。颜色越深的地方,就是需要重点关注的对象。这一步花不了几分钟,但能省掉后面很多调试的麻烦。

最后说一句:因子相关性分析不是一次性工作。随着市场环境变化,因子之间的关系也会变。我建议每个季度重新算一次,保持对因子「健康状况」的持续监控。


公众号:蓝海资料掘金营,微信deep3321