Skip to content

2.5 PCA(主成分分析)

从大量数据中找到最重要的"隐藏方向",用少量综合变量解释大部分信息。


一、先说协方差

PCA 的输入是协方差矩阵。如果你还不熟悉协方差,先从这里看起。

1.1 直觉——两个东西的"同向运动"程度

XXYY含义
大盘涨茅台涨正相关 → 协方差 > 0
大盘涨黄金跌负相关 → 协方差 < 0
大盘涨随机数无关系 → 协方差 ≈ 0

1.2 手算协方差

公式:

Cov(X,Y)=1n1i=1n(XiXˉ)(YiYˉ)\text{Cov}(X, Y) = \frac{1}{n-1} \sum_{i=1}^{n} (X_i - \bar{X})(Y_i - \bar{Y})

n1n-1(而不是 nn)是因为样本方差的无偏修正——自由度(degrees of freedom)调整。计算方差时除以 n1n-1 而非 nn,是因为用样本均值代替总体均值损失了一个自由度。

算一个:两只股票 4 天的日收益率(%)

茅台 XX五粮液 YY
1+2+1
2-10
3+3+2
40-1

Step 1:计算均值

Xˉ=2+(1)+3+04=1.0,Yˉ=1+0+2+(1)4=0.5\bar{X} = \frac{2 + (-1) + 3 + 0}{4} = 1.0,\quad \bar{Y} = \frac{1 + 0 + 2 + (-1)}{4} = 0.5

Step 2:计算每组的离差乘积

XiXˉX_i - \bar{X}YiYˉY_i - \bar{Y}乘积
121=12 - 1 = 110.5=0.51 - 0.5 = 0.51×0.5=0.51 \times 0.5 = 0.5
211=2-1 - 1 = -200.5=0.50 - 0.5 = -0.5(2)×(0.5)=1.0(-2) \times (-0.5) = 1.0
331=23 - 1 = 220.5=1.52 - 0.5 = 1.52×1.5=3.02 \times 1.5 = 3.0
401=10 - 1 = -110.5=1.5-1 - 0.5 = -1.5(1)×(1.5)=1.5(-1) \times (-1.5) = 1.5

Step 3:求和、除以 n1n-1

Cov(X,Y)=0.5+1.0+3.0+1.541=6.03=2.0\text{Cov}(X, Y) = \frac{0.5 + 1.0 + 3.0 + 1.5}{4 - 1} = \frac{6.0}{3} = 2.0

含义:茅台的日收益率变动 +1% 时,五粮液平均同向变动约 +0.67%(2.0/3.02.0 / 3.0,除以茅台方差)。两只白酒股一起涨跌。

1.3 协方差矩阵

pp 个变量时,把所有两两之间的协方差排成矩阵:

Σ=[Var(X1)Cov(X1,X2)Cov(X1,Xp)Cov(X2,X1)Var(X2)Cov(X2,Xp)Cov(Xp,X1)Cov(Xp,X2)Var(Xp)]\Sigma = \begin{bmatrix} \text{Var}(X_1) & \text{Cov}(X_1, X_2) & \cdots & \text{Cov}(X_1, X_p) \\ \text{Cov}(X_2, X_1) & \text{Var}(X_2) & \cdots & \text{Cov}(X_2, X_p) \\ \vdots & \vdots & \ddots & \vdots \\ \text{Cov}(X_p, X_1) & \text{Cov}(X_p, X_2) & \cdots & \text{Var}(X_p) \end{bmatrix}

关键性质

  • 对称Cov(Xi,Xj)=Cov(Xj,Xi)\text{Cov}(X_i, X_j) = \text{Cov}(X_j, X_i)Σ=ΣT\Sigma = \Sigma^T
  • 半正定:对任何向量 w\boldsymbol{w}wTΣw0\boldsymbol{w}^T \Sigma \boldsymbol{w} \ge 0(组合方差不能为负)
  • 这两个性质保证了 Σ\Sigma 一定有实数特征值、且全部 0\ge 0——所以可以对它做特征值分解

Quant Link:协方差矩阵是现代投资组合理论的核心。Markowitz 用均值向量 μ\boldsymbol{\mu} 估计预期收益、用 Σ\Sigma 估计风险,"最优组合"就是在收益和 wTΣw\boldsymbol{w}^T \Sigma \boldsymbol{w}(组合方差)之间做权衡。


二、核心思想

2.1 直觉

你有 100 只股票的收益率数据,每只都有独立的风险。但它们不是完全独立的——当大盘涨时,大部分股票都涨。也就是说,存在一些"共同因子"驱动着所有股票。

PCA 就是自动找出这些因子的方法。

2.2 PCA = 特征值分解

