☰
Cibersort免疫浸润分析实战:从反卷积原理到结果解读
2026/10/3 1:10:53 网站建设 项目流程

做肿瘤免疫或者生信的人,十有八九都会被问到免疫浸润。每次组学数据拿到手,审稿人一句“请补充免疫微环境分析”,就能让不少人开始在Cibersort、ssGSEA、ESTIMATE之间反复横跳。如果你只是想快速出一张堆叠条形图,又想保证结果能被领域认可,Cibersort依然是目前最常用、也最容易被问到细节的工具之一。

这篇教程写给那些刚接触免疫浸润分析、或者已经跑通流程但对参数和结果含义半懂不懂的朋友。我会从Cibersort的数学逻辑开始讲,再逐步拆解数据怎么准备、网页版和R版怎么跑、结果怎么解读、图怎么画、报错怎么修。全程经验导向,尽量把踩过的坑和判断标准放在前面。

1. 免疫浸润分析的整体思路与Cibersort选型逻辑

1.1 解决什么问题

Bulk转录组测序或者表达芯片测到的信号,是所有细胞表达量的混合平均值。肿瘤组织里不只有肿瘤细胞,还有T细胞、B细胞、巨噬细胞、NK细胞、中性粒细胞、树突状细胞等多种免疫细胞。我们想知道这些细胞各占多少比例,就需要一种思路:从混合物中反推出各个组成部分的占比。免疫浸润分析的核心就是解决这个“成分推断”问题。

Cibersort做的事情,就是利用一组已知的免疫细胞表达特征(LM22),通过支持向量回归反卷积算法,把样本的混合表达量分解成22种免疫细胞亚群的相对比例。它不是绝对定量,而是相对丰度,所以各组样本间比较时看的是“比例变化”而非“绝对数量变化”。

1.2 为什么优先选Cibersort而不是其他算法

我经常被人问:现在有那么多免疫浸润算法,为什么还在用Cibersort?说实话,它并不是完美的,但它有几个很难替代的优点。

第一,它不需要用户自己提供每个细胞类型的标记基因列表。很多算法比如ssGSEA或者MCPcounter,需要你指定基因集,选基因集的过程会引入主观偏差。Cibersort只用固定的LM22特征矩阵,虽然灵活性差,但好处是结果可复现、可比性高。你在不同数据集之间比较时,用的是同一套标准。

第二,它有统计检验支撑。Cibersort会通过置换检验输出每个样本的p值、相关性(correlation)和均方根误差(RMSE),这让你能判断这个样本的反卷积结果是否可信。很多无监督算法根本不给这个置信度评估,审稿人问起来很难自圆其说。

第三,Cibersort的SVR算法对噪声的容忍度相对较高。反卷积问题在数学上是病态的,最小二乘法容易被异常值带偏,而支持向量回归通过设定epsilon不敏感带和正则化,对个别基因的异常表达没那么敏感。实测下来,在芯片数据和测序数据上,Cibersort的稳定性确实比普通线性回归好一些。

当然,Cibersort也有明显短板。LM22的特征矩阵是2015年基于纯化细胞亚群转录组构建的,主要覆盖先天和适应性免疫细胞,没有包含基质细胞、内皮细胞等其他微环境成分,也没有更新的细胞亚群比如Treg细分或者组织驻留记忆T细胞。所以如果你的研究非常关注某个小众亚群,可能需要辅助其他算法来交叉验证,而不是单靠Cibersort。

1.3 适用场景与不适合的场景

Cibersort适合的场景:

  • 有bulk RNA-seq表达矩阵或芯片表达矩阵,想快速评估样本间免疫细胞构成的差异;
  • 想比较两组治疗前后、突变组和野生型之间免疫细胞比例的变化;
  • 想结合临床生存数据,看某类免疫细胞浸润高低是否影响预后。

不适合的场景:

  • 单细胞测序数据,那是另外一套逻辑,直接用Seurat就行;
  • 需要绝对定量而非相对比例的场景,Cibersort输出的是相对占比;
  • 样本量很小且表达矩阵质量差的情况,因为反卷积非常依赖输入数据质量。

2. 数据准备与输入格式要求

2.1 表达矩阵格式

