从UMAP准确标注目标基因对应的细胞类群:Seurat与scanpy实操指南

做单细胞转录组的人,几乎都遇到过这个场景:聚类跑完,UMAP也画出来了,clusters都标好了,然后项目组里抛过来一个问题——“我们感兴趣的那个基因,到底在哪群细胞里?能不能在UMAP图上一眼标出来?”我第一次被问的时候,第一反应是画个FeaturePlot,然后指着颜色深的地方说“在这”。事后发现,这个回答既不够严谨,也不够专业。因为UMAP上颜色深的位置可能只是少数极端高表达的细胞,而真正特异性表达这个基因的类群,可能是cluster 3和cluster 7合并以后才对应某类细胞,也可能在这两个cluster里只有一半细胞是阳性。单纯画一张表达量图,和把“基因对应的类群”明确地标识出来,其实是两件事。

这篇文章就围绕这个具体需求展开。我会从UMAP图上的“类群”到底是什么讲起,然后给出从FeaturePlot到真正“标注出目标类群”的完整流程,包括手动圈选、基因集打分、细胞类型注释、批量输出,以及最后出图时那些影响观感的小细节。内容以Seurat为主,也会对照一下用Python做单细胞时scanpy的对应做法。适合正在做单细胞数据分析、需要把目标基因和特定细胞类群对应关系讲清楚的读者参考。

1. 标注基因类群前,先想清楚UMAP图上“类群”到底指什么

很多新手拿到单细胞数据就直接画UMAP,看到上面一团一团的点就自然把它们当成“细胞类型”。这个直觉大方向没错,但它忽略了一件事:UMAP本身不负责定义类群,它只是把每个细胞的高维表达向量压缩到二维坐标上供人观察。你看到的“一团”,是由你事先做好的聚类结果(cluster)或者进一步注释出的细胞类型(cell type)决定的。

1.1 每个点是一颗细胞,不是基因

单细胞转录组表达矩阵的基本结构是:行是基因,列是细胞。UMAP上的每一个点,代表一个细胞,这个细胞在数万个基因维度上的表达谱,被降维算法压缩成了二维平面上的一个坐标。所以当你问“这个基因在UMAP的哪个位置”时,严格来说是“这个基因在哪些细胞中高表达,而这些细胞在UMAP的哪个区域”。

这个区别不是文字游戏,它直接影响画图方式。基因本身没有位置,只有细胞有位置。我们通常把基因表达量映射到细胞的颜色上,让高表达的点呈现深色,然后肉眼寻找那些深色点聚集的区域。但一张图里同时有数万个点,点与点之间还会重叠,光靠肉眼常年会误判。

1.2 “基因所在类群”的两种常见理解

在单细胞项目里,刚才那句“我们感兴趣的基因在哪群细胞里”其实可能指两种不同的需求:

需求类型典型问法核心操作
A. 基因高表达于哪个类群“CD8A是不是T细胞特异表达的?”观察某个cluster或细胞类型中该基因阳性细胞比例、平均表达量
B. 把表达该基因的细胞标成一个群体“能不能把表达IFNG的这些细胞在UMAP上圈出来”按表达阈值取细胞子集,或手动圈选,然后高亮展示

需求A是常规marker验证,FeaturePlot加一个按类群统计的表达量表基本就能回答。需求B更接近“标注类群”本身——你需要把满足某种条件的细胞集合,在UMAP上用一种醒目的方式标识出来,让读者一眼看到这个群体在哪里、边界大概如何。

建议在动手之前先明确是哪种需求。我见过不少人在需求B的场景下只画了FeaturePlot,结果别人盯着图问“那到底哪些细胞算你目标的群体”,答不上来。反过来,如果是需求A,画太多花哨的高亮图反而显得刻意。

1.3 类群粒度:先确认你标的是cluster还是cell type

单细胞聚类中,一个基本参数是分辨率(resolution)。同一份数据,resolution设0.2可能得到5个cluster,设0.8可能得到12个cluster,设1.5可能得到20个以上。这意味着“类群”不是数据里的天然标签,而是你通过参数选择构造出来的分组。

