ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

单细胞聚类后如何注释细胞类型?从UMAP体检到Marker验证全流程

单细胞聚类后如何注释细胞类型?从UMAP体检到Marker验证全流程

如果把单细胞分析流程比作做饭,前面三讲相当于处理食材和掌握火候——从读入表达矩阵、过滤低质量细胞,到归一化降维、UMAP聚类,到这里你手里已经有一盘炒好的菜了。但这盘菜里到底有什么,还没人能说清。第4讲要解决的就是这个问题:给每一个cluster一个明确的细胞身份,也就是我们常说的“注释”。这一步决定了后续所有分析的方向,差异表达、富集分析、轨迹推断、细胞通讯,全部建立在一个前提之上——你标注的“T细胞”到底是不是T细胞。这篇文章适合刚跑到聚类、准备往下走注释的同学,也适合注释完了但对结果心里没底的人。我会按自己实际跑流程的顺序来写:先体检,再手工标注,再上自动化工具,最后交叉验证,以及注释结果怎么交接到下游分析。

1. 拿到聚类结果先体检:别让UMAP骗了你

1.1 读图的正确顺序不是看颜色,而是看形态

很多同学拿到UMAP图,第一反应是“图上有几坨就分几群”,然后盯着颜色找对应关系。这个习惯得改。真正的读图顺序应该先看形态,再看颜色。簇与簇之间的边界是否干净?有没有一团细胞边缘拖出细长的尾巴,连着另一个簇?有没有某个小簇孤零零地悬在空白区域,像卫星一样飘在外面?

尾巴和卫星簇通常意味着三种情况之一:一是这个簇的细胞质量差,混进了双细胞或高线粒体的受损细胞;二是这个簇其实是某个连续分化过程的中间状态,比如上皮细胞正在向间质细胞转变,UMAP会把渐变的过程拉成一条线;三是批次效应的残留,同一个样本或同一个建库批次的微小差异被聚类算法放大成了独立小群。

这三种情况的后续处理完全不同。质量差导致的卫星簇,应该回到QC阶段重新过滤;真实的过渡状态是生物学信号,不应该硬拆;批次效应则需要用整合方法修正。如果跳过形态观察直接注释,很容易把质量问题当成真实结构,后面越做越偏。

1.2 簇的数量:分辨率不是越高越好

Seurat的FindClusters里有个resolution参数,从0.1到1.2甚至更高,直接决定你最后拿到多少个cluster。不少新手迷信高分辨率,觉得分得越细越厉害,结果得到三四十个群,注释起来头晕眼花。实际上,分辨率调高之后,很多新增的簇只是把一个大细胞群按照表达量的细微波动拆成了几份,并不是真正的细胞类型差异。

我自己的习惯是分三步走。第一步,先跑一个低分辨率(0.1到0.3),目标是先把免疫和非免疫、上皮和间质这种大类分开;第二步,在某个大类内部如果存在连续谱,比如髓系里的单核、巨噬、树突细胞,就取子集出来重新归一化聚类,用0.5到0.8的中等分辨率;第三步,每次调高分辨率之前,先确认新增的簇有没有独立的marker支持,没有就回退。

这里有个很实用的检查方法:聚类完成后,对每个簇计算平均表达谱,然后做一张cluster之间的相关性热图。如果两个簇的相关性高达0.9以上,说明它们本质非常接近,要么合并,要么说明分辨率确实调过头了。这一步能帮你省掉大量“注释了三十个类型,最后发现一半是同一个类型被掰开”的尴尬。

1.3 簇级体检清单:每个簇都要过三关

注释之前,我会给每个簇建立一份小档案,记录三个字段:细胞数、nFeature_RNA中位数、percent.mt中位数,三个都要过一遍。细胞数少于50的簇,统计上不可靠,注释完之后写文章也没底气,优先怀疑是双细胞或者细胞碎片;nFeature_RNA中位数明显低于整体水平的簇,意味着基因检出能力差,大多是低质量细胞;percent.mt显著偏高的簇,一般是受损细胞,但如果这个簇同时又高表达某类组织的特异性marker,也有可能是代谢旺盛的特殊细胞,需要结合上下文来判断。

用VlnPlot把这三个指标按cluster分面画出来,整个数据集的健康度一目了然:

VlnPlot(obj, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), group.by = "seurat_clusters", pt.size = 0)

这一环不是可选项。跳过体检直接注释,等下游分析做到一半,发现某个“细胞类型”其实是一堆死细胞,返工代价极高。尤其是短期内有交付压力的项目,这种返工最磨人。

提示:如果某个簇的percent.mt中位数超过20%,同时又检测不到任何已知细胞类型的marker,直接把它标记为候选低质量簇,别急着给身份。

2. 手工注释的核心逻辑:marker表达才是最终裁判