Cibersort的输入是一份标准的基因表达矩阵,行是基因,列是样本。基因名必须是HGNC官方symbol格式,比如TP53、CD8A,不是Ensembl ID、Entrez ID,也不是版本号带小数点的比如TP53.1。这一点经常有人搞错,上传之后解析为零匹配,问题就出在这。

表达矩阵建议用非负的连续值。芯片数据用RMA标准化之后取表达量即可,测序数据建议用TPM或者FPKM,不要用原始count数。Cibersort底层的LM22也是基于微阵列数据的表达特征构建的,虽然近期已经支持RNA-seq输入,但TMM、TPM这类长度归一化后的数据更接近芯片表达谱的分布特征。实测下来,count数据直接跑会在基因表达波动大的样本上产生很大的RMSE,p值往往不好看。

如果你手里只有count矩阵,建议先用DESeq2的varianceStabilizingTransformation或者edgeR的cpm函数做转换。但注意,不建议用log2(count+1)直接跑,因为小计数基因取对数会放大噪声,反卷积结果稳定性差。

2.2 基因名标准化

做基因名标准化,我一般用clusterProfiler的bitr函数,把Ensembl ID或Entrez ID转成SYMBOL。整个过程说复杂也复杂,说简单也就几步:

library(clusterProfiler) library(org.Hs.eg.db) bitr(rownames(expr_matrix), fromType = "ENSEMBL", toType = "SYMBOL", OrgDb = org.Hs.eg.db)

转换完以后,一个基因可能对应多个探针或者多个Ensembl ID,需要去重。建议保留表达量均值最大的那个,或者直接按表达量排序取每一基因的第一条记录。千万别简单地对重复基因取平均,效果在低表达基因上会很难看。

另外还有大小写问题。LM22矩阵中基因名全部是大写开头,HGNC标准也是首字母大写,所以最好对你的表达矩阵基因名做统一格式化。一个简单办法是使用toupper()全转大写,但全大写会丢掉部分基因名的可读性,如果原始数据本来就是小写,转大写后和LM22匹配也没问题。不过要注意某些基因全转大写后会重复,比如MARCH1和March1实际是同一个基因,需要再次去重。

2.3 样本量要求

这里想强调一个容易被忽视的点。Cibersort网页版运行虽然不限制样本量,但反卷积结果的可靠性跟样本量有直接关系。

每个样本都会输出一个p值,本质上是置换检验的结果。如果样本量太小(少于3个),后续组间差异检验几乎没有统计效力,审稿人很容易打回来。我一般建议,如果你的分组每组至少要有5个样本以上,整体样本量最好超过20例,不然即使在Cibersort里跑出来很好的结果,到后面关联分析、生存分析阶段还是会捉襟见肘。

另外,样本之间如果存在明显的批次效应,比如一部分样本来自测序平台A、一部分来自平台B,建议先做批次校正再输入Cibersort。用sva包的ComBat_seq或者limma的removeBatchEffect都可以,但要注意:如果你准备后续做差异分析,批次校正要在表达量层面做,不要在标准化后的数据上反复倒腾。

3. Cibersort运行实操:网页版与R版完整流程

3.1 网页版运行步骤

网页版Cibersort上手门槛很低,适合快速跑一两个数据集验证结论。

步骤分三步:

  1. 在Cibersort官网注册账号,提交请求后通常1-3天会收到激活邮件。注册时需要填写实验室和单位信息,一般用机构邮箱更容易通过审核。
  2. 登录后,进入“Upload”页面,上传你的表达矩阵文件。文件格式是纯文本txt,制表符分隔,第一列为基因名,第一行为样本名。
  3. 在“Run Cibersort”页面选择LM22作为特征矩阵,设置置换检验次数(permutations),一般建议设置1000次,然后提交运行。

运行结束后,会下载一个结果文件。网页版会在结果表中自动加入所有样本的相对比例、p值、相关性、RMSE等列。需要注意,网页版输出默认对每个样本将所有免疫细胞比例求和至1,所以得到的值是相对百分比。

3.2 R版本地运行

网页版虽然方便,但如果你有几十上百个样本,反复上传下载很痛苦,而且网页版有的情况下并不能保留参数细节。我更推荐在本地R环境跑,前提是你拿到了Cibersort.R函数脚本和LM22.txt矩阵。这两个文件在官网注册后可以下载,不公开发在GitHub上,所以不要试图用网上流传的旧版替代,版本不一致会直接影响结果。