这带来一个实际操作中很关键的问题:如果直接拿未注释的cluster编号作为类群,最后得到结论往往是“目标基因在cluster 0、2、5中高表达”。这种表述没有生物学意义,因为cluster 0、2、5可能只是同一个细胞类型被分辨率切成了三块,也可能cluster 0里混了两种不同的细胞类型。

我的习惯是,在回答“基因对应哪个类群”之前,先完成一轮细胞类型注释,至少要把cluster归并成有生物学意义的类型,比如T细胞、B细胞、单核细胞、NK细胞这样的水平。否则后面所有标注操作都建立在不可靠的“类群”定义上,图画得再漂亮也站不住脚。这一步可以用已知marker手工注释,也可以配合SingleR之类的自动注释工具,具体做法后面章节会展开。

2. FeaturePlot显示表达量,但距离“标出基因所在类群”还差一步

FeaturePlot是单细胞可视化里最常用的函数,没有之一。它做的事情很简单:把某个基因的表达量映射到UMAP点的颜色上。但它展示的是“表达量的连续分布”,而不是“类群的边界”。很多人用错它,是因为默认FeaturePlot就是“标识基因类群”的最终答案。

2.1 基础参数:颜色、点大小、绘制顺序

先给一个我日常用的FeaturePlot版本:

FeaturePlot(seu, features = "CD8A", pt.size = 1.2, cols = c("grey90", "firebrick3"), order = TRUE)

这个配置有几个值得解释的地方。cols控制颜色范围,默认配色是浅灰到蓝色,我习惯改成灰色到红色,因为人眼对暖色更敏感,高表达点更突出。order = TRUE很关键,它的作用是让表达量高的点后画,也就是画在最上层,避免被周围大量低表达或零表达的点盖住。如果不设这个参数,在细胞密度大的区域,高表达点可能被灰色点遮挡,看起来好像那个类群没有信号。

pt.size控制点的大小,一般不用太大,1到1.5在横版图中比较合适。如果细胞总数很多,比如超过5万个点,可以适当减小到0.8左右,不然图会糊成一片。

2.2 判断“基因在哪个类群表达”不能只靠肉眼

FeaturePlot画完,很多人会凭颜色深浅下结论。这在探索阶段没问题,但如果要写进报告或者论文,一定要有一个量化过程。因为单细胞表达数据极度稀疏,许多基因在某个细胞类群中可能只有少数细胞高表达,平均表达量被大部分零表达细胞拉低,肉眼看到的“某处有一小撮红点”不一定代表这个类群整体富集目标基因。

我常用的做法是拉出表达矩阵,按cluster或cell type统计阳性细胞比例:

library(Seurat) # 拉出目标基因表达量,并附上cluster信息 expr_df <- FetchData(seu, vars = "CD8A") expr_df$cluster <- Idents(seu) # 按cluster统计平均表达和阳性比例 summary_by_cluster <- do.call(rbind, lapply(levels(Idents(seu)), function(cl) { sub <- expr_df[expr_df$cluster == cl, ] data.frame( cluster = cl, mean_expr = round(mean(sub$CD8A), 4), pct_positive = round(mean(sub$CD8A > 0), 4) ) })) print(summary_by_cluster)

这里用mean(sub$CD8A > 0)统计阳性比例,比单纯看平均表达量稳得多。原因是单细胞数据里大量dropout事件——一个基因在某个细胞中确实有转录,但因为测序深度不够没有被捕获到,表现为0。所以阳性比例更接近生物学意义的“这个类群有多少比例细胞在表达该基因”。

如果你把Idents(seu)换成已经注释好的细胞类型,这一步可以直接输出“目标基因在各细胞类型中的阳性比例表”。做完这个统计,你才会真正知道目标基因是特异性表达在某个类群,还是仅仅在那里高了一点,甚至只是少数异常细胞的信号。

2.3 从“表达图”到“类群标签图”怎么过渡

假设你已经确定了目标基因的特异性类群,下一步是把结论变成图上可读的标签。至少有两种做法:

