4. Seurat CCA算法原理:典型相关分析寻找共享结构、锚点识别与变换
这一章咱们来啃硬骨头——Seurat的CCA整合流程。说实话,我刚接触单细胞数据整合那会儿,看到CCA这三个字母就头大。后来在项目里反复调参、踩坑,才慢慢摸清楚了它的脾性。
CCA整合的核心,可以拆成三个步骤:寻找共享结构、锚点识别、数据变换。我带你一个一个过。
4.1 典型相关分析:核心思路
典型相关分析,说白了就是找两个数据集之间的“共同语言”。
你看,两个不同批次的单细胞数据,基因表达空间可能完全不同。CCA要做的,就是给每个数据集找一个投影方向,让投影后的数据相关性最大。
举个我项目里的例子。有一次我整合人胰腺的β细胞数据,一个批次是Smart-seq2,另一个是10x Genomics。这两个平台的基因覆盖和表达量级完全不一样。用CCA投影后,它们在高维空间里自动找到了一个共享的低维表示——这意味着,不管数据来自哪个平台,只要细胞类型相同,它们在CCA空间里的位置就应该接近。
数学上,CCA求解的是这样一组基向量:
最大化:corr(U^T * X, V^T * Y)
其中:
X 是数据集1的基因表达矩阵(经过标准化)
Y 是数据集2的基因表达矩阵
U 和 V 分别是两个数据集的典型相关系数向量
corr() 是皮尔逊相关系数
为什么要最大化相关性?你想想看,如果两个数据集里同一种细胞类型的表达模式高度相似,那它们在各自投影空间里的相关性就高。CCA正是利用这个特性,把细胞类型这个“共享结构”从批次噪声里剥离出来。
4.2 寻找共享结构:CCA降维与选择
找到典型相关维度后,我们通常会保留前20-30个维度。这个数量怎么定?我一般看维度对应的相关性分数。
# Seurat中的标准调用
library(Seurat)
combined.obj <- FindIntegrationAnchors(
object.list = list(obj1, obj2),
dims = 1:20,
k.anchor = 5,
reduction = "cca"
)
# 查看各维度的相关性
cca.embeddings <- combined.obj@reductions$cca
head(cca.embeddings@feature.loadings[, 1:3])
这里有个坑要注意。CC的维度相关性是递减的——第一个维度相关性最高,后面的越来越小。我一般保留相关性大于0.5的维度,但实际操作中,20个维度基本够用。
4.3 锚点识别:MNN与打分机制
找到共享结构后,下一步就是锚点识别。Seurat用的是相互最近邻(MNN)技术。
具体怎么做呢?
- 在CCA空间里找近邻——对数据集1里的每个细胞,在数据集2里找k个最近邻。
- 反向找近邻——对数据集2里的每个细胞,在数据集1里也找k个最近邻。
- 取交集——如果两个细胞互为近邻,它们就构成一个锚点对。
为什么要用MNN?因为单纯用最近邻(NN)会引入大量假阳性。我遇到过这种情况:一个数据集里的细胞A在另一个数据集里找到了10个近邻,但反向搜索时,这些近邻里只有2个认为A是它们的近邻。那2对才是真正的生物对应关系,剩下的8对只是批次噪声造成的假象。
锚点的质量不是对等对待的。Seurat会给每个锚点打分,分越高越可靠。打分依据包括:
| 评分因子 | 说明 |
|---|---|
| 距离一致性 | 锚点对在CCA空间里的距离越小,分越高 |
| 近邻共享比例 | 两个细胞共享的邻居越多,分越高 |
| 局部结构保持 | 锚点对周围的局部近邻结构越一致,分越高 |
你看看这个打分机制,其实挺巧妙的。它不只是看两两细胞是否匹配,还看了它们周围的局部邻居是否也匹配。这保证了锚点不是孤立的对应关系,而是整个局部结构的对齐。
4.4 数据变换:从锚点到整合矩阵
锚点识别出来后,最后一步就是数据变换。这个步骤的作用是把所有数据集变换到一个共享的表达空间里。
变换的核心是一个加权公式:
整合后的表达值 = Σ(锚点权重 × 原始表达值) / Σ(锚点权重)
其中锚点权重 = exp(-d² / σ²)
d 是锚点对在CCA空间里的距离
σ 是带宽参数,控制权重衰减速度
这个公式的意义是:每个细胞的校正后表达值,是它周围锚点对的加权平均。距离近的锚点权重高,远的权重低。
我刚开始学的时候有个疑惑:为什么不用一个统一的校正因子?后来自己动手做了一次模拟实验才发现,不同细胞受到的批次效应影响是不一样的。有些亚群可能完全不受批次影响,有些则被严重扭曲。用锚点加权的方式,可以对每个细胞做精细化的校正。
4.5 完整流程示意图
下面这张图总结了CCA整合的完整流程。我建议你看完后再回到前面读一遍,会有更深的理解。
从图上你能看到,整个流程是线性的,但每个步骤内部都有很多细节。我特别想强调的是:锚点识别是决定整合质量的关键。如果锚点识别不准,后续的变换再精细也是白搭。
好了,这一章的内容就到这里。CCA的数学原理听起来复杂,但落实到代码上并不难。你只要记住核心逻辑,遇到问题时就能快速定位。如果整合效果不好,先检查CCA维度选择,再检查锚点质量,最后调变换参数——这个排查思路在项目里帮我省了不少时间。