跑之前先加载函数:

source("CIBERSORT.R")

然后运行:

result <- CIBERSORT("LM22.txt", "your_expression_matrix.txt", perm = 1000, QN = TRUE)

参数解释一下:

  • perm = 1000:置换检验次数,影响p值精度,1000次是建议值,再高计算量翻倍但提升有限;
  • QN = TRUE:是否启用分位数标准化。如果输入数据是芯片,建议TRUE;如果是RNA-seq的TPM,建议先试试TRUE和FALSE的结果差异。分位数标准化会强制样本数据分布一致,能消除部分技术差异,但如果样本之间存在真实的整体表达差异,QN可能会过度校正,把真实信号抹掉。CTRL和疾病组样本数差异很大时,QN=TRUE经常会让所有样本的比例分布趋同,导致组间差异不显著,这种情况建议改用QN=FALSE。

R版输出的结果是一个矩阵:

  • 列名就是22种免疫细胞(如B cells naive、T cells CD8、Macrophages M0等)
  • 还有三列指标:P-value、Correlation、RMSE
  • 行名是样本名

3.3 Permutation值怎么理解

置换检验的逻辑是这样的:随机打乱样本中的基因表达标签,重新计算反卷积,重复N次,得到一个零分布。然后看你真实计算结果在这个零分布中的位置,计算p值。p值表示“随机情况下得到这么大相关性的概率”。

实际操作中,p值大于0.05并不一定意味着结果不可用,更准确的理解是“该样本反卷积结果的置信度有限”。我见过很多人的样本p值在0.1左右,但还是给出了很明显的组间差异,这时候如何处理就要看审稿人心态了。稳妥起见,通常我们会在分析时加一个过滤步骤,筛掉p值大于0.05的样本,再跑下游分析。有些谨慎的课题组会使用p<0.01作为筛选阈值,前提是样本量足够。

我的做法是:先看整体样本p值分布,如果大多数样本p值低于0.05,就保留全部样本,但在方法部分写明基于置换检验结果;如果有一部分样本p值很高、Correlation很低、RMSE也高,建议直接剔除,并且在补充材料里列明剔除样本清单。审稿人看到你有这样的质控意识,印象分会好很多。

3.4 标准化参数详解

CIBERSORT函数里还有一个容易忽略的参数,是absolute。默认absolute = FALSE,返回的是相对比例。如果你打开absolute = TRUE,会额外输出一组绝对丰度分数,用absolute score表示。这个分数试图通过每个样本的总RNA含量来估计免疫细胞的绝对丰度,但在bulk转录组中,没有细胞总数信息,所以“绝对”也仅限于算法逻辑内的相对估计。

实际上,日常分析中用相对比例足够。如果你要做多组间比较,相对比例差异已经能反映趋势;但如果你想分析“某种免疫细胞丰度的绝对值与另一指标的相关性”,建议同时看相对比例和absolute score两组结果,如果趋势一致,说明结论稳健;如果趋势相反,就得小心是由总细胞组成变化造成的假象。

4. Cibersort结果解读与可视化绘图

4.1 结果表怎么看

跑完之后,第一件事不是画图,而是检查结果质量。我看结果表的习惯是:

  1. 先看P-value列有没有异常大的样本,一般大于0.05的建议标记出来;
  2. 再看Correlation列,这个值是LM22参考表达谱和样本表达谱的相关系数,正常样本应该在0.7以上;
  3. 然后看RMSE,越大说明反卷积拟合越差,一般小于0.2算比较理想;
  4. 最后看所有样本各细胞类型的比例总和是否接近1,如果差距较大,说明输入数据标准化有问题。

这四项指标合起来,基本能判断这个样本的反卷积结果是否可信。我见过有人完全不看这些,直接画单细胞亚群箱线图,结果所有p值都在0.4以上,审稿人一眼就看穿。

4.2 堆叠条形图展示样本免疫细胞构成

最常见的可视化是堆叠条形图,展示每个样本中各免疫细胞的比例。R里用ggplot2很快画出来:

library(ggplot2) library(reshape2) mydata <- result[, 1:22] mydata$sample <- rownames(mydata) mydata_long <- melt(mydata, id.vars = "sample") ggplot(mydata_long, aes(x = sample, y = value, fill = variable)) + geom_bar(stat = "identity", width = 0.8) + labs(y = "Relative Percentage", x = "") + theme_bw(base_size = 12) + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5))