一种是直接把Idents改成细胞类型名称,然后画DimPlot。这时候UMAP上每个类群边上有文字标签,你可以在图注里写明“表达CD8A的细胞主要对应图中T细胞类群”。这是学术发表里最常见的呈现方式。

另一种是保留cluster编号但只高亮目标类群,比如把目标cluster的颜色设为红色、其他全部置灰。这对内部讨论很直观,但对不了解你数据的人来说,cluster编号没有语义,需要搭配注释信息。

关键提醒:FeaturePlot展示的是连续的表达量,而“类群标注”是离散的分类信息。从连续到离散,中间必须经过一个判断步骤——要么人为定义阳性/阴性阈值,要么基于统计结果指定目标类群。跳过这个步骤直接画图,就很容易出现“图很漂亮但结论模糊”的情况。

3. 把目标类群真正圈出来:手动圈选、打分筛选、半自动注释

如果FeaturePlot只是“看”,那接下来的操作才是真正的“标”。根据目标群体的特征不同,我一般用三种方式:手动圈选、基因集打分、结合细胞类型注释。它们不是互斥的,项目里经常组合使用。

3.1 手动圈选:CellSelector适合跨cluster的目标群体

手动圈选适合一类特殊场景:目标细胞并不是一个完整cluster,而是散落在两三个cluster里,但你根据基因表达或其他特征能看出它们是一个连续的群体。这种情况用自动聚类重新分组反而费劲,直接在UMAP上圈更高效。

Seurat提供了一个交互式圈选函数CellSelector:

# 先画一张基础UMAP p <- DimPlot(seu, label = TRUE) # 弹出一个可以自由圈选的窗口 seu <- CellSelector(plot = p, object = seu)

运行后会打开一个交互式图形窗口,你可以按住鼠标在UMAP上画任意形状,把目标细胞圈进来。圈选结束后,被选中的细胞在Idents(seu)里会被赋予一个名为“selected”的新身份,你可以把它重命名为目标群体名称,比如:

levels(Idents(seu)) # 其中新出现了一个"selected"身份 Idents(seu)[Idents(seu) == "selected"] <- "CD8A_positive"

手动圈选最大的坑是主观性太强。不同人圈的边界可能差很多。所以我不建议单独依靠圈选得出可靠结论。圈完之后一定要做验证,至少要看这些细胞的目标基因表达分布和已知marker表达情况,确认你圈出来的细胞确实是目标群体。我一般会用VlnPlot比较圈选群体和非圈选群体的目标基因表达量,差异不显著就说明圈错了。

3.2 基因集打分:单基因有噪声,用一组基因更稳

另一个常见情况是“感兴趣的基因”其实只是一类基因中的代表。比如你想标出“细胞毒性T细胞”类群,只拿CD8A画图有时候不够,因为CD8A可能是其他免疫细胞的低水平marker。更稳的做法是选一组已知的细胞毒性相关基因,比如CD8A、GZMB、PRF1、IFNG,做基因集打分,然后基于分数来圈定目标细胞。

Seurat里最方便的是AddModuleScore:

cytotoxic_features <- list(c("CD8A", "GZMB", "PRF1", "IFNG")) seu <- AddModuleScore(seu, features = cytotoxic_features, name = "CytotoxicScore") FeaturePlot(seu, features = "CytotoxicScore1", pt.size = 1.0, cols = c("grey90", "firebrick3"), order = TRUE)

打分完成后,每个细胞会得到一个综合分数,代表这组基因在该细胞中的整体表达水平。你可以在UMAP上观察分数分布,并选择阈值来定义“阳性细胞群”。阈值怎么定?我通常先看分数的直方图或分位数:

quantile(seu$CytotoxicScore1, probs = c(0.5, 0.75, 0.9, 0.99))

如果目标类群是在背景中明显分离的,分数分布会呈现双峰,选峰谷即可;如果没有明显双峰,可以选0.9分位数作为高表达阈值。阈值定了之后,用WhichCells筛选细胞子集,再高亮到UMAP上,这就是“标出目标类群”了。

