4. 因子相关性分析:皮尔逊相关系数、斯皮尔曼相关系数、因子共线性问题、VIF检验
各位好,欢迎来到第四章。这一章我们聊聊因子之间的「关系」。
做多因子模型,最怕什么?最怕你辛辛苦苦挖了50个因子,结果它们之间高度相关。说白了,就是它们都在讲同一个故事。你想想看,如果两个因子几乎一模一样,那它们同时放进模型,不仅浪费,还会搞乱你的参数估计。
我个人习惯是,在因子合成之前,先做一轮「关系排查」。这就像相亲之前先查查对方有没有血缘关系——嗯,有点夸张,但道理差不多。
4.1 皮尔逊相关系数:最常用的线性关系度量
皮尔逊相关系数,大家应该都不陌生。它衡量的是两个变量之间的线性相关程度。取值范围在[-1, 1]之间,0表示无线性关系,±1表示完全线性相关。
我在项目中遇到过一件事:有次我构建了20个技术因子,跑完皮尔逊矩阵一看,好家伙,有3对因子的相关系数超过了0.9。仔细一查,原来是同一个指标的不同变形——说白了就是换了个参数,本质没变。
import pandas as pd
import numpy as np
from scipy.stats import pearsonr
# 假设 factor_df 是因子数据,index为时间,columns为因子名
def calc_pearson_matrix(factor_df):
"""计算因子间的皮尔逊相关系数矩阵"""
return factor_df.corr(method='pearson')
# 找出高相关因子对
def find_high_corr_pairs(corr_matrix, threshold=0.7):
high_pairs = []
cols = corr_matrix.columns
for i in range(len(cols)):
for j in range(i+1, len(cols)):
if abs(corr_matrix.iloc[i, j]) >= threshold:
high_pairs.append((cols[i], cols[j], corr_matrix.iloc[i, j]))
return high_pairs
# 示例
# corr_mat = calc_pearson_matrix(factor_df)
# pairs = find_high_corr_pairs(corr_mat, 0.7)
# for p in pairs:
# print(f"{p[0]} vs {p[1]}: {p[2]:.3f}")
4.2 斯皮尔曼相关系数:非线性关系的侦察兵
皮尔逊有个硬伤——它只认线性关系。如果两个因子是单调非线性关系(比如指数关系),皮尔逊可能给出一个很低的数值,让你误以为它们不相关。
这时候就该斯皮尔曼出场了。它基于秩次计算,说白了就是把原始数据换成排名,再算皮尔逊。这样一来,只要两个因子的排名变化趋势一致,斯皮尔曼就能捕捉到。
我记得有一次做基本面因子分析,发现市盈率和市净率的皮尔逊只有0.3,但斯皮尔曼高达0.78。为什么?因为这两个指标存在明显的非线性关系——低估值区间相关性强,高估值区间相关性弱。如果只看皮尔逊,你可能会误判。
from scipy.stats import spearmanr
def calc_spearman_matrix(factor_df):
"""计算因子间的斯皮尔曼相关系数矩阵"""
return factor_df.corr(method='spearman')
def compare_corr(factor1, factor2):
"""对比皮尔逊和斯皮尔曼"""
p_pearson, _ = pearsonr(factor1, factor2)
p_spearman, _ = spearmanr(factor1, factor2)
print(f"皮尔逊: {p_pearson:.4f}")
print(f"斯皮尔曼: {p_spearman:.4f}")
if abs(p_spearman) > abs(p_pearson) + 0.2:
print("⚠️ 存在潜在非线性关系,建议深入检查")
4.3 因子共线性问题:为什么它是个麻烦?
因子共线性,简单说就是两个或多个因子之间存在强相关关系。你可能会问:「相关就相关呗,有什么大不了的?」
嗯,问题大了。共线性会带来三个主要麻烦:
- 参数估计不稳定: 回归系数的方差会变得很大,稍微换点数据,系数就天翻地覆
- 模型解释力下降: 你分不清哪个因子真正起作用,因为它们的效应混在一起
- 过拟合风险增加: 模型会「记住」训练数据中的噪声模式,泛化能力变差
我见过最夸张的一个案例:有人用50个因子做线性回归,结果VIF最高的因子达到了120。模型在训练集上R²高达0.95,但一到测试集就崩到0.2。这就是典型的共线性导致的过拟合。
4.4 VIF检验:量化共线性的标尺
VIF(方差膨胀因子)是诊断共线性的标准工具。它的逻辑很简单:对每个因子,用其他所有因子做回归,看这个因子的方差被「膨胀」了多少。
公式不复杂:VIF = 1 / (1 - R²)。其中R²是用其他因子预测该因子的拟合优度。
如果VIF=1,说明该因子与其他因子完全不相关。VIF越大,共线性越严重。我个人习惯的阈值是:
| VIF值 | 共线性程度 | 建议操作 |
|---|---|---|
| 1 - 5 | 轻度或无共线性 | 可保留 |
| 5 - 10 | 中度共线性 | 需关注,考虑处理 |
| 10以上 | 严重共线性 | 必须处理 |
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant
def calc_vif(factor_df):
"""
计算所有因子的VIF值
注意:factor_df中不应包含目标变量,只包含因子列
"""
# 添加截距项
X = add_constant(factor_df)
vif_data = pd.DataFrame()
vif_data['factor'] = factor_df.columns
vif_data['VIF'] = [variance_inflation_factor(X.values, i+1)
for i in range(len(factor_df.columns))]
return vif_data.sort_values('VIF', ascending=False)
# 示例
# vif_result = calc_vif(factor_df)
# print(vif_result)
#
# # 如果发现VIF > 10的因子,建议剔除
# high_vif_factors = vif_result[vif_result['VIF'] > 10]['factor'].tolist()
# print(f"需要处理的因子: {high_vif_factors}")
4.5 实战中的处理策略
当你发现因子间存在共线性时,有几种处理方式:
- 剔除冗余因子: 保留解释力更强、逻辑更清晰的因子,剔除相关性高的冗余因子
- 因子合成: 将高度相关的因子合成为一个综合因子(如PCA、等权合成)
- 正则化: 使用Lasso或Ridge回归,自动处理共线性问题
- 分层处理: 先对因子做聚类,再从每个簇中选代表因子
我个人最常用的是「先剔除、再合成」的策略。具体来说:
- 第一步:计算皮尔逊和斯皮尔曼矩阵,标记高相关对
- 第二步:计算VIF,标记高VIF因子
- 第三步:结合业务逻辑,决定保留哪些因子
- 第四步:对保留的因子做PCA或等权合成,进一步降维
好了,这一章的内容就到这里。因子相关性分析是构建稳健多因子模型的基础,千万别跳过。下一章我们会聊因子标准化和中性化处理,到时候见。