引言:什么是差异表达分析
刚入行那会儿,我第一次接触RNA-seq项目,老板扔过来一个矩阵——基因名排成行,样本名列成列,每个格子都是个计数。他说:“跑一下差异表达,把上调下调的基因给我。”我当时以为就是做个除法、算个p值那么简单,结果一上手才发现,坑比想象中多得多。
什么是差异表达分析?说白了,就是比较两组(或多组)样本中,基因表达水平是否有显著差异。比如对照组vs处理组,或者肿瘤组织vs正常组织。我们关注的不是绝对表达量高低,而是两组之间的变化是否可靠、是否具有生物学意义。
核心问题:这个基因在A组平均表达量是10,在B组是100,看起来差10倍——但这个差异是真实的,还是由批次效应、测序深度不均、技术误差造成的?差异表达分析就是要给出统计学上的回答。
为什么I会单独拿PCA图来评估可靠性?
我见过太多新手扔出差异基因列表就交差,完全不管下游验证是否站得住脚。我个人习惯是:在做任何差异分析之前,先画一个PCA图。为什么?因为PCA能直观暴露出数据里的大问题——样本聚类是否正常、有没有离群点、批次效应是否严重。
举个例子。有一次我处理一个公共数据库的RNA-seq数据,三组样本,每组三个重复。跑完count矩阵后直接做DESeq2,结果差异基因哗啦啦出来几百个。但我不放心,先画了个PCA——好家伙,六个样本全混在一起,根本看不到分组趋势。仔细一查,是样本标签写反了。如果没有PCA这一步,我可能会推出一堆假阳性结果,那可就丢人了。
我的经验:PCA图是我每次差异分析流水线上的第一道质检关。没通过PCA的检查,后面的一切差异结果我都不敢信。
差异表达分析的常规流程
差异表达分析一般分几个阶段。我用一个表格总结下常见的步骤和工具,方便你有个整体印象。
| 阶段 | 核心操作 | 常用工具/包 |
|---|---|---|
| 数据预处理 | 质量控制、去接头、比对、定量 | FastQC, Trimmomatic, STAR, Salmon |
| 计数矩阵构建 | 生成基因-样本计数表 | featureCounts, htseq-count |
| 归一化 | 消除测序深度、转录本长度等系统偏差 | TPM, FPKM, TMM (edgeR), median-of-ratios (DESeq2) |
| 差异检验 | 基于统计模型计算p值、log2FC | DESeq2, edgeR, limma-voom |
| 多重假设校正 | 控制假阳性率(FDR) | BH (Benjamini-Hochberg), q-value |
| 结果可视化与评估 | 火山图、热图、PCA图、MA图 | ggplot2, pheatmap, PCAtools |
| 生物学解释 | GO/KEGG富集分析、蛋白互作网络 | clusterProfiler, DAVID, STRING |
你会发现,PCA图被列在“结果可视化与评估”阶段。但我个人习惯把它提前到归一化之后、差异检验之前——先看看数据是否干净,再做统计推断。
PCA到底在看什么?
PCA(主成分分析)是一种无监督降维方法。它把高维表达数据(一个样本可能测到两万个基因)压缩成少数几个主成分,然后用前两个或三个主成分画散点图。每个点代表一个样本,点与点的距离反映转录组层面的相似程度。
正常情况下,生物学重复应该聚在一起;不同实验组应该自然分开。如果样本按已知分组完美聚类,那差异分析大概率会得到可靠的结果。如果分组混乱,说明数据里混杂了更大的变异来源——比如批次效应、细胞类型比例差异、或者根本就是样本搞混了。
警告:不要以为PCA图好看就万事大吉。PCA只能反映最大方差方向,小但重要的生物学差异可能在后续主成分上才显现。另外,PCA对离群点非常敏感,一个坏样本就足以扭曲整个PC1方向。所以看PCA时要配合统计指标(如各主成分方差解释率)一起判断。
一个简单的PCA案例(代码片段)
我用R语言演示一下最基础的PCA画图流程。假设你已经有一个归一化的表达矩阵 expr_matrix,行是基因,列是样本;还有一个 metadata 表包含样本分组信息。
# 加载包
library(ggplot2)
library(PCAtools)
# 转置:PCA要求样本为行,基因为列
pca_input <- t(expr_matrix)
# 计算PCA
pca_result <- prcomp(pca_input, center = TRUE, scale. = FALSE)
# 提取主成分得分
pca_df <- as.data.frame(pca_result$x)
pca_df$group <- metadata$group # 添加分组信息
# 计算方差解释率
var_explained <- round(100 * pca_result$sdev^2 / sum(pca_result$sdev^2), 1)
# 绘制PC1 vs PC2
ggplot(pca_df, aes(x = PC1, y = PC2, color = group)) +
geom_point(size = 3, alpha = 0.8) +
stat_ellipse(aes(fill = group), geom = "polygon", alpha = 0.1) +
labs(x = paste0("PC1 (", var_explained[1], "%)"),
y = paste0("PC2 (", var_explained[2], "%)"),
title = "PCA of RNA-seq samples") +
theme_minimal()
运行这段代码,你会看到像下面这样的结构关系图。我来画一个概念性的SVG,把PCA的核心逻辑串起来——这样你记起来更容易。
哪些因素会导致PCA图“翻车”?
我自己总结了几类常见问题,一旦PCA上出现这些特征,你就要小心了:
- 所有样本混成一团——分组间差异太小,或者测序深度、文库复杂度差异太大盖过了生物学信号。解决办法:重新归一化或检查测序质量。
- 重复样本离散度过大——说明技术重复或生物学重复之间不一致,可能是RNA质量差、建库批次效应或测序深度不均。我遇到过一次,同一个样本的两个技术重复一个在左上一个在右下,结果是反转录步骤污染了。
- 某个分组完全与另一个分组重叠——要么是这个分组没有特有表达特征,要么是分组标签给错了。我曾经有一组处理组的样本反而和对照组聚在一起,后来发现是给药浓度搞混了。
- PC1方差解释率超级高(>90%)——不一定全是好事,可能数据里有一个极强的主成分(比如批次效应或细胞周期差异)压过了所有生物学信号。这时候要先跑一跑limma的removeBatchEffect。
避坑指南:我建议在所有差异分析项目里,把PCA图的生成写成脚本的一部分,强制在运行差异检验之前先输出。不要手动去点UI画图——写成自动化流程,每次跑数据就会自动出图,你扫一眼就能决定要不要往下走。
本章小结:PCA是差异分析的门神
你不是在“画PCA图”这件事上花功夫,而是在给差异分析的可靠性设立第一道防线。没有这道防线,后面所有的p值、倍数变化、富集结果都可能是建立在沙上的城堡。
这套课程里,我们会一步一步把PCA图的每个细节抠清楚——从数据准备到主成分解释到用PCA筛选差异候选基因。我手上有几个真实项目翻车的案例,后面会拿出来给你看,让你一次记住所有坑。
嗯,先搞清楚概念,下节我们直接上手实操。