除了AddModuleScore,工具链里还有两个常用的打分方法:UCell和AUCell。简单来说,AddModuleScore是取基因表达量的平均值,计算快但容易受个别高表达基因影响;UCell基于秩次统计,对基因数多寡和饱和度更鲁棒;AUCell基于AUC值判断基因集在细胞中的富集情况,适合大型基因集。日常标记一个小基因集,AddModuleScore够用;基因集有几十个基因时,我更推荐UCell:

# UCell包,先安装 # BiocManager::install("UCell") library(UCell) seu <- AddModuleScore_UCell(seu, features = cytotoxic_features, name = "CytotoxicUCell")

打分结果和AddModuleScore类似,但得到的分数解释性更稳,尤其适合在不同样本间比较时使用。

3.3 半自动注释细胞类型:让UMAP上的标签有生物学含义

如果你想标出的“类群”其实是某种细胞类型,那最应该做的是先完成细胞类型注释,再回到基因可视化,顺序别搞反。手工注释的流程一般是:找一组每个细胞类型对应的已知marker,用DotPlot或VlnPlot检查它们的表达模式,然后把每个cluster归因到对应的细胞类型。

举个例子,假设你怀疑数据里有T细胞、B细胞、NK细胞、单核细胞,可以构造一个marker集合:

markers_to_check <- list( Tcell = c("CD3D", "CD3E"), Bcell = c("MS4A1", "CD79A"), NK = c("NKG7", "KLRD1"), Monocyte = c("LYZ", "CD14") ) DotPlot(seu, features = markers_to_check, group.by = "seurat_clusters") + RotatedAxis()

看每个cluster在哪些marker上特异性高表达,然后将cluster号重命名为细胞类型名:

new_ids <- c("T细胞", "B细胞", "NK细胞", "单核细胞", "T细胞") names(new_ids) <- levels(seu) seu <- RenameIdents(seu, new_ids)

修改Idents之后,再画DimPlot(seu, label = TRUE),UMAP类群的标签就从“0、1、2、3、4”变成了可读的“T细胞、B细胞、NK细胞、单核细胞”。此时再回到最初的问题“目标基因在哪群细胞里”,答案可以写成“该基因主要富集在T细胞类群”,配一张按细胞类型着色的UMAP图,配合FeaturePlot,信息就完整了。

3.4 高亮目标群体:灰底加红点,比ColorScale更清晰

很多时候,我们不需要在整张UMAP上展示所有细胞类型,只希望“目标群体”一眼可见。这种情况下,最推荐的做法是把背景细胞设成灰色,目标群体设成醒目的颜色:

target_cells <- WhichCells(seu, expression = CD8A > 0) DimPlot(seu, cells.highlight = target_cells, cols.highlight = "firebrick3", cols = "grey85", pt.size = 0.8, sizes.highlight = 1.2)

这里WhichCells用Seurat的表达式语法,直接从对象里筛出满足CD8A > 0的细胞。cells.highlight接受一个细胞名向量,cols.highlight控制高亮颜色,cols控制背景颜色。这样画出来的图,信息层次非常清楚:灰色是全部细胞,红色是目标基因阳性细胞,边界在哪里一目了然。如果需要同时高亮多个条件,可以使用逻辑表达式,比如:

double_positive <- WhichCells(seu, expression = CD8A > 0 & MS4A1 > 0)

这个手法在我的项目里用的频率最高,也最容易被听众理解。

4. 多基因、多类群的批量标注:整理成一组能放进论文的图

真实项目里,你很少只关心一个基因。常见情况是一批候选基因——有的负责定义目标类群,有的用来排除其他类群,有的代表下游功能。这时候一张一张图画不是不行,但效率太低,而且不利于统一展示。批量处理的目标是:把多个基因对应的表达分布或类群高亮,整理成一张大图或一组对比图,同时输出一张量化表格作为支撑。

4.1 多基因FeaturePlot的循环生产与拼接

Seurat里批量画FeaturePlot最简单的方式是lapply遍历基因列表,再用patchwork拼接。patchwork是R里做图组合最顺手的包:

