批次效应检测方法:可视化与统计检验

做批次校正之前,你得先确认一个问题:到底有没有批次效应?

我刚开始做多批次的单细胞数据整合时,上来就一股脑地跑Harmony,结果校正完之后发现生物信号被抹掉了不少。后来才明白——检测比校正更重要。你连有没有效应都不清楚,怎么敢随便调参数?

我个人习惯是先用可视化方法瞄一眼全局,再用统计检验给个量化结论。两个层面的证据对上号了,再动手校正。这一章我就把这两类方法拆开讲讲。

可视化方法:先看个大概

可视化方法的本质是降维。把高维的基因表达数据压到2D或3D空间,然后按批次着色,看看不同批次的样本是不是混在一起。如果它们各自抱团、分得清清楚楚,那批次效应八九不离十。

常用的三个工具:PCA、t-SNE、UMAP。各有各的脾气。

1. PCA(主成分分析)

PCA是最老实的一个。它是线性降维,速度快,解释性强。适合先看一眼数据的整体结构。

我的经验:PCA对噪音敏感,如果数据里个别基因表达特别高(比如线粒体基因),前几个主成分可能被它们带偏。我一般会用scaled数据做PCA,或者先过滤掉异常高表达的基因。
实用小技巧:在PCA散点图上,如果不同批次的样本沿着PC1或PC2方向明显分离,说明批次效应可能占主导地位。正常情况应该是各批次混在一起,形成"蜂窝状"分布。

2. t-SNE(t分布随机邻域嵌入)

t-SNE是非线性的,擅长把局部结构展示出来。但它有个大问题:随机性。每次运行结果都不一样,而且对困惑度(perplexity)参数敏感。

我记得有次分析一个免疫细胞数据集,明明PCA看着批次效应不大,结果t-SNE画出来,各批次的T细胞居然各自缩成一团。后来调整了困惑度,又正常了。你看,这东西不能全信。

避坑提醒:t-SNE的结果不能直接用于定量比较两个批次之间的差异大小。它只适合"看有没有",不适合"看有多大"。

3. UMAP(均匀流形逼近与投影)

UMAP是目前最流行的选择。它保留了更多全局结构,速度也比t-SNE快,结果相对稳定。我个人习惯用UMAP做主图。

不过UMAP也有参数坑。比如n_neighbors设太小,局部细节会被放大,批次效应容易被"画"出来。我一般设为15~30,先跑一次,再用不同参数验证。

推荐做法:先做PCA,再用PCA的前30~50个主成分输入到UMAP,这样既去噪又保留信息。别直接用全基因表达矩阵跑UMAP,计算量太大,还容易过拟合随机结构。

统计检验方法:用数字说话

可视化是感性认识,统计检验才能给出"是否显著"的硬证据。

我最常用的两个指标:ANOVA的F统计量和R²(决定系数)。

1. ANOVA(方差分析)

ANOVA的核心思想:把总方差分解成"批次造成的方差"和"随机误差"。如果批次间差异远大于批次内差异,F值就大,p值就小。你可以对每个基因都跑一个单因素ANOVA,然后看有多少基因的p值小于0.05。

一个坑:如果样本量极大(比如单细胞数据有上百万细胞),即使非常微小的批次效应也会得到p<0.001。这时p值基本没用。我更多看效应量(effect size),比如η²或Cohen's d。
# 对每个基因跑单因素ANOVA(伪代码示意)
import numpy as np
from scipy import stats

genes = expression_matrix.T  # 基因×样本
batch_labels = meta['batch']

for gene in genes:
    groups = [gene[batch_labels == b] for b in unique_batches]
    f_stat, p_val = stats.f_oneway(*groups)
    # 记录f_stat, p_val

2. R²(决定系数)

R²衡量"批次这个变量能解释多少方差"。你把批次作为一个分类变量,对每个基因做线性回归,看加入批次变量后R²增加了多少。或者更直接地,用批次做响应变量?不对,应该是将基因表达量作为响应变量,批次作为自变量。

我个人习惯计算每个基因的批次相关方差占比:

# 伪代码:计算每个基因的R²
from sklearn.linear_model import LinearRegression

r2_list = []
for gene in genes:
    X = pd.get_dummies(batch_labels)  # one-hot
    y = gene
    model = LinearRegression().fit(X, y)
    r2 = model.score(X, y)
    r2_list.append(r2)
mean_r2 = np.mean(r2_list)

如果平均R²超过0.1,就说明批次效应不可忽视。我见过一些数据平均R²能到0.4,那基本就是批次为主、生物学差异为辅了。

重要提醒:R²高不代表一定有生物学问题。比如你有一个批次全是处理组,另一个批次全是对照组,那R²既包含了批次效应也包含了处理效应。这种情况下要先能看到原始数据处理组和对照组怎样分组的。

方法对比总结

方法 类型 优点 缺点 我的使用场景
PCA 可视化 线性、快速、可解释 对异常值敏感 初始快速扫描
t-SNE 可视化 局部结构清晰 随机性大、参数敏感 辅助验证
UMAP 可视化 全局+局部兼顾、速度快 参数n_neighbors影响大 主图生成
ANOVA (F值) 统计检验 假设检验明确 大样本下p值失效 小样本、定量指标
R² 统计指标 效应量直观、可比较 不能区分批次与生物学 大样本、定量评估
我的工作流:先跑PCA看个全貌,再跑UMAP确认局部混叠情况。如果两份图都显示批次分离明显,就计算每个基因的R²,取中位数或均值。中位数R²超过0.1就启动校正。如果只是少量基因有批次效应,可能用线性回归逐基因校正就够了,不需要用全局方法。
批次效应检测流程 原始数据(多批次) 可视化降维 统计检验 PCA t-SNE UMAP ANOVA (F值) R² (决定系数) 综合判断 → 是否校正? 两组证据一致: 可视化分离 + 统计显著 → 校
一个常见误区:有些人只看UMAP图就说"没批次效应",其实可能是UMAP参数把批次差异抹平了。我建议始终用PCA + 统计检验交叉验证。如果PCA里批次混在一起,但UMAP却分开,那多半是UMAP的局部结构过头了。
曾经踩过的坑:有一次我处理一个10x单细胞数据集,两个批次分别来自不同测序深度。PCA和UMAP都显示批次分离,但用ANOVA一看,p值全都<0.001,R²却只有0.03。这其实不是真正的批次效应,而是测序深度差异造成的"假阳性"。后来我用scran的scrignorm做了深度校正,才看到真实的生物学聚类。所以千万不要只看一个方面。