堆叠图适合看整体构成,但缺点是当样本量很大(超过50)时横轴很拥挤。如果样本量太大,建议按分组分面展示,或者随机抽部分样本出来作为代表展示,然后在补充材料里放全量图。

4.3 分组比较与箱线图

比堆叠图更有统计意义的是分组箱线图。这一步通常回答“我们关注的几类免疫细胞在两组之间有没有显著差异”。

思路是先筛选目标细胞类型,再按分组做wilcoxon秩和检验:

# 筛选要展示的细胞类型 subset_cell <- mydata[, c("T cells CD8", "Macrophages M0", "Macrophages M1", "Macrophages M2")] # 合并分组信息 # group是一个向量,表示每个样本所在分组 plot_df <- data.frame(cell_type = rep(colnames(subset_cell), each = nrow(subset_cell)), value = unlist(subset_cell), group = rep(group, ncol(subset_cell))) # 进行Wilcox检验并添加p值 library(ggpubr) p <- ggboxplot(plot_df, x = "cell_type", y = "value", fill = "group", palette = c("#E64B35", "#4DBBD5")) + stat_compare_means(aes(group = group), label = "p.format", method = "wilcox.test")

这里有几个细节值得多说一句。免疫细胞比例数据是非负且和为定值的,本质上是成分数据,直接用t检验不太合适。Wilcoxon秩和检验是稳妥选择,如果审稿人追问,再补一个调整p值的方法说明。多组比较时,推荐用Kruskal-Wallis检验加Dunn校正。

另外,T cells CD8这种带空格的细胞名在R里作为变量名很别扭,操作时建议先统一替换:

colnames(mydata) <- gsub(" ", "_", colnames(mydata))

我每次拿到结果都会做这一步,省得后面一直加反引号。

4.4 相关性热图

除了比较组间差异,很多研究会关心免疫细胞之间的相关性。比如M0巨噬细胞和M1、M2的转换关系,或者CD8 T细胞和Treg细胞之间可能存在的拮抗关系。这种时候画一个细胞-细胞相关性热图非常直观:

library(corrplot) cor_matrix <- cor(mydata[, 1:22], method = "spearman") corrplot(cor_matrix, method = "color", type = "upper", tl.cex = 0.6, tl.col = "black", number.cex = 0.5, addCoef.col = "grey50", col = COL2("RdBu", 200))

相关性矩阵的解读要谨慎。成分数据自带负偏倚,因为总比例固定为1,一种细胞比例上升其他细胞势必下降,所以负相关可能是数学约束导致的,未必代表真实的生物学负调控。如果要在文章中下结论说“某两种细胞负相关”,建议结合共定位或者配体受体分析来佐证。

4.5 生存分析关联

如果你的数据有临床随访时间,可以做免疫细胞比例高低与预后的关联分析。通常是取某类细胞比例的中位数作为阈值,分成高浸润组和低浸润组,再用survival包的survfit画K-M曲线,用log-rank检验算p值:

library(survival) library(survminer) # 假设关注CD8 T细胞,data包含time和status列 data$cd8_group <- ifelse(mydata$T_cells_CD8 > median(mydata$T_cells_CD8), "High", "Low") fit <- survfit(Surv(time, status) ~ cd8_group, data = data) ggsurvplot(fit, data = data, pval = TRUE, risk.table = TRUE)

这里有个常见的坑:直接用中位数分组,如果样本数少,两组可能不平衡,生存曲线会在后期出现平台期变化不显著的情况。建议画图前先看看浸润比例的整体分布,如果偏态严重,可以用最佳截点(cutoff)搜索,但用最佳截点时一定要做校正,不然有数据挖掘嫌疑。

5. 常见问题与排查技巧实录

5.1 基因匹配率低,最终细胞比例全为0

这是遇到最多的问题。你在网页版跑完后发现LM22里的基因只有几十个匹配上,结果自然惨不忍睹。

排查步骤是:

  1. 检查你的表达矩阵第一列是不是基因名,而不是Ensembl ID或者其它形式的ID;
  2. 查看基因名格式,比如LM22用的是“TPM3”这种HGNC标准格式,而你的矩阵可能写成了“tpm3”或者“TPM3.2”;
  3. 检查是否用了错误的符号,比如把基因分隔符写成了逗号而不是制表符;
  4. 用intersect(rownames(exp), lm22_genes)看一下交集数量,如果低于1000,说明输入格式问题比较严重。