library(patchwork) features_of_interest <- c("CD8A", "MS4A1", "LYZ", "CD14") plot_list <- lapply(features_of_interest, function(gene) { FeaturePlot(seu, features = gene, pt.size = 1.0, cols = c("grey90", "firebrick3"), order = TRUE) }) combined_plot <- wrap_plots(plot_list, ncol = 2) ggsave("umap_marker_panel.pdf", combined_plot, width = 12, height = 10, units = "in")

这里有个细节:基因列表我用的是features_of_interest,但最终图中每个小图会由FeaturePlot自动显示基因名作为标题,不需要额外加标签。如果担心部分基因表达量太低、图上几乎全是灰色,可以在循环里对表达量做一个判断,自动跳过那些阳性比例低于某个阈值的基因,避免生成一堆“啥也看不见”的图。

拼接多基因FeaturePlot适合展示“这些marker在UMAP上的空间分布”,但注意不要期待它能直接回答“目标类群在哪”。它是一个探索性视图,真正用于结论输出时,还需要配合后面的高亮图或量化表格。

4.2 基因与类群对应关系表:比图更能说服人

在做标注类群的结论时,我习惯搭配一张基因-类群对应表。表格可以直接通过计算每个细胞类型的平均表达量和阳性比例获得:

# 按细胞类型计算目标基因的平均表达量 avg_expr <- AverageExpression(seu, features = features_of_interest, group.by = "celltype", assays = "RNA")$RNA write.csv(round(avg_expr, 3), "target_genes_by_celltype.csv")

如果你的Idents已经是细胞类型名,直接把group.by改成Idents(seu)即可。这张表的价值在于,它把UMAP图上的相对位置转换成了绝对数字,比如“CD8A在T细胞中平均表达量是3.2,在其他类群中不到0.1”。审稿人和同事都可以快速验证结论,不需要反复盯着图猜。

还可以结合FindAllMarkers输出每个类群的marker基因列表,从中挑出你感兴趣的基因,查看它具体在哪个类群的排序最靠前:

all_markers <- FindAllMarkers(seu, only.pos = TRUE, min.pct = 0.25)

然后筛选gene %in% features_of_interest的行,查看某个目标基因在哪些类群中被识别为差异表达marker,这比AverageExpression多一层统计检验,作为定量支撑更扎实。

4.3 组合验证:UMAP、点图、小提琴图放在一起

画图很容易自嗨,但“基因富集在某个类群”这个结论必须交叉验证。我的最低标准是:至少同时出示UMAP、DotPlot、VlnPlot三种视图。

p_umap <- FeaturePlot(seu, features = "CD8A", pt.size = 1.0, cols = c("grey90", "firebrick3"), order = TRUE) p_dot <- DotPlot(seu, features = "CD8A", group.by = "celltype") + RotatedAxis() p_vln <- VlnPlot(seu, features = "CD8A", group.by = "celltype")

p_umap展示空间位置,p_dot直观展示目标基因在哪些细胞类型中高表达以及阳性比例,p_vln展示表达量分布和离散程度。三个图拼接后放在一张展示页里,结论的说服力要比单独一张FeaturePlot强很多。图注方面,我一般会写“UMAP上CD8A的表达分布,以及它在各细胞类型中的富集情况”,避免直接替读者下结论,让图自己说话。

5. 图和“像样”之间只差这些细节:展示规范与常见伪象

到了这一步,功能上的“标出类群”已经实现了,但要把图放进报告、PPT或论文里,还有几个平时很容易踩的细节问题。这些问题不解决,你的UMAP图可能被审稿人质疑,或者团队内部讨论时被误解。

5.1 点大小、坐标轴比例和透明度的调节

UMAP图最常见的视觉问题是点太大太密,导致高亮群体被背景盖住。pt.size这个参数要根据细胞总数动态调整:细胞数在1万以内,pt.size = 1.5没问题;超过3万,我会降到0.8以下;超过10万,可能需要0.3到0.5。如果只截图不调整,图中的目标类群会糊成一坨,完全看不出边界。