2.1 先背熟一组“保底marker”

自动化工具再方便,理解marker永远不过时。你不需要把几百个已知marker全背下来,但下面这组经典marker建议做到随手能写出来。它们是在正常组织里特异性相对较强的“保底选项”。

细胞大类常用marker备注
T细胞CD3D, CD3ECD4与CD8还需继续分亚群
NK细胞NKG7, GNLY, KLRD1与T细胞在UMAP上经常混在一起
B细胞CD79A, MS4A1浆细胞会转高表达SDC1和免疫球蛋白基因
髓系细胞LYZ, CD68, CD14单核/巨噬/树突需继续拆分
树突细胞CLEC9A, FCER1A, CD1C浆样树突可看LILRA4、IL3RA
上皮细胞EPCAM, KRT8, KRT18实体组织中最常见的非免疫细胞
内皮细胞PECAM1, VWF, CLDN5血管来源的标志
成纤维/间质DCN, COL1A1, LUM肿瘤场景里需要留意CAF亚型
增殖细胞MKI67, TOP2A这是细胞状态,不是细胞类型

这张表只是“字典”,不是“判决书”。很多marker在别的细胞里也会低量表达,比如部分成纤维细胞也能检出少量的PECAM1,不能因为这个就急着标内皮。正确定义一个细胞,需要多个marker同时指向同一个方向,而不是单凭一个基因。

2.2 DotPlot和FeaturePlot的读图要点

手工注释最常用的两张图各有所长。DotPlot用点的大小代表表达该基因的细胞比例,颜色深浅代表平均表达量,它的核心价值在于同一张图里横向对比多个cluster和多个marker,防止被个别高表达细胞带偏。实际操作时,我会把候选marker一次性丢进去:

marker_panel <- c("CD3D", "CD3E", "NKG7", "GNLY", "CD79A", "MS4A1", "LYZ", "CD68", "EPCAM", "PECAM1", "DCN", "COL1A1") DotPlot(obj, features = marker_panel) + RotatedAxis()

FeaturePlot则是把marker表达映射到UMAP坐标上,用来看空间分布。它最直观的价值是回答一个关键问题:这个marker到底把整个cluster都点亮了,还是只有零星几个细胞在表达?如果是后者,这个marker对该簇的判型作用就要打折扣。

读图有一个硬性原则:至少两个独立marker同时高表达,才能确认一个簇的身份。单个marker的点亮,尤其是那种只有几个散点发亮的图,大概率是假阳性或者背景噪音。

2.3 分层拆解:从大类到亚类

新手最常见的错误是想一步到位,直接在全局图上注释出“CD8阳性T细胞耗竭亚群”这种极细的标签。实际流程要像剥洋葱一样,一层层来。第一步,把所有簇分成免疫与非免疫两大阵营,在实体组织里,非免疫阵营还要再区分上皮、内皮和间质;第二步,在免疫阵营里区分T、NK、B、髓系,在非免疫阵营里确认上皮、内皮、成纤维各自对应的簇;第三步,才把感兴趣的簇取子集,重新降维聚类,注释亚群。

举个例子,T和NK细胞在UMAP上通常紧挨着,甚至一开始就是混在一起的。要细分CD4阳性T、CD8阳性T、调节性T和NK亚型,必须先subset出这个大群,重新做PCA和聚类,再看CD4、CD8A的表达分布。直接在全局分辨率下硬分,边界处的大量细胞会被标错。

sub_t <- subset(obj, idents = c("T_cell", "NK_cell")) sub_t <- NormalizeData(sub_t) %>% FindVariableFeatures() %>% ScaleData() %>% RunPCA() %>% FindNeighbors(dim = 1:20) %>% FindClusters(resolution = 0.5) %>% RunUMAP(dim = 1:20)

这样每一层注释产生的分歧,都会在下一层更高的分辨率下被重新审视,而不是在全局数据上一次性标完。分层策略看着繁琐,却是保证注释质量最扎实的一条路。

3. SingleR等自动化注释:省时但有前提

3.1 为什么要引入自动化工具,以及它的边界

手工注释有一个绕不开的问题:主观。同一批数据换两个人来标,结果可能差异不小。自动化注释的价值在于结果可复现、速度快、能提供一个“第二意见”,尤其适合细胞数量多、类型杂的大数据集。

但也必须说清楚它的边界。自动化注释的本质,是拿参考数据中的已知细胞类型做基因表达比对,然后给每个细胞或每个簇打分,取分数最高的类型。所以它的准确度上限,完全取决于参考数据和你的数据有多匹配。参考数据的组织来源要一致,细胞类型命名体系要兼容,测序平台的差异也要考虑进去。用小鼠胰腺的数据去注释人的外周血,结果必然离谱;用健康组织的数据去注释肿瘤样本,免疫细胞问题不大,但恶性细胞通常标不出来,因为肿瘤细胞的表达谱偏离正常细胞太多,任何参考数据都匹配不上。这种情况反而说明你的样本里确实存在恶性细胞群,是重要发现,不是注释失败。

3.2 主流工具怎么选

工具原理输入要求优点局限
SingleR参考数据集marker打分表达矩阵 + 参考数据轻量、快、结果可解释依赖参考数据质量,不同参考结果差异大
CellTypist训练好的逻辑回归模型表达矩阵自带大量免疫细胞模型与训练数据分布强绑定,跨平台可能偏移
scGate基于marker规则的逐层过滤表达矩阵 + 规则库结果透明,适合自定义只适合规则清晰的大类注释
手动注释人读DotPlot和FeaturePlot聚类结果最灵活,能处理复杂样本费时,有主观性

我个人最常用的是SingleR。原因有三个:它完全在R生态内,可以直接和Seurat对象互操作;它输出每个细胞完整的打分矩阵,你能看到“第二高分”是谁,方便排查模棱两可的情况;它对无监督聚类结果比较友好,低置信度的细胞会被标记为NA,而不是强行分配一个身份,这一点在质量控制上非常实用。

3.3 SingleR实测步骤与参数细节

跑一遍SingleR的流程很短,核心代码就几行:

library(celldex) library(SingleR) ref_data <- HumanPrimaryCellAtlasData() pred <- SingleR(test = GetAssayData(obj, slot = "data"), ref = ref_data, labels = ref_data$label.main) obj$singleR_label <- pred$pruned.labels

几个容易踩的细节值得单独说。第一,GetAssayData取的是logcounts,千万别取错slot,用原始counts会让打分偏向高表达量基因,结果被几个高丰度转录本主导。第二,第一遍注释用label.main,不要直接用label.fine。fine标签在部分参考数据集里细分到几十种T细胞状态,和你的聚类颗粒度根本对不上,只会徒增噪音。第三,用pruned.labels而不是labels,前者经过了剪枝,低置信度细胞会被置为NA,这样你至少知道哪些细胞是“没把握”的。

关于参考数据集的选择,我的经验是不要迷信“越大越好”。数据越大越杂,不同来源对细胞类型的定义本身就不统一,反而拉低注释准确度。比较务实的做法是准备两三套常用参考,交叉跑一遍,看哪套结果与手工marker判断最吻合,后续就固定用那套。

注意:跑完SingleR后,把结果按cluster汇总成一张列联表,能直观看到哪些簇的自动化标签高度一致,哪些簇存在明显分歧。这张表是进入下一步交叉验证的入场券。

4. 两种注释对不上时怎么办:冲突处理与低质量簇清理

4.1 自动化说T、手工看起来更像NK:听谁的

这种矛盾太常见了:某个簇里CD3D有表达但CD4和CD8都是阴性,NKG7和GNLY强阳,SingleR却因为整体转录模式接近T细胞而给了T细胞标签。为什么会这样?因为自动化工具做的是全基因组的全局比对,它按“整体最像谁”来打分;而单细胞分析里,决定细胞命运的往往就是两三个关键marker的差异。这两三个marker的方向,手工判断比全局打分更可靠。

我的处理原则是:手工marker优先,工具意见作参考,但两个结果都保留在对象里。上面那个例子,CD3家族阳性而缺少典型TCR下游信号的簇,先标成T/NK祖细胞或者NK样T细胞,再回到marker层面细分。反过来,如果手工marker完全说不清,而工具给了一个置信度很高的标签,也不要直接采纳。更稳的做法是回到该簇的差异表达基因列表,看排名前20的基因到底是什么。看20个基因比看100个基因更容易形成判断,前几个差异基因往往直接指向答案。

4.2 未知簇的处理循环

注释之后,总有那么一两个簇让所有marker都“沉默”,或者只表达一堆核糖体蛋白、热休克蛋白这类管家基因。遇到这种未知簇,我的建议是不要硬套标签,而是走一个三步循环。第一步,把该簇的细胞单独取出来重新聚类,看能不能把混在里面的不同状态分离;第二步,用FindMarkers把该簇和“全局最相似的已知簇”做差异比较,看top差异基因到底是什么;第三步,根据top基因做判断——如果仍然是管家基因或线粒体基因,大概率是低质量细胞,回到QC阶段重新过滤;如果呈现出了某种明确的程序性特征,比如细胞周期基因或干扰素响应基因,那说明它是一个“状态簇”,可以命名成“增殖中的T细胞”这类状态型标签,而不是强行归类。

我见过不少项目因为“必须给每个簇都起个名字”,把一群增殖中的CD8阳性T细胞标成了浆细胞,因为MKI67和其他免疫marker混检,导致后续富集分析的结论整体跑偏。未知并不丢人,如实标注unknown或者low quality,反而让下游分析更可信。