基因匹配率通常在50%到80%都算正常,低于30%就必须回头改数据格式,而不是硬着头皮跑下去。

5.2 P值普遍较大,结果不可信

如果跑完之后发现大部分样本的p值大于0.05,相关性低于0.6,问题大概率出在数据标准化方式上。

我建议按这个顺序排查:

  1. 如果是RNA-seq数据,确认不是count矩阵。count数据的离散程度和表达动态范围跟芯片谱差异很大,反卷积特别敏感。换成TPM或者vst转换后的矩阵再试一次;
  2. 检查是否所有样本都做了相同的归一化处理。如果在合并多个数据集,切记要先合并再标准化,而不是各自标准化完再拼接;
  3. 尝试QN参数的切换。有的数据集在QN=TRUE时表现很好,有的不行。两个设置都跑一遍,看p值分布哪个更好,就选哪个;
  4. 看看表达矩阵中是否有大量0值。Cibersort对0值很敏感,如果某个基因在所有样本中都是0,建议提前过滤掉。

实测经验是,RNA-seq数据用TPM加QN=TRUE的组合,对大多数公开数据集都能获得不错的p值表现;如果这样还是不行,重点检查批次效应。

5.3 已有免疫细胞浸润结果和病理IHC不一致

这种情况常见于RNA和蛋白水平不一致。Cibersort看的是转录组水平的细胞特征,IHC看的是特定蛋白表达,RNA表达受限、翻译调控、蛋白降解等因素都会造成不一致。

应对思路:如果结论与IHC矛盾,不要急着说Cibersort错。先确认IHC用的标记物特异性,比如CD8A这个基因对应CD8阳性T细胞,但如果IHC用的是CD8B或者CD3,两者代表亚群可能不一致。可以换用其他算法,比如ssGSEA、xCell、MCPcounter,看结果是否与Cibersort一致。多个算法交叉验证后如果仍矛盾,再考虑生物学解释上的差异。

5.4 批量运行多个数据集的效率问题

如果你要分析TCGA所有癌种的数据,每个癌种单独跑一遍会非常耗时间。一个建议是写一个循环脚本,批量处理同一格式的表达矩阵文件。但在合并多癌种之前,一定要先校正批次效应。TCGA不同癌种的样本来自不同测序中心,直接合并跑Cibersort会受到技术批次影响,导致癌种间的差异被夸大。

5.5 引用与可重复性说明

Cibersort的结果论文引用一般用Newman et al. 2015 Nature Methods那篇,但如果你用了网页版,还需要在方法部分说明具体的参数配置,包括LM22版本、permutation次数、QN设置、输入数据标准化方式。审稿人越来越看重可重复性,只写一句“CIBERSORT was used”是不够的。

我常用的方法是部分写法是:

CIBERSORT was applied to evaluate the relative proportions of 22 immune cell subtypes in each sample using the LM22 signature matrix. Permutation testing was performed 1,000 times to generate a p-value for the deconvolution result. Quantile normalization was enabled/disabled. Samples with a CIBERSORT p-value greater than 0.05 were excluded from subsequent analyses.

这样一段话,既能清晰表达分析条件,又能体现质控意识。

6. 个人实操心得与几点补充建议

跑Cibersort跑了几年下来,最大的体会是:这个工具本身很简单,门槛不在跑代码,而在数据理解和质控。很多结果不可信的案例,根源都是输入数据没洗干净。

另外,Cibersort的22种细胞类型中,有些亚群之间的共线性很强,比如M0和M1/M2巨噬细胞之间、T cells follicular helper和T cells regulatory之间,经常会出现估算比例高度波动。如果某个亚群在所有样本中几乎都是0,可能不是表达矩阵的问题,而是参考特征在该数据集中区分度不足。分析时不用把22种细胞全展示出来,选取关键亚群聚焦讨论就好。

最后再分享一个习惯:我每次跑完Cibersort,都会顺手把p值、相关性、RMSE、总比例列保存成一个独立的质控文件,文件名带日期和参数摘要。这样返工的时候一眼就能判断哪一步出了问题。这个习惯帮你省下的时间,远超多花的那一分钟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询