PCA 在数学上等价于对协方差矩阵做特征值分解:

Σ=QΛQT\Sigma = Q \Lambda Q^T

  • λ1\lambda_1(最大特征值)对应第 1 主成分——数据波动最大的方向
  • λ2\lambda_2(次大)对应第 2 主成分——与 PC1 正交,次大波动方向
  • q1\boldsymbol{q}_1(对应的特征向量)告诉你 PC1 在各资产上的权重

2.3 算一个

沿用上面的两只股票,假设协方差矩阵是:

Σ=[Var(X)Cov(X,Y)Cov(Y,X)Var(Y)]=[3.02.02.03.0]\Sigma = \begin{bmatrix} \text{Var}(X) & \text{Cov}(X, Y) \\ \text{Cov}(Y, X) & \text{Var}(Y) \end{bmatrix} = \begin{bmatrix} 3.0 & 2.0 \\ 2.0 & 3.0 \end{bmatrix}

对角线上 Var(X)=3.0\text{Var}(X) = 3.0 是茅台自己收益率的方法(从手算数据得),Var(Y)=3.0\text{Var}(Y) = 3.0 是五粮液的方法。对称,且是半正定。

对这个矩阵做特征值分解(方法见 2.4 节):

特征值 λ1=5.0\lambda_1 = 5.0λ2=1.0\lambda_2 = 1.0

q1=[0.7070.707],q2=[0.7070.707]\boldsymbol{q}_1 = \begin{bmatrix} 0.707 \\ 0.707 \end{bmatrix},\quad \boldsymbol{q}_2 = \begin{bmatrix} 0.707 \\ -0.707 \end{bmatrix}

含义:

  • PC1(解释 5.0/6.0=83.3%5.0 / 6.0 = 83.3\% 总方差):两只股票同方向、各占 0.7070.707 权重——这是"白酒板块因子",当板块整体涨时第一主成分得分高
  • PC2(解释 1.0/6.0=16.7%1.0 / 6.0 = 16.7\% 方差):一只做多、一只做空——这是"价差因子",当茅台跑赢五粮液时得分高

为什么 >1>1 PCA 在标准化后的数据(相关系数矩阵)上做,特征值之和等于变量个数,所以各主成分的特征值都在 1 附近。这里用协方差矩阵(未标准化),特征值可以大于变量数。


三、PCA 步骤

  1. 标准化:每个特征减去均值、除以标准差(标准化后的协方差矩阵 = 相关系数矩阵,各变量量纲统一)
  2. 算协方差(或相关)矩阵
  3. 特征值分解,特征值从大到小排列
  4. 选择前 kk:通常取能解释 80-90% 方差的数量
  5. 投影:原始数据 ×\timeskk 个特征向量 = 降维后的数据

四、量化应用

4.1 利率曲线

国债收益率曲线有几十个期限(1 个月到 30 年),但 PCA 发现前 3 个主成分就能解释 95% 以上的变动:

主成分名称解释比例含义
PC1水平~70%所有期限利率同时升降
PC2倾斜~20%短端和长端反向变动
PC3曲率~5%中间相对于两端变动

应用:不需要跟踪每个期限的利率风险,只需要管理 3 个因子的暴露。

4.2 协方差矩阵去噪

样本协方差矩阵包含大量噪声。用 PCA 只保留前 kk 个主成分来做组合优化,效果往往更好:

python
def pca_denoise(Sigma, k):
    eigvals, eigvecs = np.linalg.eigh(Sigma)
    eigvals = eigvals.copy()
    eigvals[:-k] = 0  # 小特征值归零
    return eigvecs @ np.diag(eigvals) @ eigvecs.T

五、Python 实践

python
import numpy as np
from sklearn.decomposition import PCA

# 模拟 50 只股票、500 天的数据
np.random.seed(42)
n_assets, n_days = 50, 500
returns = np.random.randn(n_days, n_assets)

# 用 sklearn 做 PCA
pca = PCA(n_components=10)
pca.fit(returns)

# 看前几个主成分解释了多少方差
var = pca.explained_variance_ratio_
print(f"PC1: {var[0]:.2%}")
print(f"PC2: {var[1]:.2%}")
print(f"PC3: {var[2]:.2%}")
print(f"前5个累计: {var[:5].sum():.2%}")

# 手动实现 PCA
cov = np.cov(returns.T)
eigvals, eigvecs = np.linalg.eigh(cov)
idx = np.argsort(eigvals)[::-1]
eigvals, eigvecs = eigvals[idx], eigvecs[:, idx]
print(f"\n第一主成分(前5个权重): {eigvecs[:5, 0].round(4)}")

\n> 下一步:继续学习 2.6 矩阵微积分

Built with VitePress