4. 常用的批次效应检测方法介绍

说实话,批次效应检测这件事,我踩过的坑比学过的理论多。早些年做多组学整合,愣是把生物学差异和批次效应混在一起,结果结论全反了。后来我养成了一个习惯:校正之前,先花十分钟做检测。这十分钟能省下后面好几天返工的时间。

下面这几种方法,是我在项目中反复验证过的。它们各自有适用场景,我建议你按顺序组合使用——先全局鸟瞰,再局部排查。

4.1 主成分分析(PCA)—— 最直观的“第一眼”

PCA 是我个人最常用的探索性工具。把高维基因表达数据压缩到前几个主成分,然后按批次给样本点着色。如果同一批次的样本在 PC 空间里扎堆,而不同批次的样本明显分离,那就有问题了。

操作起来很简单:

# R 示例:pca 后按批次着色
pca_result <- prcomp(t(expr_matrix), scale. = TRUE)
plot(pca_result$x[,1], pca_result$x[,2],
     col = factor(batch_vector), pch = 19,
     xlab = "PC1", ylab = "PC2")
legend("topright", legend = levels(factor(batch_vector)),
       col = 1:length(unique(batch_vector)), pch = 19)

你想想看,如果前两个主成分的方差解释率超过 30%,并且批次分离明显,基本可以判定存在批次效应。但要注意:PCA 太灵敏了,有时候生物学差异也会导致分离,所以需要结合后续方法交叉验证。

个人经验:我习惯在 PCA 的基础上再加一个“批次主成分贡献”图——计算每个主成分与批次标签的相关性。如果某个 PC 与批次的相关性 >0.7,那个 PC 基本就是被批次效应主导的。这个方法比肉眼判断更客观。

4.2 层次聚类与相关性热图

层次聚类是 PCA 的补充。我不看树形图的具体分支,而是看样本是否按批次聚在一起。

# 计算样本间相关系数
cor_mat <- cor(expr_matrix, method = "spearman")
# 层次聚类
hc <- hclust(as.dist(1 - cor_mat), method = "ward.D2")
# 绘制带批次标签的聚类树
plot(hc, labels = batch_vector, cex = 0.6, main = "Sample Clustering")

我记得有一次,聚类树上所有批次 1 的样本先聚成一簇,批次 2 的样本再聚成另一簇——中间几乎没有交叉。嗯,那就是典型的批次效应,而且强度很大。

避坑指南:我曾经在50个样本上直接画全相关性热图,结果颜色块密密麻麻根本看不出来。后来我改用批次内平均相关系数 vs 批次间平均相关系数的对比。如果批次内相关系数比批次间高出一大截(比如差值 >0.1),基本可以确认有批次效应。

4.3 UMAP / t-SNE —— 别忘了设置随机种子

t-SNE 和 UMAP 是近年流行的可视化方法。它们能保留数据的局部结构,往往比 PCA 更清晰地呈现批次分离。但有一个大坑:随机性。

# R 示例:UMAP 可视化批次
library(umap)
umap_config <- umap.defaults
umap_config$random_state <- 42  # 固定随机种子!
umap_result <- umap(t(expr_matrix), config = umap_config)
plot(umap_result$layout, col = factor(batch_vector), pch = 19,
     xlab = "UMAP1", ylab = "UMAP2")

为什么一定要固定种子?因为 UMAP 每次运行结果都可能不同。你不固定种子,今天看到的分离,明天可能就消失了。我团队里有个新人没设种子,汇报的时候领导让他重现结果,跑了三次三次都不一样——那场面相当尴尬。

另外,t-SNE 对困惑度参数敏感。我曾遇到过困惑度设太小,局部结构被放大,把本来没关系的一批样本强行拉在一起。我建议先用困惑度 = 30 试跑,如果不行再调。

4.4 方差分析(ANOVA)—— 定量检测的硬指标

可视化方法多少有点主观。我们还需要一个数字指标来量化批次效应的大小。方差分析(ANOVA)正好做这个。

对于每个基因,我们计算“批次”因素解释的方差比例。如果很多基因的批次方差占比超过 30%,那批次效应就很明显了。

# 对每个基因做 ANOVA,提取批次效应占比
pve_batch <- apply(expr_matrix, 1, function(gene) {
  df <- data.frame(expression = gene, batch = batch_vector)
  fit <- aov(expression ~ batch, data = df)
  ss <- summary(fit)[[1]]$`Sum Sq`
  ss[1] / sum(ss)
})
hist(pve_batch, breaks = 50,
     main = "Distribution of Proportion Variance Explained by Batch",
     xlab = "PVE_batch", col = "steelblue")
abline(v = 0.3, col = "red", lwd = 2, lty = 2)

红色虚线标记 0.3。如果大多数基因的 PVE 都低于这条线,说明批次效应可控;反之就要警惕了。我个人觉得,当超过 20% 的基因 PVE > 0.3 时,必须做校正。

核心指标速查表:

指标判断阈值备注
PCA 批次分离PC1/PC2 按批次明显分离需结合生物学分组验证
批次内 vs 批次间相关系数差差值 > 0.1适合大规模芯片数据
UMAP 批次聚集同批次样本在局部区域密集务必固定随机种子
PVE_batch > 0.3 的基因占比> 20%定量硬指标

4.5 一种更系统的方法:Silhouette 宽度评估

最后补充一个我最近特别喜欢用的方法——轮廓系数(Silhouette)。它不仅能评估聚类效果,还可以用来衡量批次分离程度。

计算每个样本基于批次标签的轮廓宽度。如果整体平均轮廓宽度 > 0.5,说明批次之间的分离非常明显。如果 < 0.1,说明批次混杂得比较好。

# 基于批次标签的轮廓宽度
library(cluster)
dist_mat <- dist(t(expr_matrix))
sil_batch <- silhouette(as.numeric(factor(batch_vector)), dist_mat)
mean(sil_batch[,3])  # 输出平均轮廓宽度

我在一个 8 批次的数据集上跑过,平均轮廓宽度 0.62。这个数字让我果断决定要用 ComBat 校正。校正后再跑一次,降到了 0.12——效果立竿见影。

4.6 知识结构总览

上面这些方法不是孤立的。我习惯按一个固定的流程跑:先 PCA 看全貌,再 UMAP 看局部,然后用 ANOVA 定量,最后用 Silhouette 做最终确认。下面这张图把流程串了起来。

批次效应检测方法流程 原始表达矩阵 全局检测:PCA + 层次聚类 局部检测:UMAP / t-SNE 定量 ANOVA(PVE_batch) Silhouette 宽度评估 综合判定:是否存在批次效应? 无需校正,直接下游 执行 ComBat 校正 数据准备 可视化检测 定量评估 决策节点

我的个人工作流:拿到新数据后,我先 PCA + 层次聚类(5分钟),如果看出明显批次分离,立刻跑 ANOVA 计算 PVE 分布。然后 UMAP 做双重确认。最后用 Silhouette 给一个数字结论。整个流程跑下来不超过半小时,但能帮我避免 80% 以上的批次校正失误。

嗯,工具和方法就这些。下一件事,就是根据检测结果决定要不要做 ComBat 校正了。不过别急,检测这一步做扎实了,后面的校正才有意义。