BBKNN 简介:从批量效应的痛点说起

单细胞测序技术发展到今天,实验做得越来越便宜,数据也越堆越多。但有个麻烦始终绕不开——批次效应。同一套细胞类型,换一个实验批次、换一台仪器,甚至换个操作员,表达谱分布就偏移了。我刚开始做单细胞整合时,拿两个批次的 PBMC 数据直接合起来降维,结果每个批次自己聚成一团,真正的生物学差异反而被淹没了。嗯,当时真是头疼。

后来开始研究各种整合方法。Seurat 的 CCA、Harmony、scANVI……各有各的长处,但有些方法跑起来太慢,有些对数据量特别敏感。直到我遇见了 BBKNN——Batch Balanced KNN。它不搞复杂的全局校正,而是从图构建的源头切入,用一种非常巧妙的采样思路解决了问题。

1. 背景:为什么需要 BBKNN?

单细胞数据的整合,本质上是想抹掉技术噪音,保留生物信号。常见的做法有两大类:

  • 数据校正型:比如 ComBat、Seurat CCA、Harmony,直接修改表达矩阵或低维嵌入,让不同批次的数据对齐。缺点是容易过度校正,丢失真实差异。
  • 图构建型:比如 BBKNN、scANVI,不直接改数据,而是在构建邻居图时考虑批次信息,使得每类的邻居尽量均匀来自各批次。这样下游聚类、可视化时批次效应自然被削弱。

我个人的习惯是,如果数据集很大(几十万细胞),或者计算资源有限,BBKNN 往往是首选。它不需要 GPU,内存占用也低,速度非常快。

BBKNN 与其他整合方法的粗略对比
方法核心思路计算资源典型场景
Seurat CCACCA 对齐,然后 MNN 校正中等,需要较多内存少量批次、高质量数据
Harmony迭代聚类 + 岭回归较高,适合大样本多批次、连续批次
scANVI半监督变分自编码器高,需要 GPU有先验标注、大规模
BBKNN批次平衡图构建低,CPU 即可任意规模,快速调试
小贴士:如果你只是想快速看看数据里有没有新细胞类型,先跑一遍 BBKNN + UMAP,几分钟就能出图。等有了初步洞察,再尝试更复杂的整合方法也不迟。

2. 原理:平衡最近邻是怎么工作的?

BBKNN 的全称是 Batch Balanced KNN。它不修改表达谱,只修改邻居图。传统 KNN 在计算每个点的邻居时,会从所有数据中找出距离最近的 k 个细胞。但如果批次效应很严重,这些邻居往往来自同一个批次——你想想看,同一个批次的细胞本来就靠得近。这样建出来的图,每个批次内部连接紧密,批次之间几乎没有连接,下流分析自然分不开。

BBKNN 的做法是:对每个细胞,从每个批次中分别选出距离最近的 m 个邻居(m = k / 批次数量),然后再把这批邻居合并起来。这就保证了最终每个细胞的邻居集合是“批次平衡”的——无论那个批次离它多远,都能拉到同样数量的代表。这一步看似简单,实际效果非常好。我之前在集成 8 个不同平台的人 PBMC 数据时,BBKNN 跑出来的 UMAP 图里,T 细胞、NK 细胞、单核细胞都正确聚在一起,每个聚类的各批次比例几乎均匀,比我用其他方法手动调参的结果还干净。

具体流程可以用下面的示意图概括:

BBKNN 核心流程 原始表达矩阵 按批次分组 独立计算 KNN 每个细胞 取各批次 top-m 邻居 平衡图 下游分析 聚类、UMAP 可视化、差异表达 核心思想:不修改数据,只修改邻居图 通过批次平衡采样,让每个细胞都能连接到其他批次的同类细胞

这个流程看下来,你可能会有个疑问:为什么只是改个图,效果就这么好?说白了,因为下游的聚类、可视化都严重依赖邻居图的结构。图对了,后面一切就顺了。我曾经在包含 5 个批次的胎脑数据上用 BBKNN 跑聚类,结果和手动标注的细胞类型几乎完全吻合,而 Seurat 的整合方法在那个数据集上反而把神经元类型混在了一起。当然,没有银弹——BBKNN 也有自己的局限,这点我后面会详细讲。

3. 适用场景:什么时候该用 BBKNN?

  • 批次数量中等或较多(2~20 个):BBKNN 对多批次支持很好,每批次只需提供足够数量的细胞来选邻居。
  • 数据规模很大(超过 10 万细胞):它的时间复杂度接近 O(N·logN),而且内存使用线性,不会炸。
  • 快速迭代探索阶段:我习惯在建模初期先用 BBKNN 跑一次,看看是否有意料之外的亚群,再决定是否用更复杂的模型。
  • 关注保留稀有细胞类型:因为 BBKNN 不强制全局对齐,稀有细胞在特定批次里不会被强行拉向其他批次,往往保留得更好。
⚠ 避坑提醒:我曾经在一个只有 2 个批次,且某个批次细胞数特别少(不到 100 个)的项目里用了 BBKNN,结果因为那个批次细胞太少,每类邻居选不够,导致图质量很差。后来我换用了 Harmony 才解决。所以对极端不平衡的批次,要谨慎评估。

4. 快速上手:一行代码跑起来

BBKNN 是 Python 包,封装在 scvelo 生态里。安装很简单:

pip install bbknn

如果你用的是 Scanpy 的 AnnData 对象,只要一行:

import bbknn
adata = bbknn.bbknn(adata, batch_key='batch', neighbors_within_batch=3)

参数 neighbors_within_batch 控制每个批次选多少邻居。默认是 3,但实际需要根据总类别数调整。我通常会设成 总类别数 / 批次数量 * 1.5,然后看看效果再微调。不过这些都是后话了——本章只是带你认识 BBKNN,后面的章节我们会详细拆解每个参数。

一句话总结:BBKNN 是一种轻量、高效、基于图构建的批次校正方法,特别适合大规模数据和快速探索阶段。它的核心是“批次平衡采样”,不篡改原始数据,只影响邻居关系。