4、PCA实战应用:使用sklearn进行PCA降维、主成分载荷矩阵解读、PCA在因子正交化中的应用
4.1 为什么我们需要PCA?
做因子模型的人,最怕什么?
怕多重共线性。两个因子高度相关,回归系数直接崩掉,方差膨胀因子(VIF)飙到几十。你辛辛苦苦构造的因子,最后模型告诉你「这两个变量不能同时用」。
我个人习惯,在因子正交化之前,先跑一遍PCA。为什么?因为PCA能帮你把一堆相关的因子,压缩成几个互不相关的「主成分」。说白了,就是给因子们做一次「去相关」手术。
核心思想:PCA通过正交变换,将原始因子映射到一组新的正交坐标系中。新坐标轴(主成分)彼此独立,且按方差大小排序。
4.2 使用sklearn进行PCA降维
先上代码。我习惯用sklearn的PCA类,因为它封装得干净,参数少,不容易踩坑。
import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# 假设我们有5个因子,1000个样本
np.random.seed(42)
n_samples = 1000
n_features = 5
# 构造一些相关的因子数据
X = np.random.randn(n_samples, n_features)
# 人为制造相关性:让前两个因子高度相关
X[:, 1] = X[:, 0] * 0.8 + np.random.randn(n_samples) * 0.2
# 标准化(PCA对尺度敏感,这一步不能省)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 执行PCA
pca = PCA(n_components=3) # 降到3维
X_pca = pca.fit_transform(X_scaled)
print("降维后的数据形状:", X_pca.shape)
print("各主成分解释方差比例:", pca.explained_variance_ratio_)
print("累计解释方差:", np.cumsum(pca.explained_variance_ratio_))
嗯,这里要注意:标准化是必须的。我在项目中遇到过有人直接拿原始数据跑PCA,结果第一个主成分被量纲最大的因子完全主导,后面的因子全被淹没了。你想想看,一个量级是1000的因子,和一个量级是0.01的因子,PCA会优先照顾谁?
4.3 主成分载荷矩阵解读
载荷矩阵(loadings)是PCA里最有价值的东西之一。它告诉你每个原始因子在新主成分上的「贡献权重」。
# 获取载荷矩阵
loadings = pca.components_.T # 形状: (n_features, n_components)
loadings_df = pd.DataFrame(
loadings,
index=[f'因子_{i+1}' for i in range(n_features)],
columns=[f'PC{i+1}' for i in range(pca.n_components_)]
)
print("主成分载荷矩阵:")
print(loadings_df.round(3))
举个例子,假设输出是这样的:
| 因子 | PC1 | PC2 | PC3 |
|---|---|---|---|
| 因子_1 | 0.85 | 0.12 | -0.05 |
| 因子_2 | 0.82 | -0.15 | 0.08 |
| 因子_3 | 0.10 | 0.91 | 0.02 |
| 因子_4 | -0.03 | 0.05 | 0.95 |
| 因子_5 | 0.45 | 0.30 | 0.55 |
怎么读?
- PC1:因子_1和因子_2的载荷很高(0.85, 0.82),说明PC1主要捕获了这两个因子的共同变异。说白了,PC1就是它们的「公共因子」。
- PC2:因子_3一枝独秀(0.91),PC2代表因子_3的独特信息。
- PC3:因子_4占主导(0.95),因子_5也有一定贡献(0.55)。
我曾经犯过一个错误:只看载荷的绝对值大小,忽略了符号。其实符号代表方向,正负只是相关性方向不同,不影响正交性。但如果你要做因子解释,符号就很重要了——比如一个因子是「盈利」,另一个是「亏损」,载荷符号相反,说明它们在PC1上是对冲关系。
小技巧:如果某个因子在所有主成分上的载荷都很小(比如都小于0.3),说明这个因子是「噪声因子」,可以考虑剔除。我一般会设置一个阈值,低于阈值的因子直接扔掉。
4.4 PCA在因子正交化中的应用
因子正交化,说白了就是让因子之间互相独立。PCA天然适合干这个活——因为主成分之间是正交的(协方差为0)。
具体怎么做?
- 收集原始因子:比如动量、价值、质量、波动率等。
- 标准化:每个因子减去均值除以标准差。
- PCA分解:得到主成分得分矩阵。
- 用主成分替代原始因子:这些主成分彼此正交,可以直接扔进回归模型。
# 完整流程:因子正交化
def factor_orthogonalization(factor_matrix, n_components=None):
"""
使用PCA对因子矩阵进行正交化
factor_matrix: (n_samples, n_factors)
"""
# 1. 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(factor_matrix)
# 2. PCA
if n_components is None:
n_components = factor_matrix.shape[1] # 保留全部主成分
pca = PCA(n_components=n_components)
orthogonal_factors = pca.fit_transform(X_scaled)
# 3. 将主成分转换回原始尺度(可选)
# 这一步是为了保持因子的量级可解释
orthogonal_factors_scaled = orthogonal_factors * np.sqrt(pca.explained_variance_)
return orthogonal_factors_scaled, pca
# 使用示例
factors = np.random.randn(1000, 5)
ortho_factors, pca_model = factor_orthogonalization(factors)
# 验证正交性
corr_matrix = np.corrcoef(ortho_factors.T)
print("正交化后的因子相关性矩阵(应该接近单位矩阵):")
print(np.round(corr_matrix, 3))
你猜怎么着?正交化后的因子相关性矩阵,非对角线元素基本为0。这就是PCA的魔力。
避坑指南:我曾经在实盘回测中直接用PCA正交化后的因子做选股,结果发现某些主成分的解释性很差——比如PC3可能对应「莫名其妙的残差波动」。后来我养成了一个习惯:正交化之后,一定要检查每个主成分与原始因子的相关性,确保能解释得通。如果某个主成分跟所有原始因子相关性都低于0.3,我建议直接扔掉。
4.5 知识体系图:PCA因子正交化流程
下面这张图,是我自己总结的PCA因子正交化完整流程。你跟着走一遍,基本不会出错。
4.6 实战中的几个坑
最后,分享几个我踩过的坑:
- 坑一:忘记标准化。 我刚开始用PCA时,直接拿原始数据跑,结果第一个主成分被「市值」因子完全主导,因为市值的量级是其他因子的100倍。标准化之后,一切正常。
- 坑二:保留太多主成分。 有人觉得保留所有主成分能保留全部信息,但这样正交化就失去了意义——你只是做了个旋转,没有降维。我建议保留累计方差解释率在80%-90%的主成分数量。
- 坑三:忽略载荷矩阵的符号。 载荷的正负代表方向,如果你在做多因子选股时直接拿主成分得分当因子,一定要搞清楚每个主成分的「经济含义」。否则你可能会在PC3上做多,结果发现它跟「亏损因子」高度正相关。
我的个人习惯:每次做完PCA正交化,我都会把载荷矩阵打印出来,盯着看5分钟。看看每个主成分对应哪些原始因子,能不能讲出一个合理的故事。如果讲不出来,我就调整主成分数量或者重新审视原始因子的构造。
好了,PCA在因子正交化中的应用就讲到这里。记住:PCA不是万能的,但它确实是处理因子共线性的利器。用好了,你的模型会干净很多。
公众号:蓝海资料掘金营,微信deep3321