免疫浸润分析实战:ssGSEA原理与R实现指南

发布时间:2026/9/1 17:01:20
免疫浸润分析实战:ssGSEA原理与R实现指南 简介本资源是面向生物信息学零基础学习者的转录组下游分析实战配套材料聚焦免疫微环境解析中的ssGSEA算法应用解决科研人员在肿瘤免疫浸润评估中缺乏可复现、易上手分析流程的痛点。压缩包共8个文件4个CSV输入数据、1个R脚本、1个PDF说明、1个PNG结果图、1个TXT基因集文件总大小72.61MB涵盖从FPKM表达矩阵、分组信息、免疫相关基因集到差异分析与可视化结果的完整链条。R脚本r.immunoinfiltration_ssGSEA.R已通过实测支持一键运行兼顾代码学习与快速出图需求配套教程详细拆解ssGSEA原理、参数设置、结果解读及Wilcoxon检验逻辑帮助用户理解每一步生物学意义。目前已有218人下载学习资源结构清晰、注释充分特别适合刚接触R语言与转录组分析的研究生及临床科研工作者开展免疫相关课题的自主实践。1. 免疫浸润分析的整体思路与ssGSEA的定位1.1 审稿人为什么盯上免疫浸润做过转录组测序RNA-seq的人应该都有体会测序本身只是第一步真正折磨人的是下游分析。无论是从TCGA下载的公共数据还是课题组自己送测的样本跑完差异表达、富集分析之后往往会被审稿人追问一句肿瘤微环境中免疫细胞浸润情况如何这时候就绕不开免疫浸润分析了。肿瘤微环境里有T细胞、B细胞、巨噬细胞、NK细胞等多种免疫细胞亚群它们之间的比例和状态与患者的预后、免疫治疗响应密切相关。而转录组测序技术可全局筛选基因转录水平的差异变化是挖掘转录因子下游靶基因、解析生物通路变化的核心手段其中就包括通过特征基因来反推样本中有哪些免疫细胞、每种细胞相对丰度是多少。目前主流的免疫浸润推断方法有好几条技术路线基于标记基因的ssGSEA、基于反卷积的CIBERSORT、基于其他打分策略的xCell、MCPcounter等。每种方法原理不同、数据要求不同、结果解释方式也不同。如果你是零基础入门我建议第一站就选ssGSEA原因后面会展开讲。1.2 ssGSEA在这套方案里处于什么位置先看一下整个转录组下游分析的常见流程拿到表达矩阵后先做质量控制、过滤低表达基因然后做差异表达分析得到上调/下调基因列表接着做GO/KEGG功能富集、GSEA通路富集再往后就是免疫浸润分析把表达矩阵里的基因信号转化为每个样本中有多少种免疫细胞在活跃的量化得分。ssGSEAsingle-sample Gene Set Enrichment Analysis单样本基因集富集分析就是在这个环节登场的。它是GSEA的变体核心思想是给定一个免疫细胞类型的标记基因集判断这个基因集的成员在某个样本的表达矩阵中是集中在高表达区域还是低表达区域。如果是集中在高表达区域就说明该样本中这类细胞的富集程度高。ssGSEA和传统GSEA的关键区别在于GSEA需要两组样本做比较输出的是两条基因列表之间的差异富集而ssGSEA是每个样本独立算不需要分组标签。这就意味着你用一批肿瘤样本每个样本都能得到一套免疫细胞浸润分数方便做后续的聚类、分组、生存分析、相关分析。我自己在做项目时通常把ssGSEA作为免疫浸润分析的主力方法因为它的结果稳健、参数少、对输入数据格式要求相对宽松而且相关的R包接口成熟出图也方便。一个完整流程跑下来半小时内能从表达矩阵得到免疫浸润热图和相关统计结果。2. ssGSEA核心原理深入拆解2.1 一句话理解富集分数是怎么算出来的很多教程一上来就甩公式容易把人吓跑。先打个比方假设你有一堆写着不同基因的卡片每张卡片上标着这个基因在某个样本里的表达量从高到低排序。现在你手里有一份免疫细胞特征清单上面列了某个T细胞亚群相关的几十个基因。你要做的就是从这堆卡片中把这些基因抽出来看它们在整副牌中的位置——是普遍排在前面高表达还是散落在各处或者全挤在最后面。如果清单里的基因大量出现在卡片堆的顶端说明这群基因在样本里整体表达水平很高我们就认为这类免疫细胞在这个样本中富集了。ssGSEA把这种直观判断变成了一个可比较的数值叫富集分数enrichment scoreES。具体到计算过程ssGSEA会先对单个样本中所有基因的表达量进行排序把表达量转化为排名。然后按照这个排名对基因集内和基因集外的所有基因分别做经验累积分布函数empirical cumulative distribution functionECDF的差值积分。当基因集内的基因普遍排在前面时基因集内的ECDF会迅速上升与基因集外的ECDF拉开差距这个差距的积分就是富集分数。2.2 排序、ECDF与权重公式背后的直觉ssGSEA的计算细节可以拆成三个关键词排序、ECDF、权重。第一步排序。对每一个样本把表达量按从大到小排列用排列位置代替原始表达量。这一步的巧妙之处在于它消除了不同样本间测序深度、文库大小、批次效应带来的绝对量差异因为只看相对排名不看具体数值。第二步是计算ECDF。对基因集内的基因分布和基因集外的基因分布分别求累积分布然后在每个排名位置上做差。差值越大代表基因集内基因越倾向于高表达。第三步是权重。ssGSEA借鉴了GSEA的加权策略会利用基因表达量或排名的绝对值大小给基因赋权。具体来说用基因表达量相对于所有基因的某个幂次作为权重因子。默认情况下这个幂次可能为0.25或0.75由R包实现决定。这意味着高表达基因在排序中的位置对富集分数的影响更大符合生物学直觉——一个基因表达特别高时它的信号理应更有代表性。最终富集分数会归一化到某个数值范围。不同实现方法归一化公式有差异但通常结果的绝对值本身不是重点重点在于样本间、分组间的相对比较。2.3 基因集选择免疫细胞特征基因从哪来ssGSEA结果的质量一半取决于算法另一半取决于你喂给它的基因集。免疫细胞类型定义是否准确、标记基因是否特异、能否覆盖主要细胞亚群这些都直接影响结果是否可信。市面上常用的免疫细胞基因集包括Charoentong等人2017年在Cell Reports上发布的42种免疫细胞类型基因集覆盖了从CD8 T细胞、Th1/Th2、调节性T细胞到中性粒细胞、肥大细胞等一系列亚群Bindea等人2013年整理的免疫细胞与免疫通路基因集以及GSVA包中自带的C7免疫学基因集来自MSigDB。实操中我比较推荐Charoentong的版本因为它的覆盖度比较好分类细致而且论文里给出了明确的cell type和marker基因列表便于追踪来源。拿到基因集后通常需要整理成R里的list格式每个list元素对应一种免疫细胞类型元素内容是基因名向量。基因名格式建议与表达矩阵保持一致否则会出现匹配不到的情况。如果你用MSigDB的C7集合需要格外注意C7里的基因集很多是某个细胞状态如T细胞在不同刺激条件下的差异表达基因的产物和免疫细胞类型特征基因是两回事。用C7做ssGSEA时应该挑选其中标注了特定细胞亚群且定义清晰的基因集而不是整批导入。2.4 ssGSEA与其他免疫浸润工具的核心差异把ssGSEA和CIBERSORT、xCell、MCPcounter放在一起看有一个关键维度区分它们是否需要特征矩阵以及如何定义特征。CIBERSORT基于线性支持向量回归反卷积算法需要一套已知的免疫细胞转录组特征矩阵LM22即22种免疫细胞亚群的标记基因表达谱然后用算法从混合表达谱中反推出各细胞亚群的比例。CIBERSORT的输出是相对百分比因此更接近细胞比例的量化但它对输入数据格式要求较严需要基因符号匹配到特征矩阵的基因集而且LM22主要基于芯片数据构建改用到RNA-seq时要做处理。ssGSEA走的是富集打分路线不需要反卷积矩阵输入就是表达矩阵和基因集。输出是每个样本在每个免疫细胞类型上的打分这个分数是相对丰度指标可以用于分组比较。由于ssGSEA只依赖基因集内的成员基因在整体表达排序中的位置它对单个标记基因的表达波动不太敏感结果相对稳定。如果说CIBERSORT是在做解剖试图计算每个细胞类型的占比那ssGSEA就是在做评估给每个细胞类型打一个相对分数两者解释口径不同。xCell和MCPcounter也各有特点xCell用ssGSEA做基线再校正输出细胞类型评分MCPcounter主要关注肿瘤微环境中的免疫细胞和基质细胞丰度。实际选型时如果你的目标是比较两组肿瘤样本之间免疫浸润差异ssGSEA完全够用如果要做免疫细胞比例的绝对值估计考虑CIBERSORT如果想把免疫和基质细胞状态一起评估MCPcounter是补充选项。3. 实操准备环境配置与数据整理3.1 从零搭建R环境ssGSEA最常用的工具是Bioconductor上的GSVA包它把ssGSEA算法封装成了现成的函数。跑这套分析需要R和RStudioR版本建议更新到4.2以上否则部分依赖包会报兼容性问题。GSVA包依赖BiocParallel、S4Vectors等底层包安装时如果网速不好可能中途失败。我习惯在安装前先执行一条指令把BiocManager装好if (!require(BiocManager, quietly TRUE)) install.packages(BiocManager) BiocManager::install(GSVA)装完后用library(GSVA)验证一下。除了GSVA这个流程里还会用到读表格的data.table、出热图的pheatmap、画箱线图的ggplot2也一并装好install.packages(data.table) install.packages(pheatmap) install.packages(ggplot2)3.2 表达矩阵格式这是最容易翻车的地方ssGSEA的输入格式要求并不复杂但很多新手在这里栽跟头。核心要求有两条第一表达矩阵必须是数值型矩阵或数据框行名是基因符号列名是样本名第二基因符号类型要统一不要有的行名是TP53、有的行名是ENSG00000141510这种混搭。如果你的原始数据是Ensembl ID建议先做注释转换把Ensembl ID映射为基因符号。这个步骤可以用clusterProfiler包的bitr函数完成library(clusterProfiler) library(org.Hs.eg.db) convert - bitr(rownames(expr_matrix), fromType ENSEMBL, toType SYMBOL, OrgDb org.Hs.eg.db) expr_matrix - expr_matrix[convert$ENSEMBL, ] rownames(expr_matrix) - convert$SYMBOL转换之后还要处理重复基因名。同一个基因符号可能对应多个探针或多条转录本需要用聚合、取均值或取最大值的方式去重。我的默认操作是取每个基因在所有样本中的平均表达量然后按基因名去重expr_matrix - as.data.frame(expr_matrix) expr_matrix$gene - rownames(expr_matrix) expr_matrix - aggregate(. ~ gene, data expr_matrix, mean) rownames(expr_matrix) - expr_matrix$gene expr_matrix - expr_matrix[, -1]一个比较容易被忽略的点是表达矩阵里不要有NA值。GSVA在内部计算排序和ECDF时遇到NA会直接报错或者返回一堆无法解释的结果。如果你的数据里有NA先做填充或删除处理。另外如果表达矩阵的数值范围特别大比如TPM几十万那种建议先做log2(x1)变换让数据分布更集中ssGSEA结果也更稳定。3.3 免疫细胞基因集整理与质量检查有了表达矩阵接下来要准备免疫细胞基因集。如果使用Charoentong的基因集可以把每个细胞类型的标记基因整理成一个list结构如下immune_genesets - list( CD8 T cells c(CD8A, CD8B, GZMA, GZMB, PRF1, IFNG), Treg c(FOXP3, IL2RA, CTLA4, IKZF2), Macrophages c(CD68, CD163, CSF1R, MRC1), NK cells c(NKG7, KLRD1, KLRK1, NCR1) )基因集整理好之后强烈建议先做一步质量检查看看每个基因集和表达矩阵的基因交集有多少。如果一个细胞类型的标记基因在表达矩阵中只匹配到两三个计算出来的富集分数就没什么参考价值了。检查代码如下overlap - sapply(immune_genesets, function(genes) { length(intersect(genes, rownames(expr_matrix))) })如果某个基因集匹配到的基因数不到5个建议换基因集版本或者检查基因符号格式是否一致。这里有个小坑不同来源的基因集可能使用基因别名如CD8A在某些版本中写作OKT3-T细胞抗原而不是标准的HGNC符号导致匹配率骤降。遇到这种情况可以用AnnotationDbi包的mapIds函数做别名标准化。4. ssGSEA核心计算与结果生成4.1 GSVA函数一行代码跑完ssGSEAGSVA包把ssGSEA封装得非常简洁核心就是gsva函数。在最新版本的GSVA中调用方式如下library(GSVA) gsva_res - gsva( expr as.matrix(expr_matrix), gset.idx.list immune_genesets, method ssgsea, kcdf Gaussian, verbose TRUE )expr是表达矩阵gset.idx.list是基因集listmethodssgsea表示使用ssGSEA算法。输出的gsva_res是一个矩阵行名是免疫细胞类型列名是样本名矩阵中的每个数值代表该样本在该免疫细胞类型上的富集分数。kcdf参数值得单独说明。GSVA支持两种核密度估计方式一种是Gaussian高斯适用于连续分布的数据比如log后的TPM、FPKM另一种是Poisson泊松适用于整数型的原始count数据。我一般直接传Gaussian因为上游通常已经处理过表达量很少把原始count喂给GSVA。4.2 输出结果的结构与解读口径gsva函数跑完后先看一下结果维度dim(gsva_res)行数等于基因集数量列数等于样本数。数值是富集分数可能是正也可能是负可能有小数可能有负值。很多新手拿到这个矩阵后会问分数是不是越高代表细胞越多答案是分数是相对富集程度不是绝对细胞比例数值越高表示该细胞类型的特征基因在样本中的整体表达排名越靠前可以理解为浸润程度相对更高。解释结果时要注意一个陷阱不能跨细胞类型比较。比如样本A的CD8 T细胞分数是0.5样本B的巨噬细胞分数是0.7不能说样本B巨噬细胞比样本A CD8 T细胞多因为不同基因集的规模、基因表达分布特征不同分数刻度不可直接横向比较。只能做同一种细胞类型在不同样本之间的比较比如样本A的CD8 T细胞分数高于样本B说明样本A的CD8 T细胞浸润程度更高。另外ssGSEA分数也可以直接变成肿瘤分型或分组的一个变量。比如你有一批样本按高/低风险分组可以比较两组之间CD8 T细胞浸润分数是否有显著差异用wilcoxon检验处理wilcox.test(gsva_res[CD8 T cells, group_high], gsva_res[CD8 T cells, group_low])4.3 数据标准化与计算长耗时问题大样本量会使gsva函数跑得非常慢。我有一次处理TCGA的几百个肿瘤样本、几十个基因组集跑了将近二十分钟。这个过程中R的控制台会刷很多进度信息看起来像卡住了其实还在跑。如果你需要反复调整基因集、反复跑ssGSEA建议先存中间结果用saveRDS把gsva_res保存下来saveRDS(gsva_res, ssgsea_result.rds)下次用时loadRDS一键读取不用重新计算。如果运行时间实在长到无法接受可以考虑并行计算。GSVA支持BiocParallel后端设置并行后能明显提速library(BiocParallel) param - MulticoreParam(workers 4) gsva_res - gsva(expr, immune_genesets, method ssgsea, kcdf Gaussian, BPPARAM param)并行核心数不要贪多我一般设成CPU物理核心数的一半否则容易内存爆炸。4.4 与差异表达分析结果的联动ssGSEA算出的富集分数并不仅是画个热图就完了它还可以和差异表达分析联动用于解释某个通路或某个关键基因的潜在作用机制。比如你做完差异表达分析发现基因X在高风险组中显著上调这时可以用ssGSEA分数做一个简单相关分析看基因X的表达量与哪些免疫细胞浸润分数显著正相关或负相关cor_test - apply(gsva_res, 1, function(x) { cor.test(x, expr_matrix[geneX, ], method spearman) })这样可以补充一个结论基因X可能通过促进调节性T细胞浸润来形成免疫抑制微环境。这种分析模式在肿瘤免疫相关论文中非常常见。需要注意的是这里的相关分析本质上是基于同一批转录组数据计算的内部一致性描述不算独立验证但作为机制探索手段是够用的。5. 结果可视化与分组比较实战5.1 用pheatmap画出免疫浸润全景图拿到gsva_res矩阵后最直观的可视化方式就是热图。行列分别对应免疫细胞类型和样本颜色深浅代表富集分数高低。pheatmap的调用代码如下library(pheatmap) pheatmap(gsva_res, scale row, clustering_method ward.D2, show_colnames FALSE, color colorRampPalette(c(#4169E1, white, #DC143C))(100))scale row这一步很关键它会对每一行做z-score标准化让不同细胞类型的分数在同一个量纲下展示方便观察每个样本的免疫浸润模式。如果不做scale某些丰度整体偏高的细胞类型颜色会很深把其他细胞类型的差异盖住。聚类方法我用ward.D2比较多得到的分类边界比较清晰。热图可以配合样本分组标注栏一起看比如肿瘤分期、分子亚型、风险分组等。pheatmap支持annotation_col参数传入样本分组数据框这样能直观看出不同分组之间免疫浸润模式的差异。还有一个常用的可视化是免疫细胞相关性热图计算不同免疫细胞类型之间富集分数的Spearman相关系数然后做聚类热图。这个图能揭示哪些免疫细胞倾向于共同浸润哪些互相排斥。5.2 分组比较箱线图高表达组vs低表达组热图只能看出整体模式真要比较两组之间某个免疫细胞类型是否有显著差异还得落到统计检验和箱线图上。假设根据某个基因的表达量中位数把样本分成高表达组和低表达组group - ifelse(expr_matrix[geneX, ] median(expr_matrix[geneX, ]), High, Low)然后提取CD8 T细胞的ssGSEA分数用ggplot2画箱线图library(ggplot2) plot_df - data.frame( group group, score as.numeric(gsva_res[CD8 T cells, ]) ) ggplot(plot_df, aes(x group, y score, fill group)) geom_boxplot() geom_jitter(width 0.2, size 0.8) theme_classic() labs(x GeneX expression, y CD8 T cell infiltration score)加上散点geom_jitter可以同时展示每个样本的分布。p值可以用wilcox.test计算后标在图上也可以用ggpubr包的stat_compare_means自动添加。这种做法比较省事适合快速出图。如果有多个免疫细胞类型需要批量比较可以用循环逐一做显著性检验把p值整理成表配合热图做标记。我自己经常会做一张所有细胞类型在两组间的p值热力表一眼就能看出哪些细胞类型在两组间存在显著差异。5.3 免疫浸润分数与预后分析的衔接ssGSEA分数更高级的用法是与生存分析衔接。比如你想看看CD8 T细胞浸润高低是否影响总生存期OS可以把所有样本按CD8 T细胞分数的中位数分成高浸润组和低浸润组然后用survival包做KM曲线和log-rank检验library(survival) library(survminer) score_group - ifelse(as.numeric(gsva_res[CD8 T cells, ]) median(as.numeric(gsva_res[CD8 T cells, ])), High, Low) surv_data - data.frame( time survival_time, status survival_status, group score_group ) fit - survfit(Surv(time, status) ~ group, data surv_data) ggsurvplot(fit, pval TRUE, risk.table TRUE)做这类分析时有一个需要注意的地方ssGSEA是基于表达矩阵推断的浸润分数表达矩阵和临床生存数据必须来自同一批样本并且样本名要一一对应不能错位。我用这个流程做过不少数据挖掘的分析临床数据匹配是最容易出错的环节建议先用intersect把样本名对齐再继续。6. 常见问题与排查技巧实录6.1 gsva函数报错一览GSVA的报错信息对新手不太友好很多报错只看英文看不出原因。这里整理一份我实际遇到过的报错速查表报错现象最常见原因处理方法Error in rowSums 或 x must be numeric表达矩阵不是数值型可能混入了ID列或字符列清理矩阵确保所有列都是数值用as.matrix转换gene set has no genes in the expression matrix基因集里所有基因名都和表达矩阵对不上检查基因符号格式统一为HGNC符号kcdf must be Gaussian or Poissonkcdf参数传错填Gaussian或Poisson运行卡住长时间无响应大样本量矩阵运算耗时启用并行计算或减少基因集数量结果中有大量NaN表达矩阵存在NA或非有限值检查并填充/移除NA值这些报错里最典型的其实是第一种。很多时候你从Excel复制数据进来时行名会变成第一列或者某个样本列里混入了文本注释导致gsva无法计算。建议在跑之前先用typeof(expr_matrix)检查数据类型用str(expr_matrix)检查结构。6.2 基因匹配率低怎么办基因集和表达矩阵匹配率低是免疫浸润分析里最常见也最坑的问题。我遇到过一种情况用GTEx数据做表达矩阵基因名是Ensembl ID带版本号ENSG00000210194.1这种而基因集是纯基因符号结果匹配率几乎为零但R不会报错只是所有分数都趋近于0。这时候需要先剥掉版本号再转成基因符号rownames(expr_matrix) - sub(\\..*, , rownames(expr_matrix))另外如果表达矩阵来自不同平台比如芯片和测序合并基因名的版本差异和别名问题会更严重建议用uniprotdb或者AnnotationDbi做一次统一转换。检查匹配率时不要只看是否有交集还要计算交集基因占基因集的比例。我一般要求至少60%以上的标记基因在表达矩阵中能找到低于这个阈值就会考虑换基因集或换数据源。6.3 组间差异不显著的排查思路跑完ssGSEA发现所有免疫细胞在两组间都没有显著差异先别急着改数据大概率是前端的问题。第一检查分组定义是否合理比如用某个基因的中位数分组时如果该基因在样本中表达差异不大分组本身就造不成免疫浸润差异。第二检查样本量如果高/低组各只有不到10个样本统计功效自然不够很难得到显著p值。第三检查基因集是否过于粗糙有的免疫细胞类型存在高度异质性比如巨噬细胞有M1/M2两种状态笼统的Macrophages基因集可能掩盖了亚群差异改用M1 Macrophages和M2 Macrophages分开跑会更有洞察力。如果确实想确认ssGSEA流程本身有没有问题可以做一个正对照用一组已知免疫高浸润和免疫低浸润的样本跑流程看结果是否拉开差距。或者把同一样本重复跑两次看结果是否完全一致。稳定性和可重复性是流程可靠的基本要求。6.4 结果解释中的常见误区解释结果时有个高频误区是把ssGSEA分数直接等同于细胞比例。审稿人或者导师如果对研究方法熟悉看到你把ssGSEA分数说成percentage是会质疑的。正确措辞是相对浸润水平或富集分数描述趋势而不是绝对含量。另一个误区是忽略批次效应。如果对比的样本来自两个不同测序批次批次效应可能显著影响基因表达进而干扰免疫浸润评分。最好在分析前对表达矩阵做批次校正比如用ComBat-seq或limma的removeBatchEffect再做ssGSEA。虽然不是所有情况都需要校正但一旦涉及跨平台、跨批次比较这一步不能省。还有一个容易被忽视的点ssGSEA是对整体转录组表达模式的综合推断肿瘤样本里混杂了肿瘤细胞、免疫细胞、基质细胞等多种成分ssGSEA反映的是混合信号并非肿瘤区域特异性免疫浸润。如果研究想要关注的是肿瘤核心区域的免疫状态建议结合病理切片的多重免疫组化或单细胞空间转录组数据做验证转录组层面的推断只能作为群体层面的线索。7. 个人实操经验分享做免疫浸润分析这几年我越来越觉得ssGSEA的价值不在于它有多高级而在于它把复杂的肿瘤免疫微环境问题简化成了一个可量化、可比较的步骤。尤其是在课题早期探索阶段先用ssGSEA跑一遍所有样本看看免疫浸润的整体格局是什么样的哪些细胞类型在分组间有差异这些差异和已有的差异表达基因是否有联动能帮你快速确定后续要重点探究的方向。我自己在项目里通常不会只用ssGSEA一种方法出结论而是把ssGSEA和MCPcounter或xCell的结果做交叉验证如果两种算法独立推断出相近的趋势结论的可信度会提升不少。对于需要发表的研究我还会补充一组CIBERSORT的结果用多种算法互相印证这已经是肿瘤免疫方向审稿人的普遍期待。最后想再提醒一个实用的小细节README那种记录文件一定要留好。等你跑完整个项目写下每个基因集的来源版本、表达矩阵的处理步骤、gsva函数的参数设置两个月后再回来看才不会一头雾水。数据分析和实验一样可复现性永远是第一位的。本文还有配套的精品资源点击获取