第3章 批次效应可视化:PCA、t-SNE等展示数据分布
你拿到一批质谱数据,第一件事是什么?我个人习惯,不是急着跑差异分析,而是先做可视化。说白了,就是先看看数据长什么样,有没有明显的批次结构。这一步做扎实了,后面建模才心里有底。
可视化批次效应,主流方法就那几种:PCA、t-SNE、UMAP,偶尔也用到MDS。它们背后的数学原理可以很复杂,但在咱们实战里,你只需要记住一件事:这些方法能把高维数据压到2维或3维,让你用肉眼判断样品是否按批次聚类。我曾经见过一个团队,直接拿原始强度值跑PCA,结果全乱套——因为方差最大的主成分被几个高丰度代谢物带偏了。所以,先处理数据再用可视化,顺序不能错。
3.1 数据预处理:从原始矩阵到降维输入
先给原始强度矩阵做个log2转换,这是质谱数据的常规操作。然后按特征(代谢物/峰)做z-score标准化,让每个特征均值为0、方差为1。
为什么要标准化?如果不做,PCA会偏向方差大的特征——那些高丰度的代谢物可能压掉真正的批次差异。我再告诉你一个坑:标准化之前一定要检查缺失值。我带的某个项目中,一批QC样本测出来的峰面积全为0,如果不剔除,z-score之后那批样本全部被压缩成一个点,视觉上变成离群簇,误导了整整一周的分析。
下面是个典型的R语言预处理代码,我习惯用scale()一步到位:
# 读取原始强度矩阵 (行: 样本, 列: 代谢物)
data_raw <- read.csv("peak_intensity.csv", row.names = 1)
# log2转换 (加1避免log0)
data_log <- log2(data_raw + 1)
# z-score标准化 (按列)
data_scaled <- scale(data_log)
# 检查缺失: 任意一列有NA就剔除或插补
data_scaled <- data_scaled[, colSums(is.na(data_scaled)) == 0]
预处理后的矩阵就是降维的输入了。记住,PCA和t-SNE接收的都是样本×特征矩阵,行是样本,列是变量。
3.2 主成分分析(PCA)——快速检查批次效应
PCA是我最早接触的可视化方法。它的核心思想是找到一组正交方向(主成分),让投影后的样本方差最大。前两个PC通常能解释总变异的30%~60%,如果批次效应足够强,你甚至不需要调参数就能看到明显分离。
实战代码,还是用R的prcomp():
# PCA
pca_result <- prcomp(data_scaled, center = FALSE, scale. = FALSE)
# 提取前两主成分得分
pca_df <- as.data.frame(pca_result$x[, 1:2])
pca_df$Batch <- factor(sample_meta$Batch) # 批次标签来自样本信息表
# 绘制散点图
library(ggplot2)
ggplot(pca_df, aes(x = PC1, y = PC2, color = Batch)) +
geom_point(size = 3) +
stat_ellipse(level = 0.95) +
theme_minimal() +
labs(title = "PCA: Sample Distribution by Batch")
你得到了这张图,怎么看?如果不同批次的样本在PC1/PC2平面上明显分开,比如一批全在左边、一批全在右边,那就是典型的批次效应。但如果只是彼此交错,或者只有个别离群点,问题可能不大。
3.3 t-SNE——局部结构下的批次发现
t-SNE(t-distributed Stochastic Neighbor Embedding)和PCA的思路完全不同。它不关心全局方差,而是尽量保留样本间的局部邻域关系——说白了,就是让原来离得近的样本在二维图上仍然挨着,离得远的样本彻底分开。这使得t-SNE特别擅长揭示那些在PCA中看不到的“小批次效应”。
但t-SNE有个麻烦的参数:perplexity(困惑度)。它大致代表每个点的“有效邻居数”。经验值一般在5~50之间,太小了图会成团分散,太大了又过于平滑。我的经验是,对于几百个样本的数据,先设30,如果看不清楚再往小调。而且一定要固定随机种子,否则每次运行结果都不一样。
R语言实现,用Rtsne包:
library(Rtsne)
set.seed(42) # 固定随机种子
tsne_result <- Rtsne(data_scaled,
dims = 2,
perplexity = 30,
verbose = TRUE)
tsne_df <- as.data.frame(tsne_result$Y)
colnames(tsne_df) <- c("tSNE1", "tSNE2")
tsne_df$Batch <- factor(sample_meta$Batch)
ggplot(tsne_df, aes(x = tSNE1, y = tSNE2, color = Batch)) +
geom_point(size = 3) +
theme_minimal() +
labs(title = "t-SNE: Sample Distribution by Batch")
你可能会问:为什么t-SNE比PCA更容易发现批次效应?因为质谱数据中,批次往往体现在局部特征模式的漂移(比如某段保留时间内的峰强度集体偏移),这些变化对全局方差贡献不大,但对局部邻域的影响却很显著。t-SNE刚好擅长捕捉这种模式。我曾在一次合作项目中,PCA图显示两批样本混在一起,但t-SNE却分出了两个明显的簇——后来追踪实验原始记录,发现第二批的色谱柱确实换了。那一次的经验让我从此把t-SNE作为必选项。
3.4 其他可视化方法(UMAP、MDS等)
除了PCA和t-SNE,还有几个常用方法,简单对比一下:
| 方法 | 原理类型 | 全局/局部 | 计算速度 | 典型场景 |
|---|---|---|---|---|
| PCA | 线性降维 | 全局 | 极快 | 快速初筛 |
| t-SNE | 非线性降维 | 局部 | 较慢 | 发现小批次效应 |
| UMAP | 非线性降维 | 兼顾局部+全局 | 快 | 大数据集批次可视化 |
| MDS | 线性/非线性 | 取决于距离度量 | 中等 | 当需要保留原始距离时 |
我个人在项目中,PCA + t-SNE是最常用的组合。如果样本量超过2000,我会换用UMAP,因为它快且能保留更多全局结构。UMAP的R包是umap,Python里是umap-learn,用法和t-SNE类似。
3.5 综合判断批次效应的可视化策略
最后,我画一张流程图来总结这个可视化决策流程——你每次拿到新数据,都可以照着走一遍。
这个流程图的逻辑很直白:先跑PCA快速看全局,如果批次明显分离就直接确认;如果看不出分离,再用t-SNE或UMAP深入看局部。局部也看不出,那这批次效应可能真的弱到可以忽略,你放心融合就是了。
最后提一句:可视化只是定性手段,要想量化批次效应的强度,你还需要配合后面章节会讲到的统计检验(如ANOVA、PERMANOVA)。不过先把图看顺了,大脑的直觉往往比数字更敏锐。今天就先到这里,你可以拿自己的数据试试,按照这个流程走一遍。