4. 批次效应可视化:PCA / t-SNE / UMAP 按批次着色

你得先看见问题,才谈得上解决问题。

批次效应这东西,光靠统计指标很难完全说清楚。我见过太多人一上来就调 Harmony、跑 Seurat,结果调了半天效果还是怪怪的。其实问题根本就不在矫正方法上——而是他根本没看清楚自己的数据长什么样。

所以这一章,我们就来聊聊如何通过三种最常见的降维可视化手段——PCA、t-SNE 和 UMAP——把批次效应“看”出来。说白了,就是给斑点按分群阶段或来源 batch 着色,看它们是否抱团在一起。

4.1 为什么可视化比数值更关键

假设你跑了一通校正工具,然后对着数值指标报喜:“哇, ASW 提高了 0.2, LISI 也合格了。” 但千万别止步于此。我之前做过一个项目,某个矫正后的数据指标全绿,我差点就要写结论了。结果一画 UMAP,好家伙,某个 case 样本直接飞出去成了一颗孤岛。

可视化能告诉你三件事:

  • 定性判断:批次之间是否存在明显的分群?
  • 局部与全局结构:不同 batch 是彻底混合,还是存在亚群间的不均匀混合?
  • 异常点识别:是否有 batch 内的个别斑点漂移到异常位置?

你想想看,一个数值告诉你“混合度很好”,但图上一看,某两个 batch 虽然是混在一起,但几乎只混了部分细胞类型,其他类型完全被某个 batch 独占——这不就是批次效应 + 生物学变异纠缠在一起了吗?靠数值很难判断这件事。

4.2 PCA:快速扫描全貌

4.2.1 为什么先用 PCA

PCA 计算快,结果稳定,在预处理阶段我是必看的。它不像 t-SNE 或 UMAP 对参数那么敏感,PCA 的结果基本靠谱。我个人习惯是在标准化之后、Harmony / CCA 矫正之前,先用 PCA 看看是否有明显的 batch effect。

你只要把前两个主成分(PC1 & PC2)拿过来,按 batch 上色就行了。

4.2.2 实操代码片段

假设你已经有了一个 SeuratObject,而且已经正常执行了 ScaleData 和 RunPCA。我习惯用 DimPlot 直接着色:

# Seurat v5 示例
p1 <- DimPlot(obj, reduction = "pca", group.by = "batch", pt.size = 0.5) +
  ggtitle("PCA - colored by batch")
print(p1)

如果你用的是 Scanpy,那就更直接了:

# Scanpy 示例
sc.tl.pca(adata, n_comps=50, svd_solver='arpack')
sc.pl.pca(adata, color='batch', title='PCA colored by batch')

4.2.3 我怎么看 PCA 图

不会有人盯着 PCA 图要求它完全混合。

PCA 的目标是保留最大方差,所以如果批次效应只占了很小一部分方差,那它在 PC1/PC2 上可能根本看不出来。你应当检查的是:不同 batch 是否在某些 PC 方向上明显分层。如果 PC1 基本把 batch 1 和 batch 2 分开,哪怕 PC2 混合得挺好,也得警惕。

一个实用技巧:别看前两个 PC 就觉得够了。我之前有个项目,PC1-3 看起来 batch 混合得相当完美,但我去看了一下 PC4,结果发现 batch 3 和 batch 4 在 PC4 方向上完全分离。所以多检查几个维度,不至于在第一个坑里就摔倒。

4.3 t-SNE:感受局部对比

4.3.1 t-SNE 的特性

t-SNE 非常擅长将高维近邻关系(局部结构)映射到二维平面上,但它对 perplexity 参数很敏感。你可能会遇到这样的情况:在 perplexity=30 时看 batch 完全混合,但 perplexity 调到 5 时发现有些 batch 的亚群挤成一小团。

我个人习惯用 t-SNE 作为定性校验,而不是定量标准。因为 t-SNE 不保留全局距离尺度,哪怕你知道两个 batch 混在一起了,但混得“多好”很难量化。

4.3.2 运行与着色代码

# Seurat
obj <- RunTSNE(obj, dims = 1:30, perplexity = 30)
DimPlot(obj, reduction = "tsne", group.by = "batch", pt.size = 0.3) +
  ggtitle("t-SNE - colored by batch")

# Scanpy
sc.tl.tsne(adata, n_pcs=30, perplexity=30)
sc.pl.tsne(adata, color='batch', title='t-SNE colored by batch')

# 如果用 Python 手动做,注意 random_state 固定
from sklearn.manifold import TSNE
tsne_model = TSNE(n_components=2, perplexity=30, random_state=42)
tsne_result = tsne_model.fit_transform(adata.obsm['X_pca'])

4.3.3 我在项目中遇到的坑

我之前有个项目,t-SNE 显示两个 batch 完全重叠,我当时差点就以为批次效应不存在,可以跳过矫正了。但我不死心,用 Harmony 矫正了一下,把矫正后的 PCA 再跑 t-SNE,结果发现原来所谓的“完全重叠”其实是两个 batch 在某些亚群上完全混合,但在一个稀有亚群上 batch 2 的细胞比 batch 1 多出三倍以上。这个稀有亚群在 t-SNE 上位置偏边缘,不注意看根本看不出来。

所以我的建议是:t-SNE 看大趋势,别只看它说“混得很好”就停止。

4.4 UMAP:当前可视化的事实标准

4.4.1 为什么 UMAP 比 t-SNE 更适合批处理可视化

UMAP 既保留局部结构,又尽可能保留一些全局结构。计算速度也比 t-SNE 快得多。我目前绝大部分项目都用 UMAP 作为核心可视化手段。