另一个经常被忽略的点是UMAP的坐标轴比例。默认的DimPlot可能让X轴和Y轴的单位长度不一样,图形看起来被拉伸了。在出正式图时,我习惯加上coord_fixed(),让图形保持等比例:

DimPlot(seu) + coord_fixed()

这会让UMAP的结构看起来更紧凑真实,不会因为画面比例导致远近距离失真。

出图保存时,优先输出矢量图。PDF和SVG是首选,因为论文印刷时不会产生马赛克感。ggsave("umap_final.pdf", width = 8, height = 7, units = "in")保存的PDF,可以放到任何排版软件里无损缩放。

5.2 背景与目标颜色的对比策略

画高亮图时,很多人直接用默认配色,结果一整张图五种颜色,目标群体和无关群体都花里胡哨,反而看不出重点。我的建议是:目标群体永远是暖色高饱和色,其余全部置灰或浅色。

一种简单实现是前面提到的cells.highlight方案。另一种情况是,你想高亮的是已注释的某个细胞类型,而不是按阈值筛出的细胞,那就直接把该类型设为红色,其他类型设为灰色:

celltype_colors <- rep("grey85", length(levels(Idents(seu)))) names(celltype_colors) <- levels(Idents(seu)) celltype_colors["T细胞"] <- "firebrick3" DimPlot(seu, cols = celltype_colors, label = TRUE)

这样出来的图,读者第一眼就看到T细胞的位置,其他细胞自动退到背景。这个“背景压暗、前景高亮”的逻辑,适用于所有需要强调单一类群的场景,比试图在五种颜色里找出红色点要省力得多。

5.3 批次效应和聚类分辨率可能制造“假类群”

这个问题必须重点提醒。UMAP上看起来聚成一团的“类群”,未必是你真正关心的生物学群体。最常见的情况之一来自批次效应:如果你的数据来自多个样本或多种条件,且没有做好整合,UMAP上经常先按样本分堆,再按细胞类型分堆。这时候某个基因的阳性细胞在图上“聚成一团”,很可能只是因为它们来自同一个样本,而不是因为该基因定义了这类细胞。

排查方法很简单,画图时按样本拆开看:

DimPlot(seu, group.by = "sample", split.by = "sample", ncol = 3, label = TRUE)

如果每个样本的UMAP结构差异很大,说明批次效应还没有处理好,此时在合并图上高亮目标群体要非常谨慎。

另一个坑是聚类分辨率。如果某位同事调高了resolution,原本一个合理细胞类型被切成七八个碎块,然后你拿基因表达去对应“cluster 1、cluster 4、cluster 9”,结论就会非常碎且难以解释。反过来,resolution太低也会把不同细胞类型合并成一个cluster,目标基因富集情况被“稀释”。我的建议是:在确定“目标类群”之前,先用一组核心marker检查分辨率是否合适——比如T细胞marker、B细胞marker是否都集中在各自单独的大块里。如果它们混在一起或者被彻底分开,就需要调整resolution重新聚类,再继续后续可视化。这个步骤属于基础质量控制,却是在“标注基因类群”时最常出问题的源头。

5.4 如果项目用的是Python:scanpy的快速对照

现在不少单细胞流程已经完全跑在Python里了。scanpy做UMAP和基因可视化思路与Seurat几乎一一对应:

import scanpy as sc import matplotlib.pyplot as plt # 基础UMAP,按细胞类型着色 sc.pl.umap(adata, color="celltype") # 按基因表达量着色 sc.pl.umap(adata, color="CD8A", cmap="Reds", order=True)

高亮某个群体的逻辑也类似,只需要把目标细胞的颜色映射写进一个自定义列里,或者用adata.obs里的布尔列:

# 先创建一个布尔列 adata.obs["target_group"] = (adata.obs["CD8A_raw"] > 0).astype(str) # 画UMAP,目标群体单独着色 sc.pl.umap(adata, color="target_group", palette=["grey85", "firebrick3"])

本质是一样的:先计算每个细胞是否属于目标群体,再把群体归属映射到点颜色上。无论R还是Python,标注的核心逻辑都是“表达信息 -> 群体定义 -> 颜色映射”。

