3.5 PCA 降维、UMAP 与邻居图 本节摘要:降维三件套分工明确——PCA 把两千个基因压成几十个主成分(去冗余、留主要变化),邻居图在这几十个维度上连接每个细胞与它最近的邻居(聚类的真正地基),UMAP 把图摊平成二维图(只管好看,不管计算)。本节讲清三者的顺序、主成分个数的选法,以及"UMAP 距离不能当真"这条铁律。 从高维到低维 即便只留了两千个高变基因,数据的维度仍远超人类直觉能处理的范围,而且这两千个基因高度相关——T 细胞的一串基因总是一起动、单核细胞的另一串也抱团变化。PCA 的作用正是把这种冗余压掉:它寻找数据方差最大的方向,把两千个相关基因重组成几十个互不相关的主成分,前几个主成分承载了绝大部分细胞间差异。
本节摘要:降维三件套分工明确——PCA 把两千个基因压成几十个主成分(去冗余、留主要变化),邻居图在这几十个维度上连接每个细胞与它最近的邻居(聚类的真正地基),UMAP 把图摊平成二维图(只管好看,不管计算)。本节讲清三者的顺序、主成分个数的选法,以及"UMAP 距离不能当真"这条铁律。
即便只留了两千个高变基因,数据的维度仍远超人类直觉能处理的范围,而且这两千个基因高度相关——T 细胞的一串基因总是一起动、单核细胞的另一串也抱团变化。PCA 的作用正是把这种冗余压掉:它寻找数据方差最大的方向,把两千个相关基因重组成几十个互不相关的主成分,前几个主成分承载了绝大部分细胞间差异。压完之后,每个细胞从"两千维向量"变成"几十维向量",聚类计算的成本和噪声同时下降。
主成分取多少个,是个有标准答案的开放问题——取少了丢信号,取多了引噪声。两条实操判据:一是碎石图(每个主成分解释的方差占比连线),找曲线从陡转平的肘部;二是累计方差贡献,前若干个主成分累计解释百分之十到二十就属正常区间,硬要凑到八成毫无必要。经验落点是十到五十个:两千七百细胞的经典 PBMC 数据取十个就很稳,几万细胞的复杂组织常取三十到五十。取值在这个区间内小幅浮动,聚类结果通常不敏感——如果敏感,说明图谱本身不稳,问题不在主成分数。
import scanpy as sc sc.pp.pca(adata, n_comps=50, use_highly_variable=True) sc.pl.pca_variance_ratio(adata, n_pcs=50) # 碎石图,找肘部 sc.pp.neighbors(adata, n_neighbors=15, n_pcs=20) # 邻居图:聚类的地基 sc.tl.umap(adata) # 只为可视化
Seurat 侧一一对应:RunPCA(pbmc, features = VariableFeatures(pbmc)) 出图用 ElbowPlot(pbmc),然后 FindNeighbors(pbmc, dims = 1:20)、RunUMAP(pbmc, dims = 1:20)。注意两边的 dims 参数指的是主成分编号区间,图形化探索与聚类共用同一组主成分,保证"看到的"和"算出来的"是同一个空间。
很多人以为聚类是在 UMAP 图上做的,顺序恰好相反。邻居图在高维主成分空间里为每个细胞连接最近的若干个邻居(n_neighbors 常取十到三十,默认十五),把离散的细胞点织成一张"谁和谁相似"的图。后续的 Leiden 聚类在这张图上找连接紧密的社区,UMAP 也只是把这张图投影到二维。这张图决定了聚类与可视化,而它自己只依赖两个参数:用几个主成分、每个点连几个邻居——所以"主成分个数怎么选"才如此重要,它通过邻居图一路传导到最终的分群。
n_neighbors 的取值影响图谱的"颗粒感":取得小,图谱细碎、局部结构凸显;取得大,全局结构更平滑、小群容易被并进大群。没有特殊理由就用默认十五,把调参的精力留给分辨率。

UMAP 是把高维邻居图压成二维平面的投影算法,两个参数决定图的观感:min_dist 控制同群点的松紧(小则紧凑成团,大则松散),spread 控制整体铺开的幅度。默认值适合大多数场景,调参主要是为了报告里更好看,不是为了"更对"。两条铁律必须立起来:其一,UMAP 图上两点之间的距离没有定量含义,二维图为了摆下高维结构做了大量变形,远处的两点可能在高维空间里很近,不能凭图说"这两种细胞差别有多大";其二,不要在 UMAP 坐标上重新聚类或算轨迹,所有定量结论都建立在主成分空间与邻居图上,UMAP 只是给人看的窗口。
误用之外还有个常见疑问值得回答:UMAP 两团粘连分不开,是不是聚类失败了?不一定——粘连可能只是投影的视觉效应,也可能提示两群确实共享转录程序。判断办法是回到邻居图层面看连通性,或者换个随机种子重跑 UMAP:图形大变而聚类标签稳定,说明分群可靠、图在开玩笑;聚类标签跟着乱,说明分群本身不稳,回去查质控与主成分数。
邻居图织好后,聚类只差一步:在图上找"社区"——内部连接密集、之间连接稀疏的细胞团。主流算法是 Leiden(Louvain 的改进版,保证社区内部连通),它只暴露一个真正需要理解的参数:resolution(分辨率)。分辨率调的是社区切分的粗细倾向,值越低团越少越粗,值越高团越多越细。零点几到一点几是常规扫描区间:经典 PBMC 数据分辨率一点零左右能出七八群,恰好对应主要细胞类型。
调分辨率不是找一个"最优值",而是找一个"解释得住"的值。实操做法是从零点四起往上扫(零点四、零点八、一点二、一点六),每个分辨率下做两件事:数一数分出多少群,抽查几群的标记基因是否自洽。好图谱的特征是分辨率提高时,大群沿着有生物学意义的方向裂开(比如 T 细胞群裂成 CD4 与 CD8);坏图谱的特征是凭空冒出几群没有任何标记基因支撑的碎片。碎片出现,就把分辨率退回去。
sc.tl.leiden(adata, resolution=1.0, key_added="leiden") # 扫描时换不同的 key 存下来,便于对照 for r in [0.4, 0.8, 1.2]: sc.tl.leiden(adata, resolution=r, key_added=f"leiden_r{r}") sc.pl.umap(adata, color=["leiden_r0.4", "leiden_r0.8", "leiden_r1.2"], ncols=3)
Seurat 侧是 FindClusters(pbmc, resolution = c(0.4, 0.8, 1.2)),结果存在 meta.data 的多个 leiden 列里,DimPlot 换列显示即可对照。定下分辨率后,把它写进分析记录——图谱是参数的函数,换一个分辨率就是另一张图谱,可复现性从这里开始。
降维后多花一步检查能让后面少返工:把每个主成分与质控协变量(总分子数、线粒体比例)算相关,再扫一眼各主成分的顶部载荷基因。发现某主成分的载荷被线粒体基因或核糖体基因霸占,说明技术噪声抢到了一个维度,回头做定点回归或把它们从高变名单里剔掉;发现载荷全是核糖体蛋白基因这类"平台信号",同理处理。主成分体检通过后再进聚类,图谱的每个维度都有明确的身份,排错时也就有了坐标。