资讯动态

单细胞测序t-SNE聚类与Marker基因筛选:从Seurat实操到细胞注释避坑指南

发布时间:2026/10/5 8:51:29 来源:尧图企业网站定制
单细胞测序数据处理走到这一步前面已经完成了质控、标准化、高变基因筛选、PCA降维这些前置操作手里是一个已经Scale过的Seurat对象。接下来要做的就是把上万个细胞按照转录组相似性分堆并且找出每一堆的“身份标签”——也就是t-SNE聚类分析和寻找marker基因。这部分是整个单细胞流程里最核心、也最容易被各种参数坑到的一环。从我开始跑单细胞数据到现在印象最深的就是第一次跑聚类时对着t-SNE图发呆图是出来了但根本分不清哪些群是有生物学意义的、哪些是技术噪音更不知道下一步该拿什么标准给细胞命名。后来把t-SNE的降维逻辑、聚类的参数选择、marker基因的统计学检验原理都啃了一遍才算真正理解这条流程。这篇文章就把t-SNE聚类分析和marker基因筛选的完整流程、参数选型逻辑、常见翻车场景都整理出来写给正在跑Seurat流程、准备做细胞注释的朋友。1. 聚类分析前置为什么要做t-SNE数据从哪里来1.1 为什么单细胞数据必须降维和聚类单细胞转录组数据本质是一张巨大的基因表达矩阵行是细胞列是基因。一个10X平台出来的样本通常能捕获一万到两万个基因测到五千到一万个细胞。这意味着每个细胞都是一个上万维的向量整份数据就是一个高维空间里的点云。人类没法直接理解上万维空间里的距离和密度必须把它压缩到二维或三维才能用眼睛找到规律。聚类分析就是在这张表达矩阵上根据转录组相似性把细胞分成若干个组。这个思路其实和传统的统计学聚类一脉相承——类似SPSS里的K-means、系统聚类本质上都是把相似样本归堆。但单细胞数据有自己的特殊性维度极高、稀疏性极强、噪音很大直接把传统聚类工具搬过来跑结果往往惨不忍睹。所以单细胞分析里才演化出了一套“先降维、再聚类”的标准操作。为什么要先降维因为高维空间里距离度量会失真直接用原始表达矩阵算细胞间距离大量噪音基因会淹没真实信号。降维相当于把“噪音”和“信号”做了一次分离让聚类在更干净的低维空间里进行。这也是为什么t-SNE在这条流程里的定位不是聚类本身而是把聚类结果“画”出来给人看。1.2 从PCA到t-SNE的递进两种降维的分工在Seurat的标准流程里降维是分两步走的。第一步是PCA也就是主成分分析它是线性降维目标是把高维数据投影到方差最大的几个方向上保留数据的主要结构。PCA输出的主成分数量通常在几十个以内这一步的主要目的是压缩维度、去除噪音为后续分析打底。很多人在这里有个误区觉得PCA降维之后就可以直接聚类。实际上PCA本身不适合直接展示细胞群体结构因为它的线性约束让它只能捕捉全局方差很难把复杂的非线性细胞状态分离开。t-SNE的作用就在这里体现出来它在PCA的基础上再做一次非线性降维把高维空间里的局部相似性映射到二维平面让相似的细胞在图上靠得更近不同类群之间的缝隙更明显。t-SNE的代价也很明显全局距离失真。t-SNE的二维坐标只反映局部的相似性群与群之间的距离远近没有绝对意义甚至同一次运行、不同的随机种子画出来的图都会有些许差别。所以t-SNE在流程里的角色就是“可视化工具”真正的分群依据还是来自聚类算法的结果不能拿t-SNE图的视觉距离去判断细胞关系。1.3 开始聚类前Seurat对象需要准备到什么程度很多新手会忽略一个关键前提t-SNE聚类分析和寻找marker基因是建立在一个已经完成标准预处理的Seurat对象上的。具体来说前面的流程至少要跑完这几步质控过滤剔除低质量细胞、标准化NormalizeData、高变基因筛选FindVariableFeatures、数据缩放ScaleData和PCA降维RunPCA。如果前面的步骤没做扎实后面跑出来的聚类图就非常容易出问题。比如质控没做好死细胞或者双细胞的转录组特征会形成一堆“假群”导致marker基因筛选出来的结果全是线粒体基因或核糖体基因完全没法做注释。我踩过最狠的一次坑就是QC阈值放得太松结果整整一个下午都在跟一个纯线粒体基因的cluster较劲。准备到这个程度之后我强烈建议先把对象保存成RDS文件再继续。这一步看似多余实际上能救命——后面每调试一次参数如果都要从头跑一遍前面的标准化和PCA时间成本是成倍增加的。# 假设你已经跑完了QC、Normalize、ScaleData、RunPCA saveRDS(seurat_obj, file seurat_after_pca.rds) # 下次直接加载省掉前面的所有预处理时间 seurat_obj - readRDS(seurat_after_pca.rds)需要确认的是seurat_objreductions里已经有pca这个降维结果seurat_objassays$RNAscale.data也已经生成。如果没有请返回上一步补齐不要强行往下走。2. t-SNE聚类实操Seurat里的标准流程与参数控制2.1 三步走FindNeighbors、FindClusters、RunTSNE真正做聚类和t-SNESeurat里就是三段式操作先构建细胞间的图结构再在这个图上做社区发现聚类最后把聚类结果放到t-SNE坐标里展示。每一步都有各自的参数选错任何一个结果可能就是天壤之别。第一步是FindNeighbors。这一步会基于PCA结果计算细胞之间的相似性并且构建KNN图。这里唯一的参数是dims也就是使用前多少个主成分。选择标准有两个一个是看ElbowPlot找到主成分方差贡献率下降趋缓的拐点另一个更实用的思路是根据预期细胞类型复杂度来定一般免疫细胞图谱用10到20个主成分都比较常见。主成分选得太多引入的噪音会把聚类结果打散选得太少又会丢失部分真实的生物学信号。第二步是FindClusters。这一步是在KNN图上做聚类默认算法是Louvain算法它通过迭代优化模块度来寻找图中的社区结构。这里最重要的参数是resolution它直接控制聚类结果的粒度数值越大分出来的群越多数值越小群越少。很多人第一次跑的时候直接用默认的0.8这不一定适合所有数据。第三步是RunTSNE它接收聚类结果然后计算t-SNE坐标。这一步唯一需要注意的是set.seed因为t-SNE的迭代过程是随机初始化不同的种子会得到稍微不同的二维坐标但这不改变聚类本身的结果。# 标准三步走 seurat_obj - FindNeighbors(seurat_obj, dims 1:15) seurat_obj - FindClusters(seurat_obj, resolution 0.5) seurat_obj - RunTSNE(seurat_obj, dims 1:15, seed.use 42) # 可视化 DimPlot(seurat_obj, reduction tsne, label TRUE)这段代码看起来简单但里面的dims和resolution才是真正决定结果质量的参数。跑完DimPlot之后重点不是看图好不好看而是看分群数量是否符合你对样本复杂度的预期。如果你预期样本里有十种左右的细胞类型但画出来只有四个群说明resolution给低了如果出来三十个群很多群只有一两个细胞说明分辨率太高需要降下来。2.2 resolution怎么选种子要不要设resolution的调试是我做聚类时花时间最多的地方。一个比较实用的方法是从一个较低的值开始比如0.1然后逐步往上调记录不同分辨率下群的数量和群内细胞数。重点观察两个指标一是群的数量是否还符合生物学预期二是是否存在细胞数量特别少的“碎片群”——通常少于总细胞数0.5%的群非常可疑。实际操作中我一般会跑一个循环直接把多个分辨率的结果全部算出来再统一看图。# 同时尝试多个分辨率比较后再选 for (res in c(0.1, 0.3, 0.5, 0.8, 1.0, 1.2)) { seurat_obj - FindClusters(seurat_obj, resolution res) print(paste0(resolution, res, , clusters, length(unique(seurat_obj$seurat_clusters)))) }比较下来如果0.5到0.8这个范围内群的数量已经稳定一般就选那个中间值。但这里有一条重要的实操经验不要盲目追求群的数量多。群的粒度应该由下游的生物学问题决定。比如你做免疫细胞图谱想区分CD4和CD8那分辨率至少要到能分出T细胞亚群的程度如果你只想分大类分辨率可以适当低一些。至于种子RunTSNE里一定要设。这不仅仅是为了结果可复现更是为了调试工作流的时候不会被随机性干扰。你调试参数的过程中如果每次跑出来的图都不一样你会完全无法判断当前改动是改善还是恶化。设置种子之后至少t-SNE的可视化层面是稳定的。注意FindClusters里也可以设置random.seed但更常见的是在开头用set.seed(42)统一控制所有随机过程。2.3 UMAP和t-SNE怎么配合用很多教程会把UMAP和t-SNE放在一起比较初学者经常纠结该用哪个。说一个我自己实践下来的结论两个都跑但它们的用途不同。t-SNE擅长展现局部分群结构群内部相对紧凑适合微观观察UMAP在保持全局结构方面做得更好群与群的相对位置更有意义适合整体布局判断。在Seurat里跑UMAP很简单RunUMAP的参数逻辑和RunTSNE几乎一样同样需要指定dims和种子。我通常把t-SNE作为默认展示图因为它在免疫细胞类群之间拉开的间距更明显视觉上更容易向合作者解释但做批次效应评估或者跨样本比较时我会更信任UMAP的全局排列。这里也给一个对比表格方便你按需选维度t-SNEUMAP降维类型非线性侧重局部结构非线性兼顾局部与全局群间距离不能作为定量依据相对更可信运行速度大数据集上明显更慢通常更快参数敏感性对perplexity较敏感对n_neighbors较敏感适用场景精细分群的可视化展示整体结构判断、批次评估实际项目里我经常是先用UMAP判断细胞群整体的拓扑结构选好分辨率之后再用t-SNE出正式图表两个图一起放进报告里。3. 寻找marker基因从统计学逻辑到实际操作3.1 marker基因是什么找到它有什么意义聚类完成之后每个细胞都被贴上了一个数字标签比如0、1、2、3但数字本身没有生物学含义。接下来要做的事情就是给每个cluster找到一个“身份证明”——也就是marker基因。marker基因指的是在某一个cluster里特异性高表达、而在其他cluster里低表达的基因。为什么要强调“特异性高表达”而不是“平均表达高”因为有些基因比如核糖体蛋白基因几乎在所有细胞里都高表达它们不能区分细胞类型。真正的marker基因就像每个人群里的“暗号”看到某个基因高表达就能大概率判断这个细胞属于哪一类。比如CD3E是T细胞的经典markerCD19和MS4A1是B细胞的markerLYZ和S100A8则是髓系细胞的常见marker。找marker基因的意义主要有两个一是辅助细胞类型注释这是最直接的用途二是验证聚类结果的质量。如果某个cluster里筛出来的marker全是不知名的长链非编码RNA而且表达模式乱七八糟那大概率是技术伪迹而非真实的细胞亚群这时候就要回头检查聚类参数了。3.2 FindAllMarkers的关键参数和计算原理在Seurat里最常用的找marker函数是FindAllMarkers它的底层逻辑是对每个cluster做一次“该cluster vs 所有其他细胞”的差异表达检验。默认使用Wilcoxon秩和检验这是一种非参数检验方法不假设数据符合正态分布非常符合单细胞表达数据的分布特征。关键参数有四个only.pos、min.pct、logfc.threshold、test.use。only.pos TRUE表示只返回在该cluster中上调的基因因为注释细胞类型时最关心的就是上调基因。min.pct表示基因在两组细胞中被检测到的比例下限默认0.1我一般会调到0.25过滤掉那些只在极少细胞里表达的基因。logfc.threshold表示平均表达差异的倍数变化阈值默认0.25如果希望结果更紧凑可以调到0.5或1。一个很容易忽略的问题是test.use。默认是Wilcoxon但有些文章会用MAST、DESeq2等。这里结合我自己的使用体会说明Wilcoxon速度快、结果稳健适合初次筛选如果有比较强的批次效应或者样本结构复杂可以考虑MAST它是专门为单细胞数据设计的对零膨胀有处理但速度慢很多。绝大多数的场景下Wilcoxon就足够了没必要为了炫技换一个更慢的检验。# 寻找所有cluster的marker基因 markers - FindAllMarkers( seurat_obj, only.pos TRUE, min.pct 0.25, logfc.threshold 0.5, test.use wilcox )跑完之后markers是一个data.frame每一行代表一个“基因在某cluster里作为marker”的检验结果。对这个结果表的解读是整个环节里的重头戏。3.3 从结果表到真实marker的筛选方法FindAllMarkers输出的结果表里最关键的三列是cluster、avg_log2FC和p_val_adj。cluster表示基因对应哪个clusteravg_log2FC表示该基因在这个cluster和所有其他cluster之间平均表达倍数变化的log2值数值越大说明越特异p_val_adj是多重检验校正后的p值越小说明差异越显著。筛选的第一步是过滤掉p_val_adj不显著的基因一般取小于0.05第二步是过滤掉avg_log2FC过低的基因这个阈值要根据注释的精细程度调整。注释大类细胞类型时可以保留avg_log2FC 0.5 的基因注释T细胞亚群这种比较接近的类群时可能需要把阈值降到0.25否则可能筛不到足够多的标记。# 筛选显著且高表达的marker sig_markers - markers[markers$p_val_adj 0.05, ] top_markers - sig_markers %% group_by(cluster) %% arrange(desc(avg_log2FC), .by_group TRUE) %% slice_head(n 10) # 按cluster查看top基因 top_markers[, c(cluster, gene, avg_log2FC)]这里我有一个经常使用的判断技巧如果某个cluster的top基因里出现大量的线粒体基因MT-开头或者核糖体基因RPS/RPL开头这个cluster很可能是低质量细胞或者双细胞形成的伪群。这些基因在几乎所有细胞里都表达它们的“高表达”往往反映的不是细胞类型特征而是细胞状态异常。遇到这种cluster不要急着注释先回到数据里看看这个cluster的QC指标是不是偏低了。4. 结果可视化与细胞类型注释4.1 三种可视化工具各解决什么问题marker基因筛选出来之后还需要通过可视化来验证结果不能只盯着统计表格。Seurat里最常用的三个可视化函数是DimPlot、FeaturePlot和VlnPlot它们分别解决不同的问题。DimPlot用于展示整体聚类结构就是把t-SNE/UMAP坐标图按cluster染色直观看到分群边界和群间关系。FeaturePlot用于展示某个基因在降维图上的表达量分布比如你想确认CD3E到底是不是在某个cluster里特异性高表达在t-SNE图上直接把该基因的表达量映射为颜色即可。VlnPlot则以小提琴图的形式展示某个marker基因在不同cluster中的表达分布比FeaturePlot更能反映表达量的统计分布。实际应用里我一般这样配合使用先用DimPlot看全局再用FeaturePlot看候选marker的空间分布最后用VlnPlot确认表达差异是否统计上可靠。三层验证都通过了一个cluster的身份才算基本锁定。# 三种可视化示例 DimPlot(seurat_obj, reduction tsne, label TRUE) FeaturePlot(seurat_obj, features c(CD3E, CD19, LYZ), reduction tsne) VlnPlot(seurat_obj, features c(CD3E, CD19, LYZ), pt.size 0)小提琴图里默认会画出每个细胞的散点细胞数量大的时候图会很乱。这里给一个建议看群体分布的时候把pt.size设成0只保留密度分布曲线这样能更清晰地比较不同cluster之间的表达差异。同时可以通过stack方式把多个marker基因的小提琴图纵向堆叠一张图就能看清一个cluster所有的marker表达模式。4.2 怎么把cluster ID变成真正的细胞类型这是让很多人头疼的一步cluster数字编号知道了top marker也看完了但到底该叫什么名字我的做法是先整理一个候选marker清单然后逐个cluster对照。比如某个cluster的top marker里同时出现CD3D、CD3E、IL7R基本可以判定为T细胞如果还出现CCR7、LEF1这些基因可以进一步推断为初始T细胞或者中央记忆T细胞。但这里要提醒一点不要只看单个marker要看marker组合。有些marker并不完全特异比如CD4在单核细胞里也有一定表达仅凭CD4一个基因不能判定这个cluster就是CD4 T细胞。靠谱的做法是一个cluster里至少要有两到三个已知marker同时出现而且表达模式互相印证才能做注释。另一个重要的经验是先注释大类再注释亚群。不要一上来就想着区分CD4 Naive、CD4 Memory、CD8 Effector这些精细亚群。先把T细胞、B细胞、NK细胞、单核细胞、树突状细胞这些大类分清楚大类注释之后如果数据质量支持再对感兴趣的细胞群做二次聚类、二次注释。我见过不少新手的分析报告第一次注释就直接给每个cluster一个很精确的细胞类型名称结果因为marker证据不足被审稿人问得下不来台。4.3 手动验证和数据库辅助注释除了自己看marker还可以借助一些已知的标记基因数据库做辅助验证比如CellMarker数据库、PanglaoDB、SingleR自动注释工具。这些工具可以作为参考但不能直接替代人工判断。尤其要注意的是不同数据库之间的marker基因命名和特异性存在差异同一个基因在不同的数据集里特异性也不同。在我自己的流程里通常会先跑一个SingleR或celldex的自动注释作为粗筛再用人工注释的marker组合去校准。如果人工注释和自动注释在大类水平上冲突明显我会重新审视聚类参数和marker筛选阈值而不是直接信任自动注释的结果。自动注释的结果最多只能作为线索最终命名还是要靠marker基因的组合证据。5. 常见问题、翻车现场与避坑技巧5.1 为什么两次运行聚类结果不一样不少人在复现别人代码时遇到过这种情况两次运行FindClusters或RunTSNE结果看起来不太一样。这里要分清两种不同情况。第一种情况是 t-SNE图的位置发生变化但cluster的组成基本一致。这是正常的因为t-SNE的降维过程有随机初始化不同的随机种子会得到略有差异的坐标排布。这种情况不影响下游结论只要设置了种子图就是可复现的。第二种情况是 cluster 成员本身都变了跟在跑两个完全不同的数据一样。这时候就要警惕通常是因为FindClusters的随机种子没有固定。在Seurat里Louvain算法也有随机性如果不固定种子每次聚类可能都不相同。解决办法是在FindClusters时加上random.seed或者在脚本开头统一set.seed。# 建议的写法显式固定随机种子 set.seed(42) seurat_obj - FindClusters(seurat_obj, resolution 0.5, random.seed 42)5.2 常见报错和处理速查表我整理了平时被问得最多的一些报错多数是参数或对象结构的问题排错思路都写在表格里了。报错/异常现象可能原因解决方案找不到pca这个reduction没跑RunPCA或读取的是旧版本对象重新运行RunPCA后继续FindClusters后没有seurat_clusters列Seurat版本新老差异或对象未正确保存用seurat_obj$seurat_clusters手动赋值t-SNE图上所有细胞挤成一团无分群主成分数选得太少或数据存在严重批次效应增加dims或先做批次整合marker基因全是线粒体/核糖体基因低质量细胞或双细胞混入回退到QC阶段收紧线粒体比例上限某个cluster只有几个细胞resolution过高或聚类随机性降低resolution或检查是否过聚类遇到报错不要慌先看对象结构再逐条排查前置步骤是否有漏。单细胞分析的大部分问题根源都在前期的QC和标准化上而不是聚类本身。5.3 几条我的实操心得最后分享几个我自己踩过之后总结出来的心得。第一聚类和marker筛选不是一锤子买卖。第一次跑出来的聚类结果往往不是最终的需要反复调整resolution、重新筛marker、验证注释结果。这个迭代过程看起来耗时但非常必要。我一般会记录下不同参数组合下的cluster数量、关键marker的富集情况以便复现和调整。第二不要过度解读t-SNE图上“看上去很近”的群。t-SNE的局部结构很有欺骗性尤其是在perplexity设置不当时无关群体可能被强行拉近。判断cluster之间关系时更可靠的方法是看marker基因的表达谱相似度而不是看图上的距离。第三marker基因筛选的阈值不要拍脑袋定死。免疫细胞大类和上皮细胞亚群的表达差异幅度完全不同同一个logfc.threshold不能适用于所有场景。我的建议是先跑一遍默认参数看整体结果再根据具体注释需求调整阈值。如果筛出来的marker数量太多就提高阈值和min.pct如果数量太少就降低阈值。这套流程跑通之后你会发现单细胞分析的“聚类注释”其实是有节奏感的聚类给的是线索marker给的是证据注释是最后的综合判断。把这个节奏掌握了后面哪怕换一批数据、换一个物种你也能很快跑出靠谱的结果。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