6. 我习惯保留的流程清单和几个容易踩的小细节

写到这里,我把整个工作流在脑子里过了一遍,觉得有几个小经验值得单独列出来,它们不见得写在任何手册里,但实操中非常影响效率和结果。

第一,在做任何UMAP可视化之前,先把Idents整理成有序的、有注释的状态。如果Idents还是原始的seurat_clusters数字编号,后面每个可视化步骤都要额外分组一次,非常累。我用的是“先注释细胞类型,再可视化”的顺序,这样DimPlot、FeaturePlot、DotPlot全部可以基于同一种分组逻辑,不会因为换一种图就换一种分组方式。

第二,FindAllMarkers这类差异表达分析在大型数据上跑起来很慢,但如果只是为了确定目标基因在哪些类群富集,直接跑AverageExpression加阳性比例统计往往更快,结果也足够用。差异表达分析用在正式marker筛选阶段,不用每次可视化都全量跑一遍。

第三,有关“目标基因阳性细胞”的定义,体现在代码里就是阈值选择。用expression = CD8A > 0是最宽松的标准,只要是深度够、被捕获到表达的都算阳性。如果某个基因背景表达较高,用> 0会把一大批背景细胞也选进来,这时可以上调阈值,比如按高表达细胞的表达量分位数来切:

expr_cd8a <- FetchData(seu, vars = "CD8A")[[1]] thr <- quantile(expr_cd8a[expr_cd8a > 0], probs = 0.5) # 取阳性细胞的中位数表达 target_cells <- WhichCells(seu, expression = CD8A > thr)

这个阈值不是固定的,取决于你的生物学问题。想粗看分布就取0,想标出真正高表达群体就往上调。关键是阈值一旦定了,写论文时需要注明,保持可重复性。

最后,我几乎每一张“基因对应类群”的结论图,都会配套至少三样东西:FeaturePlot展示表达连续性,高亮图展示目标群体边界,DotPlot或VlnPlot展示类群特异性。这三样东西放在一起,无论给谁看都不容易产生歧义。这个方法还有一个好处,它逼我自己先确认目标类群是不是真的存在,而不是看着颜色深浅自己脑补一个结论。

单细胞可视化的工具更新很快,但“基因表达信息 -> 细胞群体定义 -> UMAP上明确标注”这条主线一直没有变。理解了这条主线,无论是换包、换语言还是换降维算法,你都能快速迁移。

相关新闻

如何系统盘点个人技能:三层结构、0-5级量表与提升路径

如何系统盘点个人技能:三层结构、0-5级量表与提升路径

先说结论&#xff1a;这是一件大多数人根本没做对的事“skills”这个词&#xff0c;估计是简历里出现频率最高的单词。招聘JD里写“requirements”&#xff0c;候选人就在自我评价里罗列七八项技能&#xff1b;年终复盘的时候&#xff0c;大家对着空白的表格发呆&#xff0c;想…

2026/10/21 4:27:47 阅读更多 →
室内机器人地图模块实操:栅格建图、代价地图与导航避障

室内机器人地图模块实操:栅格建图、代价地图与导航避障

做机器人项目这几年&#xff0c;地图模块给我的印象可以用四个字概括&#xff1a;又爱又恨。爱它&#xff0c;是因为形态直观&#xff0c;一张栅格图导出来&#xff0c;客户和老板都能看懂场景结构&#xff1b;恨它&#xff0c;是因为只要导航、定位出了问题&#xff0c;第一个…

2026/10/21 4:27:47 阅读更多 →
技能管理:把个人能力变成可复利、可迁移的体系

技能管理:把个人能力变成可复利、可迁移的体系

聊聊 skills&#xff1a;如何把你的本事变成一套可复利、可管理、可迁移的体系这么多年我带过不少人&#xff0c;也见过太多人把"技能"挂在嘴边&#xff0c;但真要问他"你会什么"&#xff0c;他能说出一堆名词&#xff0c;却说不清每一项到底值多少钱、能解…

2026/10/21 4:27:47 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →