CCA 在单细胞中的应用:Seurat 中的 CCA 流程、参数设置、注意事项

CCA,全称 Canonical Correlation Analysis,中文叫典型相关分析。说白了,它是一种衡量两组数据之间“共同变异”的方法。在单细胞领域,我们用它来寻找不同批次之间细胞状态的对应关系。

我记得我第一次用 Seurat 整合数据,面对一堆参数完全懵了。后来慢慢摸索,才明白这玩意儿其实挺直接的。今天咱就聊聊,怎么在 Seurat 里用好 CCA。

Seurat 里 CCA 的核心流程

Seurat 的整合框架,本质是两步:先找锚点,再做整合。CCA 主要负责第一步——找锚点。

大致流程如下:

  1. 标准化与特征选择:每个样本单独跑 NormalizeData + FindVariableFeatures。
  2. 跨样本 CCA:用 SelectIntegrationFeatures 选出共享的高变基因,然后 RunCCA 计算典型相关。
  3. 锚点识别:通过 FindIntegrationAnchors 在 CCA 低维空间里探测“互相最近邻”的细胞对。
  4. 数据整合:IntegrateData 利用这些锚点,把表达矩阵校正到同一空间。

关键点:你不需要手动调 CCA 的数学细节,Seurat 已经封装好了。但参数踩坑可是家常便饭。

参数设置:这几个值得盯着调

Seurat 的整合函数 FindIntegrationAnchors 和 IntegrateData 里有一堆参数。我挑三个最常出问题的,逐一说说我的踩坑经历。

参数默认值作用我的建议
k.anchor 5 每个锚点周围用于计算权重的邻居数 样本量小(<5000)时可以用 3-5;大数据(>5万)可以提到 10-20。太小会过拟合,太大容易模糊批次边界。
k.filter 200 过滤掉最多匹配锚点数量的上限 默认值一般够用。但我遇到过异质性很大的实验设计,比如肿瘤样本和正常样本差异过大,需要把 k.filter 降到 100 左右,否则会引入很多假阳性锚点。
k.score 30 计算锚点得分时考虑的邻居数 这个参数影响锚点的“可信度筛选”。我习惯保持 30,除非你有明确理由。

警告:不要把 k.anchor 设得比 k.filter 大。逻辑上讲不通,程序会报错,但很多人会写反。我自己就犯过这个低级错误,排查了半小时才发现参数顺序错了。

在实际项目中,CCA 的预处理坑

CCA 对输入数据非常敏感。我曾经处理过一个包含 10x v2 和 v3 两个平台的数据,直接丢进 RunCCA,结果整合效果很差。后来发现,两个版本的基因捕获效率不同,导致部分基因的方差分布差异巨大。

应该怎么做?

  • 确保数据已经标准化:建议用 SCTransform 代替 LogNormalize,它能更好地处理不同检测深度的批次。
  • 特征数量要合理:SelectIntegrationFeatures 默认取 2000 个高变基因。如果你研究的细胞类型非常稀有,可以考虑增加到 3000-5000,否则稀有群体可能被淹没。
  • 维度选择:RunCCA 默认只保留 50 个典型相关维度(1:50)。你想想看,如果样本差异特别大,这 50 维可能不够。可以设置 num.cc = 100,但别超过样本内细胞数的 1/10。

避坑指南:我曾经一次性处理 8 个样本,每一对样本都独立跑 CCA,结果内存直接爆表。后来才学会用 SelectIntegrationFeatures + PrepSCTFindMarkers 这种 pipeline,把特征选择和归一化统一处理,效率高多了。

一个简单的代码示例

下面是我常用的一套流程,适用于两个样本的整合。注意配置里的注释:

# 标准化(以 SCTransform 为例)
s1 <- SCTransform(s1, verbose = FALSE)
s2 <- SCTransform(s2, verbose = FALSE)

# 整合特征选择
features <- SelectIntegrationFeatures(object.list = list(s1, s2), nfeatures = 3000)

# 准备锚点
anchors <- FindIntegrationAnchors(
  object.list = list(s1, s2),
  anchor.features = features,
  normalization.method = "SCT",
  k.anchor = 10,       # 因为两个样本都超过 1 万细胞
  k.filter = 150,      # 我们担心差异太大,所以降低过滤阈值
  k.score = 30,
  reduction = "cca",   # 明确指定用 CCA
  dims = 1:50
)

# 整合
combined <- IntegrateData(anchorset = anchors, normalization.method = "SCT", dims = 1:50)

这段代码跑完后,combined 对象里的整合矩阵就可以直接用来降维和聚类了。

CCA 整合的适用场景与局限

CCA 不是万能的。我自己的经验是,它特别适合以下情况:

  • 批次效应表现为主成分旋转差异,而不是单纯的均值偏移。
  • 细胞类型在两个批次中都存在,CCA 可以用共享的共变异结构锚定它们。
  • 样本量差异不大。如果一个样本只有 500 细胞,另一个有 5 万,CCA 的锚点倾向于集中在数据量大的批次,稀有批次很难配对。

反过来,如果两个批次的细胞组成完全不同(比如你硬把 PBMC 和肿瘤组织整合),CCA 基本没用——它找不到有意义的共同变异。这时候还不如用 MNN 或者 scVI。

SVG 图:CCA 锚点识别与整合逻辑

下面这张图能帮你直观理解 CCA 在 Seurat 里是怎么工作的。

Seurat CCA 整合流程 批次 A (原始数据) 批次 B (原始数据) 标准化 标准化 CCA 低维空间 (dims=1:50) 锚点识别 (FindIntegrationAnchors) 整合表达矩阵 (IntegrateData) k.anchor 控制局部 邻居权重 k.filter 过滤异常锚点

几个需要注意的细节

嗯,这里还要补充几点现场经验:

  • 特征数量别贪多:我见过有同学直接选 5000 个基因。说实话,CCA 的奇异值分解速度会成倍增加,而且很多噪音特征会拉低信噪比。2000-3000 足够了。
  • 降维范围别死用默认:RunCCA 默认拿前 50 维。但如果你两个样本的细胞类型差异很大,可以试试 dims = 1:30 或 1:20。选多少合适?跑完 CCA 后画个 ElbowPlot 看一下。
  • 内存使用:整合步骤 IntegrateData 会生成一个很大的矩阵。如果你的机器只有 32GB 内存,处理 10 万个细胞就会很吃力。可以考虑分批次整合,或者用 merge 先合并再细调。

一句话总结:CCA 在 Seurat 里就像一个“翻译官”,用共享的基因协方差模式把不同批次拉到一个坐标系。参数调得好,翻译就准;调不好,就是鸡同鸭讲。