资讯详情

资讯详情

转录组PCA分析避坑指南:标准化、离群样本与批次效应

1. 为什么转录组里几乎人手一张PCA图却总在三个地方翻车做转录组项目第一批要出的图里面大概率有主成分分析PCA图。样本量不论大小跑完比对定量拿到表达矩阵之后大家习惯性就是一句先画个PCA看看样本能不能分开。这个习惯本身没问题PCA确实是探索样本整体表达谱关系、发现异常样本、判断组间分离趋势的最快工具。但这几年我经手了不少转录组项目也和很多做生信的同学对过线发现围绕PCA的误解比想象中多而且翻车的点非常集中。最常见的情况是把PCA图当成了“最终结论图”看到点聚在一起就说组间没差异看到某个点飞出去就说这个样本质量差看到批次分的清清楚楚就开始慌。而真正导致结论相反的往往不是PCA这个算法本身而是跑PCA之前的数据标准化、跑完之后对离群样本的处理、以及在批次效应面前采取的应对策略。这三件事要是没做对PCA图长得再漂亮结论也可能站不住。这篇文章就围绕转录组PCA里最容易踩的三个坑展开数据标准化、离群样本识别与处理、批次效应处理。每一部分我都会从原理层面解释“为什么这是个坑”然后给出我在实际项目里的处理方式包括代码、判断标准和踩坑记录。内容不追求把PCA的数学推导写全重点放在能用、能避坑、能说服自己和审稿人的操作细节上。2. 误区一协方差矩阵对量级太敏感不做好标准化高表达基因就会“夺权”2.1 从协方差矩阵的运行逻辑理解PCA为什么怕“尺子不统一”PCA的核心逻辑是找数据中方差最大的方向然后把样本投影到这些方向上。但在转录组数据上这个“方差最大”有一个很麻烦的前提直接基于原始表达量计算协方差矩阵时基因的表达量级差异太大会直接绑架主成分的方向。举个例子。基因A在样本中的表达量在2000到5000之间波动基因B的表达量在5到50之间波动。计算协方差时基因A贡献的数值会比基因B大几百倍。PCA在找第一主成分时会倾向于让PC1几乎平行于基因A的方向因为顺着这个方向投影样本间的总方差最大。这样一来PC1代表的其实是“基因A有多高”而不是样本之间真正最值得关注的生物学差异。转录组里这种“高表达基因压制低表达基因”的情况是很普遍的。管家基因比如ACTB、GAPDH的表达量在绝大多数组织中都很高而大量有生物学意义的调控基因转录因子、细胞因子等表达量反而很低。如果直接拿原始counts跑PCAPC1基本就是被少数几个超高表达基因占据看PCA图只能看到“谁的总表达量大”看不到样本的分组结构。很多人会说我不会直接拿原始counts我会先做log2(CPM1)。这个想法是对的log变换确实能把表达量的动态范围压下来但只做log还不够。log2(CPM1)之后基因之间的绝对差异还是存在高表达基因的均值和方差依然显著高于低表达基因。如果构不到这一步PC1依然会被高方差基因所主导只不过从“高表达基因”变成了“高表达且高变异的基因”。这两个词看着像但实际上已经是两批基因了。2.2 转录组数据里的“量纲”体现在哪CPM、TPM、FPKM和原始counts先明确一点跑PCA用的矩阵是“基因×样本”的转置形式这一点很多人会搞混。数据进来的时候行是基因、列是样本prcomp要求行是样本、列是特征所以必须转置。转置后每个基因就是一个变量每个样本就是一个观察值。而PCA对变量量级的敏感性正是在“每个基因是一个变量”这个设定下显现出来的。用什么样的表达量进入PCA直接影响结果。原始counts绝对不行——不同样本的测序深度差异就会直接造成样本间的距离偏差。CPM、TPM这类经过文库大小归一化的数值比原始counts好很多但依然没解决基因量纲差异的问题。FPKM在这件事上更麻烦它有跨样本不可比的问题所以我个人不建议在PCA这一步用FPKM。正确的路子是文库大小标准化得到CPM/TPM接着log变换再按基因做中心化和缩放。第三步在R里的实现就是prcomp(..., centerTRUE, scale.TRUE)。这一步的作用相当于把所有基因放到同一把尺子上来比较让每个基因不管表达量高低都只贡献一个标准差的方差。否则PCA依然会偏向量级大的基因。2.3 实操中我更推荐的处理顺序我最近跑的一个真实项目里最初的学生代码就是这个经典写错版本# 错误示例只做了log2变换没有按基因缩放 expr - edgeR::cpm(counts, log TRUE) # log2 CPM pca_res - prcomp(t(expr), center TRUE, scale. FALSE)跑出来的PCA图上样本不按任何已知分组分开反而按照“总表达量的高低”从右往左排。后来把scale. FALSE改成TRUE之后图立刻变了个样处理组和对照组在PC1上分开了。这个例子在当时让组里的同学印象很深——同样的数据一样是PCA只是标准化策略不同图形信息完全不同。但我还要强调直接把所有基因都丢进prcomp(scale.TRUE)也不是最优解。因为全基因组里大量基因是低表达且低方差的即使缩放到单位方差它们实际上还是在给分析加噪声。所以我的推荐流程是第一步过滤低表达基因至少在2个样本中counts大于1第二步对过滤后的表达矩阵做log2(CPM1)第三步基于过滤后的矩阵取高变基因通常取top 500-2000简单按方差排序即可第四步对选出的高变基因做prcomp(t(mat), centerTRUE, scale.TRUE)。这样既避免了高表达基因主导PC1也避免了全基因组噪声基因的稀释。我默认会取top 1000高变基因来跑PCA这个数量在实践中没有绝对标准500-2000都是常取的区间。样本量小可以取少一点样本量大或者分组信号弱可以取多一点多试几次比较一下PC1/PC2的分组效果选一个最稳定的参数即可。有一点必须提醒高变基因的选择一定要放在标准化之后。如果你在原始counts上直接算方差那么高表达基因天然方差就大你选出来的“高变基因”还是那批管家基因跟没选有什么区别。3. 误区二看见离群样本就删先别急先查它是怎么“离”出来的3.1 PCA图上的“离群”可能来自四种截然不同的原因PCA图上一旦出现一个离群样本第一反应往往是“这个样本有问题删了”。这个反应本身就是一个坑。PCA图只告诉你“这个样本在表达谱空间距离其他样本比较远”它不告诉你为什么远。根据我的经验一个离群样本可能有完全不同的四种身份技术性低质量样本RNA降解严重、测序深度不足、比对率低表达谱整体被技术噪声污染。样本标签错换或污染样本在实验过程中被弄混了或者混入了其他来源的RNA/DNA。真实的生物学极端值比如肿瘤样本中含有极高比例的间质细胞或者某个个体免疫状态特殊表达谱确实和其他个体不同。批次或实验处理混入某个样本不小心用了不同批次的试剂或者在提取/建库时经过了不同流程。这四种情况前两种通常需要剔除第三种绝对不能剔第四种应该用批次校正或建模解决而不是删除样本。PCA图本身无法区分这几种情况所以拿到离群点先做交叉验证不要急着按删除键。3.2 我判断离群样本时的交叉验证清单遇到离群样本我会按下面的顺序过一遍全部走完再下结论第一看QC指标。打开样本的测序质量报告重点看测序reads数、比对到参考基因组的比例、基因检出数表达量0的基因个数。如果离群样本的reads数只有其他样本的一半或者基因检出数明显偏低那基本可以判断是技术原因。第二看RNA质量。如果项目有RIN值RNA完整性指数的记录一定要拿出来对比。RIN值低的样本3端偏好性会更强在PCA上很容易表现为往某个方向偏移。这种偏移如果出现在PC1上还可能带动整个图旋转让其他样本看起来也不对了。第三算样本间相关性。计算每个样本与同一组内其他样本的平均Spearman相关系数。如果离群样本跟同组样本的平均相关性明显低于组内其他样本的平均相关性水平那更倾向于技术异常。第四多看几个主成分。PC1/PC2只是前两个最大的方差方向离群样本可能只是在前两个方向上显得远如果把PC3、PC4也画出来它可能就回到自己组里了。如果这个样本在PC3/PC4上不再离群那说明离群主要来自一两个主成分方向未必需要当成质量问题处理。第五回到实验记录。这一步很多人会跳过但它恰恰最关键。样本来自哪个个体有没有特殊病史取样时有没有特殊情况我之前在一个临床样本项目里碰到过一例正常人对照组里的一个样本在PCA上直接飞到了肿瘤组里大家最初怀疑是标签搞错了。后来翻了临床记录发现这个“健康对照”在取样前刚经历过一次急性炎症事件免疫相关基因整体上调表达谱自然更接近肿瘤样本。这个样本如果被删了这个临床关联信号就被彻底抹掉了。3.3 处理离群样本的决策标准和报告规范交叉验证之后如果判定是技术性离群该删还是要删但要有理有据。我的经验是在项目方法部分或者补充材料里写清楚“基于reads数、基因检出数和样本间相关性样本XXX被判定为技术离群样本并排除后续分析”这是审稿人能接受的表述。删除之后必须重新跑一遍PCA确认结果稳定。这里有个常见误区删了一个样本后因为PCA轴重新计算了原本不远的样本跑到了边缘于是又删一个再跑又有新的离群……这种“滚动删除”会把样本越删越少最后只剩下一堆看起来“很听话”的样本这不是质量控制这是数据造假。我给自己定的规则是每一轮PCA最多只处理1-2个明显离群样本删完重跑一遍如果第二轮又出现新的离群点暂缓删除回到QC指标里寻找系统性问题而不是继续删下去。对于那些被判定为“生物学离群”的样本不但不能删反而应该单独标注出来分析。它可能是你最值得关注的那一类样本。如果你做过单细胞测序就会更容易理解PCA图上单独飘在外面的细胞亚群通常就是新的亚型。放到bulk转录组里也是同样的逻辑它代表的可能是一个隐藏在组内的特殊分子亚型删除它等于堵死了一个研究角度。完整的处理代码片段大概是这样的# 基于前10个主成分计算马氏距离 scores - pca_res$x[, 1:10] mahd - mahalanobis(scores, center colMeans(scores), cov cov(scores)) # 把马氏距离合并到样本信息中 metadata$mahdist - mahd # 检查距离最大的几个样本的QC指标 flagged - metadata[order(metadata$mahdist, decreasing TRUE)[1:3], ] print(flagged[, c(sample_id, group, total_reads, gene_detected, mahdist)])用马氏距离而不是单看PC1/PC2的散点图是因为它综合考虑了多个主成分维度能更客观衡量样本在整个表达谱空间中的位置。结合QC指标对比判断依据就硬多了。4. 误区三批次效应被PCA图“看见”之后两个最容易翻车的操作4.1 批次效应在PCA图上的典型表现和影响机制批次效应是转录组项目里几乎躲不开的问题只要样本不是同一批提取RNA、同一批建库、同一批上机测序就存在批次差异的可能性。这种差异在PCA图上表现得非常直观来自同一批次的样本聚在一起批次之间沿着某个主成分被明确分开有时候这个“批次主成分”甚至比生物学分组的差异还要大直接占据PC1的位置。一旦批次效应占据了PC1生物学分组就只能体现在PC2甚至PC3以后从PC1/PC2图上看你的处理组和对照组可能完全没有分开。这不代表生物学差异不存在只是批次差异的方差太大了把生物学差异压到了后面的成分里。PCA作为一种方差最大化算法只会忠实地呈现这个事实不会替你区分“哪种方差更重要”。面对批次效应常见的翻车操作有两个一是把批次校正后的矩阵直接拿去做差异分析二是只画校正后的PCA、完全忽略校正前的情况。4.2 翻车操作一把removeBatchEffect后的矩阵直接拿去做差异分析limma::removeBatchEffect()这个函数在PCA可视化里非常常用它可以从表达矩阵中剔除批次变量的效应让PCA图更清晰地展示生物学分组。但很多人跑完这个函数看着校正后的PCA图很满意顺手就把校正后的矩阵当成最终的表达矩阵接着跑差异表达分析。这一步是非常危险的。原因在于removeBatchEffect输出的矩阵已经是“去除批次项后的残差表达值”。对这个残差矩阵做差异检验等于把样本内的随机波动也一并当作真实生物学信号来处理统计检验的自由度和误差估计都会发生偏移非常容易产生大量假阳性差异基因。说得直白一点你最后得到的差异基因列表里有很大一部分可能是噪声被当成了信号。正确的差异分析思路是用原始counts在统计模型中加入批次变量来控制。用DESeq2时就写成dds - DESeqDataSetFromMatrix(countData counts, colData metadata, design ~ batch group)用limma-voom就写成design - model.matrix(~ batch group, data metadata) v - voom(counts, design) fit - lmFit(v, design)这样批次效应是在建模时作为协变量被“吸收”掉的而不是提前从数据里“抠掉”。这两者在数学上看着差不多但在统计推断的性质上完全不同前者对后续检验标准更严格不会造成假阳性膨胀。那removeBatchEffect是不是就不能用了当然不是它的合法用途就是画图而且是画图神器。因为差异分析阶段已经通过模型控制了批次变量如果想在正文里展示PCA图来说明生物学分组是稳定存在的用removeBatchEffect校正后的矩阵画图是完全合理的。关键记住一条边界线校正矩阵只用来画图做探索原始counts拿去建模型跑差异两件事各司其职。4.3 翻车操作二只画“校正后”的PCA图不展示校正前的结构第二个翻车操作隐蔽但同样危险有人为了让PCA图“更好看”直接展示校正后的结果完全不提校正前长什么样。如果你的项目批次效应很明显而正文里的PCA图一上来就是校正后、分组干净的版本审稿人大概率会问批次效应处理了吗处理前是什么样我现在的习惯是成对呈现PCA图第一张是没有做任何批次校正的图让读者直观判断批次效应有多大第二张是removeBatchEffect校正后的图展示去掉批次影响后生物学分组是否仍然稳定。两张放在一起既透明又高效还省去了一大段被追问的麻烦。判断批次校正是否“过度”的方法也很直接校正后的图上原本应该在PC2甚至PC3才出现的生物学分组如果跳到了PC1并且校正前后PCA的前几个主成分解释方差比例没有出现断崖式的变化那这个校正就是合理且有效的。如果校正后PC1解释的方差下降得特别剧烈说明批次效应非常大这时候反而要警惕差异分析里加了批次变量后还能否检测到足够的生物学差异这需要靠后续差异基因数量来验证而不是PCA图能回答的。4.4 批次效应处理的实际判断经验在处理批次效应时还有一个很常见的认知误区认为PCA图上没有看到批次聚类就代表数据没有批次效应。PCA只能看到最大的几个方差方向如果批次效应的量级比较小它可能被压在PC5之后在PC1/PC2图上根本看不出来。所以“PCA图没有批次分离”不等于“数据里没有批次效应”。我自己的经验是只要样本来自不同批次无论PCA图上有没有清晰的批次分离差异分析模型里都默认加批次变量。这样做不会损失太多统计功效能控制则控制除非批次与分组完全共线比如所有处理组样本都在批次一、对照组样本都在批次二这种情况下批次和分组无法分离只能如实报告这个设计缺陷不能靠算法“猜”出哪个效应是真实的。如果需要判断批次效应的显著性可以跑一遍SVA包的num.sv()函数估计一下数据里有多少个隐藏的“替代变量”surrogate variables。如果这个数大于0说明在已知批次之外可能还有其他未知的系统性变异来源差异模型里可以进一步考虑加入sv项。这一步在PCA可视化中看不到但对差异分析的可靠性帮助很大。5. 落地方案一个转录组项目从表达矩阵到可信PCA图的完整流程5.1 流程总览和可复现代码骨架说了这么多还是要把完整流程串起来。以下是我跑一个典型转录组项目时的PCA标准步骤用R实现依赖edgeR、limma和ggplot2环境。第一步读入counts矩阵和样本信息表。library(edgeR) library(limma) counts - read.csv(counts_matrix.csv, row.names gene_id) metadata - read.csv(sample_info.csv, row.names sample_id)第二步过滤低表达基因。keep - rowSums(counts 2) 2 counts_filt - counts[keep, ]第三步构建log2-CPM矩阵。cpm_mat - edgeR::cpm(counts_filt, log TRUE)第四步取top 1000高变基因做转置和缩放跑PCA。gvar - apply(cpm_mat, 1, var) top_genes - names(sort(gvar, decreasing TRUE))[1:1000] mat_selected - cpm_mat[top_genes, ] pca_res - prcomp(t(mat_selected), center TRUE, scale. TRUE)第五步提取主成分得分并绘图。pca_df - data.frame( sample_id rownames(pca_res$x), PC1 pca_res$x[, 1], PC2 pca_res$x[, 2], stringsAsFactors FALSE ) pca_df - merge(pca_df, metadata, by sample_id) library(ggplot2) ggplot(pca_df, aes(PC1, PC2, color group, shape batch)) geom_point(size 3) theme_minimal()第六步结合QC指标检查离群样本处理方式参考第3节。第七步如果确认批次效应分别画校正前后的PCA图。# 校正批次仅用于可视化 corrected_mat - limma::removeBatchEffect(cpm_mat, batch metadata$batch) # 在校正后矩阵上重新跑高变基因和PCA cvar - apply(corrected_mat, 1, var) top_genes2 - names(sort(cvar, decreasing TRUE))[1:1000] pca_corrected - prcomp(t(corrected_mat[top_genes2, ]), center TRUE, scale. TRUE) # 分别画图再加上方差解释率的标注在展示PCA图时我建议在两个坐标轴标注上加上各主成分的方差解释比例比如“PC1 (23.4%)”。这一步直接告诉读者前两个主成分占了总方差的多少可以帮助判断结论的可信度。如果PC1PC2连30%都不到说明数据很复杂单靠一张二维PCA图展示全局是远远不够的需要配合t-SNE、UMAP或热图来交叉验证。5.2 一个典型实例批次、离群和生物学差异交织在一起时怎么处理我最近的一次经历72个肿瘤样本3个批次建库分成4个分子亚型。第一次画PCAPC1被批次二和批次三的差异占据分子亚型完全看不出任何聚集结构。如果我当时只看PC1/PC2图把这张图放进报告里结论就是“这个数据集的分子亚型没有表达谱层面的分离”。我没有急着下这个结论。先检查了每一批的QC指标确认没有技术离群样本后用removeBatchEffect在校正矩阵上重跑PCA。结果校正后的PC1上四个分子亚型分得清清楚楚这至少说明“分子亚型之间有表达谱差异”这件事是真实存在的只是批次效应把它压住了。最后进入差异分析时我用原始counts搭配~ batch subtype的设计矩阵跑出来的差异基因数量和我用校正矩阵直接跑出来的结果相比少了差不多30%的假阳性候选基因。这个例子可以很直观地看出“可视化用校正矩阵建模用原始counts批次变量”这套流程的意义。5.3 交付前我会反复核对的一张检查清单文章最后分享一个我在每个转录组项目交付之前都会过一遍的PCA检查清单就当是这些年踩坑换来的经验备忘确认跑PCA用的矩阵是有意义的过滤低表达基因log2-CPM或类似标准化做了基因方向中心化和缩放吗如果用了高变基因是基于标准化后的矩阵选的吗top基因数量是否记载在方法部分发现离群样本后是否对照了QC指标、相关性矩阵、临床/实验记录删除决策有没有留下书面记录删除样本后重新跑过PCA吗有没有因为“滚动删除”导致样本量过度削减是否分别画了批次校正前和校正后的PCA校正矩阵的用途是否仅限可视化差异分析使用的是原始counts加批次协变量而不是removeBatchEffect后的矩阵吗PCA图轴标签上有没有写清楚方差解释比例样本点有没有标注组别、批次等信息这些问题全部回答“是”之后我才会认为一张PCA图是有资格被放进论文或者交付报告的。PCA这件事单看算法本身并不复杂但它对输入数据极其敏感又特别容易被人为解读带偏。把标准化、离群样本和批次效应这三个环节处理明白比学会任何高级的分析算法都更能提升转录组项目结论的可靠性。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →