细胞注释:手动 marker 与自动 SingleR
跟完这篇,您能把一张已经聚好类的单细胞图谱,从「第 0 群、第 1 群」这样的编号,落实成「CD8 T 细胞、单核细胞、B 细胞」这样的生物学身份:既会用 marker 手动判读,也会用 SingleR 自动打分,还知道两者怎么互相印证、一个标签到底可不可信怎么看。
聚类只把细胞按表达相似度分成了若干群,群还只是编号。注释就是给每个群贴上细胞类型标签。做法分两条路:一条是手动 marker 法,靠已知的标志基因人工判读;一条是自动注释,让算法拿参考数据集给每个细胞打分。两条路各有长短,稳妥的做法是都跑一遍、互相对照。
开始之前:您手上要有什么
注释是聚类之后的一步,动手前请确认两样东西已经就绪:
- 一份聚过类的 Seurat 对象:已经跑完标准化、降维、
FindClusters,Idents()或seurat_clusters里是群编号。聚类怎么做,见 单细胞聚类:从降维到分群。 - log 标准化后的表达数据:
FindAllMarkers和 SingleR 都在标准化后的表达上工作(Seurat 的data层,即NormalizeData的结果),不是原始 counts。
注释的目标不是「给每个群硬塞一个名字」,而是在有把握的地方下明确判断,在没把握的地方老实标成「待定」。宁可留一个 Unknown,也不要凭一个不特异的基因乱认。
如果对象做过整合(多样本用 CCA、Harmony 等对齐过),默认 assay 可能是
integrated或SCT,那里只留了部分基因、也不是用来做差异的层。找 marker 前先把默认 assay 切回原始表达:DefaultAssay(seurat) <- "RNA"。
手动 marker 法
手动法分两步:先让每个群自己「交出」区分性最强的基因,再拿这些基因去比对已知的细胞类型标志。
先摆一眼两条路的性格,好知道各自该派什么用场:
| 维度 | 手动 marker 法 | 自动注释(SingleR) |
|---|---|---|
| 依据 | 已知标志基因 + 人工判读 | 参考数据集 + 相关性打分 |
| 速度 | 慢,逐群看 | 快,一次跑完 |
| 可复现 | 依赖经验,不同人可能给不同结论 | 参数固定,结果可复现 |
| 短板 | 费时、要懂组织的生物学背景 | 只和参考数据集一样好,库里没有的类型会被硬套 |
| 最适合 | 定案、确认稀有或组织特有类型 | 快速起底、给手动判读提供线索 |
第一步:找各群的 marker
用 FindAllMarkers 对每个群做「本群 vs 其余所有细胞」的差异检验,找出每群特异高表达的基因。
library(Seurat)
library(dplyr)
# 若对象整合过,先把默认 assay 切回原始表达再找 marker
DefaultAssay(seurat) <- "RNA"
# seurat 已完成聚类;默认用 Wilcoxon 秩和检验
markers <- FindAllMarkers(
seurat,
only.pos = TRUE, # 只要上调的,即本群相对高表达的基因
min.pct = 0.25, # 至少在 25% 的本群细胞里检出:常用惯例,按需调
logfc.threshold = 0.25 # log2FC 门槛:常用惯例,不是铁律
)
# 每群取 avg_log2FC 最高的前若干个,作为候选 marker
top <- markers %>%
group_by(cluster) %>%
slice_max(avg_log2FC, n = 10)
FindAllMarkers 的输出每行是「某个基因在某群」,关键列有:avg_log2FC(本群相对其余的 log2 倍数变化)、pct.1 / pct.2(在本群 / 其余群的检出比例)、p_val_adj(校正 p 值)。一个好 marker 的特征是 pct.1 高、pct.2 低——在本群普遍表达、在别处几乎不表达。
第二步:比对已知细胞类型
拿每群的 top marker 去对照该组织里公认的标志基因。下面是外周血单个核细胞(PBMC)里一些常被引用的经典 marker,供体会「怎么比对」,不同组织要换成对应的标志基因:
| 细胞类型 | 常用标志基因 |
|---|---|
| T 细胞(总) | CD3D、CD3E |
| CD4 T 细胞 | IL7R、CD4 |
| CD8 T 细胞 | CD8A、CD8B |
| B 细胞 | MS4A1(CD20)、CD79A |
| NK 细胞 | NKG7、GNLY |
| 单核细胞 | CD14、LYZ |
| 树突状细胞 | FCER1A |
表里是示例,不是清单。每种组织、每个课题的 marker 都不一样,请以您所在领域公认的标志基因为准(可查 CellMarker、PanglaoDB 等公开数据库,或对照同组织的经典文献)。
判读时别只看一个基因是否表达,要看它是否只在这一群里表达。DotPlot 把这件事画得最清楚——一个基因的点若只在某一群又大又深、在别群又小又浅,才是可信的判据。
# 挑一组候选 marker,一次看清各群的表达分布
DotPlot(seurat, features = c("CD3D", "CD8A", "MS4A1", "CD14", "NKG7", "LYZ")) +
RotatedAxis()
# 换个角度核验同一件事,两图配合看更踏实:
VlnPlot(seurat, features = c("CD3D", "CD14")) # 各群的表达分布(小提琴图)
FeaturePlot(seurat, features = c("CD8A", "MS4A1")) # 把表达画到 UMAP 上,看落在哪群
判定清楚后,把编号换成细胞类型名:
new.ids <- c("0" = "CD4 T", "1" = "CD14 Mono", "2" = "B",
"3" = "CD8 T", "4" = "NK") # 按您的判读结果填
seurat <- RenameIdents(seurat, new.ids)
seurat$cell_type <- Idents(seurat) # 存一列,便于后续引用
自动注释:SingleR 打分
手动法准,但慢、依赖经验。自动注释反过来:快、可复现,代价是结论只和参考数据集一样好。SingleR 是常用的一种,思路是拿一套已标注好的参考表达谱,为每个细胞(或每个群)跟各参考类型算相关性、打分,取分最高的类型作标签。
第一步,选参考数据集。celldex 包提供了几套现成的参考,按样本类型选:
| 参考数据集(celldex) | 适用 |
|---|---|
HumanPrimaryCellAtlasData() | 人,覆盖面广、类型多 |
BlueprintEncodeData() | 人,免疫与基质细胞 |
MonacoImmuneData() | 人,免疫细胞分得细 |
第二步,把 Seurat 对象转成 SingleR 认的格式并打分。SingleR 有两种粒度:
library(SingleR)
library(celldex)
ref <- HumanPrimaryCellAtlasData() # 选一套参考
sce <- as.SingleCellExperiment(seurat) # Seurat -> SingleCellExperiment
# 粒度一:逐细胞注释
pred <- SingleR(test = sce, ref = ref, labels = ref$label.main)
seurat$singler <- pred$labels
# 粒度二:逐群注释(拿聚类结果做单位,结果更稳、更省算力)
pred_cl <- SingleR(test = sce, ref = ref,
labels = ref$label.main,
clusters = seurat$seurat_clusters)
label.main是粗粒度标签(如T_cells),label.fine更细(如具体亚型)。先用main看大类,需要再上fine。- 逐群注释(给
clusters参数)通常比逐细胞更稳:单个细胞噪声大,落到群上做多数投票更抗噪,也更省时间。
两者互相印证
手动和自动,谁也不该单独定案。真正让人放心的,是两条独立的路走到同一个结论。做法很直接:把 SingleR 的标签和您手动判读的身份放到一张表里对照。
# 把逐细胞 SingleR 结果与手动注释交叉列表
table(manual = seurat$cell_type, singler = seurat$singler)
看这张交叉表,三种情况分别处理:
- 两者一致:手动认成 B 细胞、SingleR 也判 B 细胞——这一群基本可以定案。
- 粗细之差:手动只到「T 细胞」,SingleR 分到「CD8 T」。不矛盾,是互补,可采用更细的那个(但要回头用 marker 确认细分站得住)。
- 明确冲突:手动认 NK、SingleR 判单核。这是警报,别急着信任何一方——回去看这群的 marker 是不是不特异、是不是两类细胞混在了一群(见下文「常见坑」)。
参考数据集里没有的细胞类型,SingleR 只会硬套最接近的已知类型,不会说「不认识」。所以自动注释给出的类型,一定要用 marker 回验一遍,尤其是参考库里可能缺失的稀有类型或您组织特有的类型。
注释可信度怎么判断
一个标签贴上去,得能说清「凭什么」。判可信度看两个维度:marker 够不够特异、群本身纯不纯。
marker 特异性——好的判据是「本群有、别群无」:
DotPlot里该 marker 只在目标群又大又深,别群点小色浅;FindAllMarkers结果里pct.1高而pct.2低;- 若一个「marker」在半数群里都表达,它就不特异,别拿它当判据。
SingleR 的把握度——SingleR 自带两个诊断量:
# 每个细胞对各参考类型的打分热图:好的注释应「独高一峰」
plotScoreHeatmap(pred)
# delta 分布:最高分与其余分的差距,差距越大越可信
plotDeltaDistribution(pred)
pred$pruned.labels 是 SingleR 剔除低置信后的标签:把握不足的细胞会被置成 NA。把 NA 当成「模型也拿不准」的诚实信号,不要强行填一个类型。
群纯度——一个群若是干净的单一类型,它的 marker 会集中、SingleR 打分会一致;若一群里同时冒出两套互斥的 marker(比如既有 T 细胞的 CD3D 又有单核的 CD14),说明这群很可能没分开,注释也就无从可信。
常见坑
| 坑 | 表现 | 怎么办 |
|---|---|---|
| marker 不特异 | 拿来判读的基因在多群都表达,认谁都像 | 优先选 pct.1 高、pct.2 低的基因;一个类型用一组 marker 交叉印证,别靠单基因 |
| 分辨率过高,一类被切碎 | 同一种细胞被拆成好几群,marker 几乎一样 | 调低 FindClusters 的 resolution;marker 高度重叠的相邻群可考虑合并 |
| 分辨率过低,多类混一群 | 一群里同时有互斥 marker(如 CD3D 与 CD14) | 调高 resolution 重新分,或对该群单独再聚类 |
| 参考库里没有的类型 | SingleR 硬套一个最接近的已知类型 | 换更贴合的参考;一律用 marker 回验,稀有或特有类型尤其要查 |
| 双细胞(doublet) | 一小群同时高表达两谱系的 marker | 上游用 doublet 检测标记并剔除,别把它当成一种「新细胞类型」 |
| 迷信自动、跳过回验 | SingleR 给了标签就直接采用 | 自动结果永远用 marker 复核,冲突的群务必查清 |
| 在整合层上找 marker | 忘了切 assay,marker 数目少、结果奇怪 | 找 marker 前 DefaultAssay(seurat) <- "RNA" |
分辨率没有「正确值」。
resolution调大分得细、调小分得粗,合适与否取决于您要区分到多细的类型。惯常做法是试几个值,看哪个能让已知细胞类型恰好各成一群——这本身也要靠 marker 来判断,具体怎么调见 单细胞分辨率:分群该多细。
在平台上跑一遍
不想装 Seurat、SingleR,也能完成整套注释:在 百沐一下 传入聚好类的单细胞对象,用一句话说清目的——「给各群做细胞注释,先跑 marker、再用 SingleR 对照,物种是人、组织是外周血」,平台会自动找各群 marker、调参考数据集打分、把两者交叉对照,并连同 DotPlot、打分热图和可复现的 R 代码一起交付,可信度存疑的群会替您标出来。用法见 组学分析助手使用指南。
相关
- 单细胞聚类:从降维到分群 —— 注释的上一步,群从哪来
- 单细胞分辨率:分群该多细 —— 群切碎或混在一起时,回这里调分辨率
- 单细胞 RNA-seq 分析全流程 —— 从质控到注释的完整脉络,注释在其中的位置
- 组学分析助手使用指南 —— 在平台上不写代码完成注释与回验