4.3 低质量簇和双细胞簇:删还是留

体检阶段发现的低质量簇,在注释阶段往往会再次冒出来。它们通常表现为细胞数极少、线粒体比例高、几乎不表达任何特异性marker。处理方式有三种:回退到QC阶段,把这些细胞从对象中直接过滤掉,重新跑下游流程,这是最干净的做法;或者保留在对象里,但标注为低质量,不进入后续差异分析的比较矩阵;如果这个簇与某个健康簇在UMAP上紧邻,且形态不是孤立的卫星,可以尝试用更高分辨率重新聚类,看能否把坏细胞重新摊回正常簇中。

双细胞的情况不同。它们的典型特征是同时高表达两种完全无关的细胞类型marker,比如EPCAM和CD3D同时点亮。遇到这种“跨界表达”的簇,先回查线粒体阈值是不是设得太宽,或者上游建库环节是否做了双细胞剔除。通用经验是:宁可少收几千个细胞,也不要让一批双细胞混进后续分析。单细胞分析里质量永远优先于数量。

5. 把注释结果交付给下游分析:保存、输出与衔接

5.1 注释结果的规范化保存

注释完成后,我会做两个动作:把标签写回Seurat对象的metadata,然后按生物学的逻辑顺序重排因子水平。

obj$cell_type <- final_labels levels_order <- c("T_cell", "NK_cell", "B_cell", "Myeloid", "Epithelial", "Endothelial", "Fibroblast") obj$cell_type <- factor(obj$cell_type, levels = levels_order) Idents(obj) <- "cell_type" saveRDS(obj, "obj_annotated.rds")

重排因子水平这一步看着不起眼,影响却很大。后续画图、做差异分析,所有输出的分组顺序都按这个层级排列,免疫细胞在前、间质在后,成图时一目了然。很多下游脚本会默认按因子水平决定分组顺序,不提前排好,出来的图顺序就很随意,修改成本反而更高。

5.2 固定输出三类文件

每跑完一个项目的注释,我都会固定产出三类文件,方便写文章或者交付给合作方复用。第一类,细胞类型数量统计表:

write.csv(as.data.frame(table(obj$cell_type)), file = "celltype_counts.csv")

第二类,每个簇的平均marker表达矩阵,配合前面做的DotPlot一起存成PDF。这属于注释的“可视化存证”,审稿人后期追问注释依据时,能直接拿出原始证据。第三类,每类细胞的核心marker列表和平均表达信息,包括marker名称、平均差异倍数、表达该marker的细胞比例,按下面这种格式整理:

cell_typemarkeravg_log2FC表达比例
T_cellCD3D2.310.87
NK_cellNKG72.050.93
B_cellCD79A1.780.82

这类表格在写方法学段落时可以直接引用,也能帮助合作方快速理解数据质量。

5.3 为差异分析、轨迹分析、细胞通讯打好基础

最后说一个容易被忽略的点:注释环节完成的不是一个标签,而是一整套决策上下文,后面每一步分析都在消费它的价值。

差异分析是最直接的。FindAllMarkers基于注释后的细胞类型分组运行,跑完后得到的是“每类细胞区别于其余所有细胞”的marker列表,直接拿去跑富集分析。但运行之前必须确认一件事:对象当前的Idents已经切换成cell_type,而不是默认的seurat_clusters。这个坑我见过不止一次,输出结果看起来是细胞类型间的差异,实际上还是聚类编号间的差异,分析结论完全不可用。

Idents(obj) <- "cell_type" markers <- FindAllMarkers(obj, only.pos = TRUE, min.pct = 0.25, logfc.threshold = 0.5)

轨迹分析同样依赖注释。轨迹推断需要指定起始细胞,通常从某个特定类型出发,注释结果直接告诉你起点应该设在哪里。比如上皮向间质转化的轨迹,起点往往落在某个上皮簇,没有注释根本无从下手。细胞通讯工具输入的也是细胞类型标签,标签一旦混乱或者颗粒度不足,通讯网络图会完全失去生物学意义。把注释做扎实,后面每一步都省力;注释做得糙,后面所有的图都得推倒重来。这也是我把体检放在整个注释流程第一位的根本原因。

最后分享一点个人体会。刚开始做单细胞分析时,我也觉得注释是“一次定性、终身受用”,后来发现完全不是这样。更合理的做法是先给一个粗一点的标签把所有流程跑通,让差异分析和轨迹分析的结果反过来验证注释是否合理——如果某个“细胞类型”在后续分析里表现得异常孤立,差异基因全是管家基因,那就回头细看它的原始marker表达。注释是会迭代的,不是一锤子买卖。把每一次修正都写进脚本和注释文档,整个流程的可复现性自然就上来了。

返回列表