3. 批次效应的可视化技术:PCA、t-SNE、UMAP
可视化,说白了就是给你的数据“拍张照”。
高维数据你看不见摸不着,但降维投影到二维或三维空间后,批次效应就藏不住了。我个人习惯在跑任何统计检验之前,先用可视化扫一眼。这步做对了,后面省一半时间。
3.1 为什么需要降维可视化?
单细胞数据动辄上万基因,直接画散点图?不现实。我们需要把高维空间“压”到低维,同时保留关键结构。
三个目标必须同时满足:
- 分组可见:生物条件(病变/正常)能否分开?
- 批次暴露:来自不同批次的样本是否各自抱团?
- 异常识别:有没有孤零零漂在外面的离群样本?
你想想看,如果第一批次样品全挤在左边,第二批次全压在右边,这肯定有问题。
3.2 PCA:第一步快速筛查
主成分分析(PCA)是你工具箱里最快的那把刀。它用线性变换找出方差最大的方向,投影下去一看便知。
3.2.1 什么时候该用PCA?
- 初步数据浏览:5秒出图,立刻判断有没有明显批次分离。
- 大样本量场景:万级细胞,t-SNE要跑半小时,PCA十秒就搞定。
- 移除强烈批次效应前/后对比:校正前各批细胞在PC1上明显分离;校正后混在一起。
3.2.2 实战代码
在Python里用scanpy一行就够。我个人习惯先标准化再跑PCA。
import scanpy as sc
import matplotlib.pyplot as plt
# 读取数据
adata = sc.read_h5ad("batch_data.h5ad")
# 标准化 + 高变基因选择
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
# PCA
sc.pp.pca(adata, n_comps=50)
# 按批次着色
sc.pl.pca(adata, color="batch", title="PCA colored by batch")
# 按生物学条件着色
sc.pl.pca(adata, color="condition", title="PCA colored by condition")
3.2.3 PCA的局限性
说实话,PCA有时候也会骗人。它只能捕获线性结构。如果批次效应嵌套在非线性流形里,PCA可能完全看不到。
我在项目中遇到过这样的情况:PCA图上不同批次完全重叠,我差点以为没批次效应。结果用UMAP一跑,每个批次自成小岛。所以别只看PCA就下结论。
3.3 t-SNE:局部细节放大镜
t-分布随机邻近嵌入(t-SNE)是非线性的王者。它把高维相似度映射到低维,特别擅长揪出那些PCA漏掉的局部模式。
3.3.1 关键参数:perplexity
perplexity可以理解为“每个点考虑多少个邻居”。我见过很多人用默认值30就撒手不管了。
| perplexity值 | 效果倾向 | 实测建议 |
|---|---|---|
| 5 ~ 15 | 局部细节丰富,但易产生假簇 | 小样本(<500细胞)时用 |
| 30 ~ 50 | 全局平衡较好 | 中等样本(500~5000细胞)首选 |
| 50 ~ 100 | 整体结构更平滑,局部细节丢失 | 大样本(>5000细胞)适用 |
3.3.2 批次效应解读技巧
t-SNE图上看到批次分离后,别急着下结论。先检查:分离是否沿着t-SNE1方向?如果是,那大概率是批次效应。如果分离方向与已知生物标记物对齐,那可能是真实差异。
# scanpy的t-SNE运行
sc.tl.tsne(adata, n_pcs=30, perplexity=30, random_state=42)
sc.pl.tsne(adata, color=["batch", "cell_type"],
title=["t-SNE by batch", "t-SNE by cell type"])
3.4 UMAP:你的日常工作马
UMAP(统一流形逼近与投影)是我现在用得最多的工具。为什么?因为它既保留了t-SNE的局部聚类分辨率,又比t-SNE快一个数量级。
3.4.1 UMAP vs PCA vs t-SNE
| 特性 | PCA | t-SNE | UMAP |
|---|---|---|---|
| 变换类型 | 线性 | 非线性 | 非线性(基于拓扑) |
| 全局结构 | 保留较好 | 不保留 | 部分保留 |
| 局部聚类 | 一般 | 优秀 | 优秀 |
| 计算速度 | 极快 | 慢(O(n²)) | 快(近似O(n¹.¹⁴)) |
| 可重复性 | 确定 | 随机种子依赖 | 可设置种子,相对稳定 |
3.4.2 UMAP的批次效应诊断流程
我个人的标准流程是这样:
- 先跑UMAP:用n_neighbors=15, min_dist=0.3的默认参数。
- 看三张图:按批次着色、按实验条件着色、按已知细胞类型着色。
- 对比分离度:如果批次标签能完美预测UMAP聚类,而细胞类型标签却混淆在一起,那批次效应需要校正。
- 调节参数验证:把n_neighbors从5调到50,看批次分离图案是否稳定。稳定的话,基本就是真·批次效应了。
# UMAP + 多视图对比
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
sc.tl.umap(adata, min_dist=0.3, random_state=42)
# 同一张画布对比批次 vs 细胞类型
sc.pl.umap(adata, color=["batch", "cell_type", "condition"],
ncols=3, wspace=0.3,
title=["UMAP: Batch", "UMAP: Cell Type", "UMAP: Condition"])
3.5 三图联判法
光看一张图就做决定?不太稳妥。我推荐“三图联判”:
- PCA图:快速评估全局趋势。如果PC1上各批完全对齐,大概率强批次效应。
- UMAP图:精细确认局部结构。各批次是否形成独立的小岛?
- t-SNE图:作为UMAP的交叉验证。如果UMAP和t-SNE都显示同样的批次分离模式,那基本上板上钉钉了。
如果三张图中,批次标签都能把数据完美分成独立的簇 → 批次效应严重,必须校正。
如果三张图中,批次标签只是轻微重叠在真实细胞的子结构上 → 批次效应较弱,可接受或轻度校正。
如果三张图中,只有一张图显示批次分离 → 再核实一下参数设置,可能只是可视化假象。
3.6 避坑经验
讲了这么多,最后说说我踩过的坑。
曾经有一次,我处理一个4个批次的数据。PCA看起来完美重叠——我高兴坏了,以为没有批次效应。结果UMAP一跑,4个批次排成了4条平行线。后来才发现,这4个批次里各包含不同的细胞亚型比例差异。PCA因为方差大被“细胞类型”主导了,而UMAP把“批次”的细微差异给放大了。
所以我的建议是:可视化只是线索,不是证据。看到分离后,一定要结合定量指标(如批次差异评分、PCA贡献率)做双重确认。
另外,别忘了颜色标度。有时批次只有两个值,染色很容易。当批次有10个以上时,颜色容易混淆。我会用ColorBrewer的调色板来保证区分度。
# 多批次时建议用显式调色板
batch_colors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8',
'#f58231', '#911eb4', '#42d4f4', '#f032e6']
sc.pl.pca(adata, color="batch", palette=batch_colors)
嗯,到这里,可视化的基本套路就到位了。你手上有数据的话,建议现在就打开Jupyter跑一遍——肉眼看到的,往往比p-value更直观。