它还有一个巨大优势:对参数变化没那么极端敏感。n_neighbors 调到 5 和 50,结果差异会很大,但总体趋势比较稳定——这点比 t-SNE 强不少。

4.4.2 标准代码流程

# Seurat
obj <- RunUMAP(obj, dims = 1:30, n.neighbors = 30, min.dist = 0.3)
DimPlot(obj, reduction = "umap", group.by = "batch", pt.size = 0.3) +
  ggtitle("UMAP - colored by batch")

# 如果要同时看 cell type 和 batch
DimPlot(obj, reduction = "umap", group.by = "celltype", pt.size = 0.3) +
  ggtitle("UMAP - colored by cell type")
# Scanpy
sc.tl.umap(adata, n_components=2, n_neighbors=30, min_dist=0.3)
sc.pl.umap(adata, color='batch', title='UMAP colored by batch')
sc.pl.umap(adata, color='celltype', title='UMAP colored by cell type')

4.4.3 双着色对比判断法

这是我个人的一个习惯:把 UMAP 图分成左右两张,一张按 batch 着色,一张按细胞类型 / 簇注释着色。然后你对比这两张图——

  • 如果 batch 着色图有明显的 batch 集群(哪怕这些集群恰好和某些细胞类型重叠),那说明 batch effect 已经干扰了生物学结构。
  • 如果 batch 着色和 cell type 着色几乎没有重叠模式,而只是某些 batch 的值特别集中或特别分散,那可能是染色质量差异或测序深度问题,不一定是真正的 batch effect。

举个例子:

你有两个 batch,A 和 B。在 batch 着色图上,A 点集中在左上,B 点在右下;在 cell type 着色图上,这些位置正好对应不同的细胞类型——那就麻烦了,你可能面临“批次效应 == 生物学差异”的混杂问题,这也是矫正最棘手的场景。

4.5 三种方法的核心对比

属性 PCA t-SNE UMAP
适合场景 快速初筛、方差贡献分析 局部结构精细展示 兼顾全局与局部、当前标准
计算速度 极快 中等 快(比 t-SNE 快数倍)
参数敏感性 低(选择 PC 数量即可) 高(perplexity 变化极大) 中等(n_neighbors, min.dist)
可否用于定量比较 可以(欧氏距离有意义) 不可以 部分可以(嵌入距离近似)
批次效应指示性 适合发现结构性 batch 效应 适合发现局部混合异常 综合指示性强,最常用

4.6 可视化与矫正的判断闭环

嗯,这里很重要,我专门画了一张流程图来说明批次效应检测与矫正之间的可视化判断闭环。

批次效应可视化与矫正判断闭环 输入:已标准化的 PCA 坐标 PCA + t-SNE + UMAP 按 batch 着色 (perplexity=30, random_state 固定) 判断:batch 是否主导分群? 是否存在明显 separation 或 cluster by batch? 是否与生物学分组存在混淆? 是 运行矫正:Harmony / CCA / scVI 选择与数据适配的矫正方法 否 无需额外矫正 进入找差异基因 / 聚类分析 迭代验证:重新 PCA → t-SNE → UMAP 观察混合效果 每次矫正后都重画一次

你看,这个闭环的核心在于:每次矫正方法调整后,都必须重新做一遍 PCA → t-SNE → UMAP 可视化评估,才能判断矫正效果是否足够。

4.7 可视化中的常见隐藏陷阱

  • 参数不同导致错觉:t-SNE 的 perplexity 越大,局部结构越平滑,batch 不一定会“分群”但可能变得模糊。我建议用几个不同的 perplexity 值(15, 30, 50)检查一致性。
  • 点太多看不清:当你的空间数据有几十万个 spots 时,散点图上的点互相重叠,你可能根本看不出 batch 效应。这时我习惯用 2D 密度图(stat_density_2d)配合透明度叠加来判断。
  • 选错 PC 数:如果只基于前 5 个 PC 跑 t-SNE / UMAP,你可能会丢失信息。我个人习惯用 30-50 个 PC。
  • 随机种子差异:t-SNE 和 UMAP 都有随机性,不固定种子,你看到的“batch 效应”可能只是随机波动。
实用提示:
固定随机种子 random_state=42(Scanpy)或 seed.use = 42(Seurat),保证每次重画结果可复现。这是基本的科研诚信和可重复性要求。
注意:
不要只看一种降维方法就下结论。如果一个 batch 在 PCA 上明显分离,但在 t-SNE 上似乎混合良好,那多半是 t-SNE 的局部结构淹没掉了批次的全局差异——以 PCA 为准,不要被 t-SNE 的“迷惑性混合”欺骗了。

4.8 综合评估与下一步行动

在你跑完上面三张图后,大致会有以下几种情况,可以参考对应的处理策略:

可视化发现 可能原因 推荐行动
PCA 和 UMAP 均无 batch 分离 批次效应极弱或不显著 直接进入下游分析(但保持怀疑心态)
PCA 分离明显,但 UMAP 混合良好 批次效应占方差大,但局部结构混合好 尝试 Harmony 矫正,再重画验证
t-SNE / UMAP 上 batch 完全分离成团 强批次效应存在 必须矫正,并考虑使用 Harmony + 移除低质量 batch 或样本
某些 batch 内部稀疏,无局部密度 测序深度低、细胞污染或批次质量差 考虑剔除该 batch 或先 QC 过滤

嗯,看到这里你应该已经掌握了“可视化前置,判断后跟”的基本流程。别想着跳过这一步直接上矫正工具,除非你非常熟悉自己的数据——我见过太多人跳过后补一上午的结果发现没效果,其实就是因为没看清问题出在哪。