Seurat 基础:标准化、降维、聚类

说到单细胞分析,标准化、降维和聚类这三步,就好比盖房子的地基、框架和墙体。地基没打好,后面全歪。我今天就跟大家聊聊这三件事在 Seurat 里到底怎么做,顺便透几个我亲手踩过的坑。

标准化——你的数据真的“正”了吗?

原始 Count 矩阵直接往下跑 PCA?千万别。基因表达量在细胞间差异巨大,有些基因就是表达量高,不标准化的话主成分全被那些“大嗓门”基因霸占。

Seurat 里最常用的标准化函数是 NormalizeData(),默认走的是 LogNormalize 方法:每个细胞里基因的 Count 除以该细胞的总 UMI 数,乘以一个缩放因子(默认 10000),再取自然对数。说白了就是:

normalized_count = log1p(count / total_umi * scale_factor)

我负责过一个 10x 多组学整合项目,当时有个通道的 UMI 总数是其他通道的两倍,LogNormalize 一做完,所有基因的表达分布就拉平了。真心推荐新手先别急着换方法,默认的 LogNormalize 够稳。

如果你用 SCTransform 替代 NormalizeData,效果类似但正则化更精细。不过 SCTransform 跑下来要花时间,我一般只在 10 万细胞以上的大项目里开。

√ 标准化常见选择

方法原理适用场景
LogNormalize(默认)Count → 每细胞深度标准化 → ln(1+x)大多数常规分析,速度快
SCTransform正则化负二项回归,去除 sequencing depth 影响高深度、批次效应明显的项目
CLR中心化对数比变换ATAC-seq 或蛋白标记数据

ScaleData——别让高变基因“独裁”

标准化之后还有一步 ScaleData(),把每个基因的表达值缩放为 z-score。这步很多新手会跳过,觉得“反正标准过一遍了”。我曾经也跳过,然后跑去问同事为什么 PCA 第一主成分全是线粒体基因……真相是标准化只处理了细胞间差异,没处理基因间的量纲差异。

而且你想想看,PCA 是基于协方差矩阵的,量的范围不同,大数值基因直接就主导了。ScaleData 把每个基因的均值拉成 0,方差变成 1,顺便还能回归掉你不想看到的变量(比如线粒体比例)。

# 标准化后缩放
pbmc <- NormalizeData(pbmc)
pbmc <- FindVariableFeatures(pbmc, selection.method = "vst", nfeatures = 2000)
pbmc <- ScaleData(pbmc, vars.to.regress = c("percent.mt"))

⚠ 注意 如果细胞数 < 5 万,建议把 FindVariableFeatures 和 ScaleData 的顺序做对。先找高变基因再缩放,否则全基因缩放耗内存还慢。我有个同事一次性缩放 3 万个基因,8G 内存的机器直接崩了。

降维——从高维到看得见

跑完 ScaleData,下一步是降维。Seurat 主推 RunPCA()。我习惯先设 npcs = 50,后面用 ElbowPlot 挑主成分个数:

pbmc <- RunPCA(pbmc, npcs = 50, verbose = FALSE)
ElbowPlot(pbmc, ndims = 50)

肘部图里的“肘”在哪里,就选几个。说实话我很少严格卡在肘部值——通常会多选 2-3 个 PC,让下游聚类更宽容。你想想,少选几个可能丢掉稀有的细胞亚群,多选几个噪声大不了用 graph-based 聚类来容错。

💡 避坑 如果数据有很强的批次效应,建议先做 RunHarmony 或用 IntegrateData 去除批次,再跑 PCA。我曾经在 PBMC 数据上直接跑 PCA,结果 PC1 全是 Batch 标签,细胞分群按实验批次排的,跟生物学毫无关系。

聚类——把细胞“分堆”

降维后聚类的核心是 FindNeighbors() + FindClusters()。前者基于选定的 PC 构建 KNN 图,后者用 Louvain 或 Leiden 算法做社区检测。

pbmc <- FindNeighbors(pbmc, dims = 1:15)
pbmc <- FindClusters(pbmc, resolution = 0.5)

resolution 是个调参滑钮:值大分群多,值小分群粗。我一般先设 0.5 跑一遍,看看细胞有没有明显的过分离 / 欠分离,再微调。比如有个 T 细胞数据集,分辨率 0.8 分出了 10 个群,但每个群 Marker 基因高度重叠——明显是过分割了。

关于选择分辨率的经验,我教大家一个笨方法:在 FindClusters 外面套一层 for 循环,遍历 0.2 到 1.2,每次用 clustree 包看树状图,哪个分辨率既能区分已知类型又不产生太多孤点,就用哪个。这个方法我每个项目至少用一次。

用 Snakemake 串联这三步时的注意事项

如果你们正在做 Snakemake 集成,下面几个细节我建议关注:

  • 标准化之后的 SeuratObject 占内存明显增大。Snakemake 里记得用 temp() 或 protected() 管理中间文件,不然硬盘被撑爆。
  • ScaleData 回归变量(如 percent.mt)的列名必须提前算好,写在配置文件里,别硬编码。
  • PCA 的 npcs 参数可以在 Snakefile 的 config 里统一管理,方便不同数据集调参。

流程总览

Seurat 标准化 → 降维 → 聚类流程 NormalizeData ScaleData RunPCA ElbowPlot FindNeighbors FindClusters 调整 resolution (clustree 校验) 图中所有节点颜色对应:蓝色=标准化,绿色=缩放,红色=PCA,紫色=肘部图,橙色=建图,青色=聚类

这张图把每个步骤的顺序、依赖关系画得清清楚楚。Snakemake 的 rule 就按照这个层级写,每一步的输入输出都链起来。比如 rule scale_data 的输入是 normalized 的 RDS,输出是 scaled 的 RDS,这样重跑时只有上游变了才会刷新。

小结

标准化、降维、聚类,每个环节的细节我都讲到了。ScaleData 不能省,ElbowPlot 要配合多选几个 PC,resolution 要像调显微镜一样一点点拧。记住一句话:好的下游结果,一半取决于上游参数调对了。你们自己动手做一遍,比看我写十遍都有用。