
如果把单细胞分析流程比作做饭前面三讲相当于处理食材和掌握火候——从读入表达矩阵、过滤低质量细胞到归一化降维、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.87NK_cellNKG72.050.93B_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表达。注释是会迭代的不是一锤子买卖。把每一次修正都写进脚本和注释文档整个流程的可复现性自然就上来了。