3、标准化与降维聚类:NormalizeData、FindVariableFeatures、ScaleData、RunPCA、RunUMAP、FindClusters

好,咱们开始真刀真枪地处理数据了。前面读进来的表达矩阵还是原始 count,基本没法直接比较——有的细胞测序深度高,有的低,直接降维聚类会乱成一锅粥。所以官方推荐了一串流程,我习惯叫它“六步曲”。

3.1 流水线概览

这六个函数按顺序调用,每一步都在为下一步做准备。我刚开始用 Seurat 时觉得 Step 3 可以跳过,结果 PCA 图全是离群点…后来老老实实走完整套,再没出过幺蛾子。

步骤函数核心目的
1NormalizeData消除测序深度差异,让细胞在相同尺度上可比
2FindVariableFeatures挑选信息量最大的基因(通常 2000 个)
3ScaleData中心化 + 缩放,给每个基因均值为 0 方差为 1
4RunPCA线性降维,提取主成分
5RunUMAP非线性降维,用于 2D 可视化
6FindClusters基于图社区检测,划分细胞亚群

小技巧:把这六步封装成一个函数或脚本,每次跑新数据集直接调用,少敲好几行代码。

Seurat 六步曲流程图 NormalizeData FindVariableFeatures ScaleData RunPCA RunUMAP FindClusters PCA 结果同时服务于 UMAP 和聚类,但聚类本质上基于 PCA 降维后的空间

3.2 NormalizeData:让数据可比

说白了就是给每个细胞做个“归一化”。默认方法是 LogNormalize——先算出每个基因占该细胞总 UMIs 的比例,再乘上缩放因子(默认 10000),最后取 log1p。

pbmc <- NormalizeData(pbmc, normalization.method = "LogNormalize", scale.factor = 10000)

为什么非要这一步?你想想看——一个测了 5000 个 UMIs 的细胞和一个只测了 500 个的,原始 count 根本没法比。归一化之后,单位变成了“相对表达量”,这才公平。我个人习惯直接用默认参数,除非你的数据是单细胞核 RNA 或者有特殊批次效应,才考虑换 sctransform。

曾经踩过的坑:我有一次把 NormalizeData 和 ScaleData 的顺序搞反了——先缩放了 count 再归一化,结果所有基因的方差都爆炸,PCA 根本跑不下去。记住,归一化必须第一步。

3.3 FindVariableFeatures:筛选高变基因

不是所有基因都有区分细胞的潜力。比如那些在所有细胞里都不表达的基因,或者表达量恒定的管家基因,它们对聚类没贡献。我们只挑“最会变”的 2000 个基因。

pbmc <- FindVariableFeatures(pbmc, selection.method = "vst", nfeatures = 2000)

这里有个小心得:默认的 vst(方差稳定变换)效果通常好于旧版的 mean.var.plot。不过如果你的数据特别稀疏(比如 10x v3 上清液),可以试试 selection.method = "mvp" 。我比较过几次,vst 对高表达基因的把控更稳定。

3.4 ScaleData:中心化与缩放

这一步把每个基因的表达值转变为 Z-score——均值为 0,标准差为 1。为什么需要?因为 PCA 和后续聚类对量纲敏感,如果某个基因表达量本身就大(比如线粒体基因),它会主导主成分。缩放后所有基因平等竞争。

pbmc <- ScaleData(pbmc, features = rownames(pbmc))

注意上面代码里的 features = rownames(pbmc)——如果你只传了高变基因,那 ScaleData 只缩放那 2000 个。但为了后面做热图时能展示所有基因,我通常全量缩放,内存够用就行。实话说,全量缩放后你要小心一点:如果细胞数量特别多(几万),这一步会慢到让你想骂人,可以只缩放 VariableFeatures。

加速技巧:ScaleData(pbmc, vars.to.regress = "percent.mt") 能同时回归掉线粒体比例等混淆因素,节省一轮操作。

3.5 RunPCA:降维的核心

PCA 把原始几千个基因压缩成几十个互不相关的主成分。每个主成分本质上是基因表达量的线性组合,按贡献的方差从大到小排列。我们用前 20–50 个主成分就能代表大部分信息。

pbmc <- RunPCA(pbmc, features = VariableFeatures(object = pbmc), npcs = 50)

为什么是 50?其实默认是 50,但并不是越多越好。我一般先跑 50,然后用 ElbowPlot 看拐点——那个“肘”的位置就是有效主成分个数。在我的项目里,大多数 10x 数据集前 15–20 个 PC 就够了,再多就开始加噪声。

3.6 RunUMAP:可视化降维

PCA 是线性降维,对高维结构的还原能力有限。UMAP 是非线性方法,擅长把高维流形摊开到二维平面,同时保持局部邻居关系。它的输出就是我们在文章里最常看到的那种绚丽细胞散点图。

pbmc <- RunUMAP(pbmc, dims = 1:20)

这里的 dims 直接对应你上一步 PCA 选择的主成分序号——我习惯先用 ElbowPlot 确定有效维度,再传给 UMAP。曾经试过不筛选直接传 1:50,结果图里所有细胞挤成一坨,完全看不出结构。

关键参数:n.neighbors(默认 30)控制局部 vs 全局平衡,小值强调局部;min.dist(默认 0.3)控制点之间最小距离。调参时可以从默认开始,微调即可。

3.7 FindClusters:发现细胞亚群

最后一步,把降维后的细胞聚成一个个“社区”。Seurat 用的是基于图的共享最近邻(SNN)算法,然后通过 Louvain 或 Leiden 算法做社区检测。分辨率(resolution)参数决定了聚类的粗细程度。

pbmc <- FindNeighbors(pbmc, dims = 1:20)
pbmc <- FindClusters(pbmc, resolution = 0.8)

分辨率是个值得唠叨的参数。0.5 是默认,对 PBMC 一般能分出 8–10 个群。我建议从 0.2 到 1.2 多跑几个分辨率,然后用 clustree 包可视化层次结构,直观判断哪个分辨率最合理。

嗯,到这步你就拿到了细胞的聚类标签。后面就可以做 marker 基因鉴定、差异表达分析和热图解读了。不过那些是后面章节的事,咱们先把这六步练扎实。