4. 主成分分析(PCA)在批次效应检测中的应用

主成分分析——PCA,说实话这是我做组学数据分析时最常用的工具之一。你想想看,高维数据动辄几万个基因,肉眼根本看不出分布。PCA 能把维度压缩到两三个主成分,然后直接画个散点图,批次效应往往一眼就暴露了。

我个人习惯呢,在正式做差异分析之前,必先跑一遍 PCA。为什么?因为批次效应如果很严重,后续的统计推断全是扯淡。这一讲我就带你看看 PCA 到底是怎么帮我们“抓出”批次效应的。

4.1 PCA 的核心思想

说白了,PCA 就是寻找数据中方差最大的方向。第一个主成分(PC1)是数据投影后方差最大的方向,第二个主成分(PC2)是正交于 PC1 且方差次大的方向,依此类推。

当批次效应是数据中主要的变异来源时,它往往会被捕获到前几个主成分里。我在一个多中心 RNA-seq 项目里遇到过——不同测序中心的数据混在一起,PC1 直接把样本按中心分成了两簇,批次效应明晃晃地摆在那里。

关键概念: 如果 PCA 散点图上样本按批次分组聚集,而不是按生物学变量(如处理/对照)聚集,那就是批次效应在作祟。

4.2 PCA 检测批次效应的标准流程

流程不复杂,但每一步都有坑。我一般按下面几步走:

  1. 数据预处理: 标准化(Z-score)或 log2 转换,去除低表达/低变异基因。
  2. 运行 PCA: 用 prcomp() 或 sklearn.decomposition.PCA。
  3. 可视化: 用前两个或三个主成分着色,批次信息用颜色表示。
  4. 判断: 如果批次间样本分离,则存在批次效应。
  5. 量化: 计算各主成分与批次变量的相关程度。

嗯,这里要注意:标准化很重要。如果基因表达量差异巨大(比如有些基因是 1000,有些是 10),PCA 会被高表达基因主导,批次效应就可能被掩盖。我在做单细胞数据时吃过这个亏,后来老老实实先做 scale。

4.3 代码实战:用 R 和 Python 做 PCA 检测

R 版本(基于 DESeq2 的 rlog 或 vst 数据)

# 假设 expr 是 log2 归一化后的表达矩阵,行为基因,列为样本
# batch 是批次因子向量

pca_result <- prcomp(t(expr), center = TRUE, scale. = FALSE)
percentVar <- round(100 * (pca_result$sdev^2 / sum(pca_result$sdev^2)), 1)

# 提取前两个主成分
pca_df <- data.frame(
  PC1 = pca_result$x[, 1],
  PC2 = pca_result$x[, 2],
  Batch = batch
)

# 用 ggplot2 画图
library(ggplot2)
ggplot(pca_df, aes(x = PC1, y = PC2, color = Batch)) +
  geom_point(size = 3, alpha = 0.8) +
  labs(x = paste0("PC1 (", percentVar[1], "%)"),
       y = paste0("PC2 (", percentVar[2], "%)")) +
  theme_minimal()

Python 版本

import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# expr_df: 基因×样本 的 DataFrame
# 转置为 样本×基因
data = expr_df.T.values
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

pca = PCA(n_components=2)
pc_scores = pca.fit_transform(data_scaled)

# 方差百分比
var_exp = pca.explained_variance_ratio_ * 100

plt.figure(figsize=(6, 5))
for batch_name, group in df_batch.groupby('batch'):
    idx = group.index
    plt.scatter(pc_scores[idx, 0], pc_scores[idx, 1],
                label=batch_name, s=30, alpha=0.7)
plt.xlabel(f'PC1 ({var_exp[0]:.1f}%)')
plt.ylabel(f'PC2 ({var_exp[1]:.1f}%)')
plt.legend()
plt.tight_layout()
plt.show()
避坑指南: 我曾经在 1000 个样本的大数据里忘记做标准化,结果 PC1 全是测序深度差异,批次效应完全看不到。后来加了 StandardScaler,批次效应一下就冒出来了。所以,标准化这一步千万别省。

4.4 如何解读 PCA 图?

光会画图还不行,得会读图。我总结了三种典型情况:

图样 解读 行动
样本按批次完全分离 强批次效应,批次差异 > 生物学变异 必须进行批次校正
样本按批次部分重叠但有偏移 中度批次效应,部分批次间存在差异 可以考虑校正或保留为协变量
样本混合均匀,批次间无区分 无显著批次效应 放心往下做差异分析

你想想看,如果 PC1 和 PC2 加起来解释了 60% 以上的方差,而颜色正好按批次分开,那基本上可以确定批次效应是主要干扰因素。

4.5 用 PCA 量化批次效应的指标

光看散点图还是有点主观。我习惯再用一个量化指标——PC vs Batch 的 ANOVA。

具体做法:对每个主成分(比如 PC1 到 PC10),用批次做分组进行方差分析,计算 p 值。如果某个主成分的 p 值极小(比如 < 0.001),说明该主成分与批次显著相关。

# R 代码:计算每个主成分与批次的关联
pca_vars <- apply(pca_result$x[, 1:10], 2, function(pc) {
  summary(aov(pc ~ batch))[[1]]$`Pr(>F)`[1]
})
names(pca_vars) <- paste0("PC", 1:10)
print(pca_vars)

我在实际项目中,通常要求前 5 个主成分中至少有 3 个与批次显著相关,才确认批次效应需要校正。当然这个阈值你可以自己调。

4.6 本章知识体系图

PCA 检测批次效应知识体系 原始表达矩阵(基因×样本) 标准化 / log2 转换 PCA 分解(中心化 + SVD) 可视化散点图(PC1 vs PC2) ANOVA 量化关联 判断:是否存在批次效应? 散点图 + 统计双重验证

4.7 常见误区与我的经验

  • 只看 PC1 和 PC2: 批次效应有时藏在 PC3 或 PC4 里。我建议至少看前 5 个主成分的散点图矩阵。
  • 过度解读小样本: 如果每组只有 2-3 个样本,PCA 分离可能是偶然,要结合统计检验。
  • 忽略 outliers: 单个异常样本可能拉偏 PCA 方向。我曾经遇到一个污染样本,PC1 方差 80% 都是它贡献的,赶紧剔除重画。
注意: PCA 只能检测线性的批次效应。如果批次效应是非线性的(比如某些基因在批次间有交互作用),PCA 可能看不出来。这时候可以用 t-SNE 或 UMAP 辅助排查。