4. kBET 算法原理:k近邻批次效应检验的数学基础

好了,咱们进入正题。前面聊了那么多为什么需要校验批次效应,这章我们就把 KBET 这个工具拆开看看。说实话,我刚接触 KBET 时也有点懵,觉得又是近邻又是卡方检验的,太数学了。但真正用过几回就发现,它的核心逻辑其实就那么几条线。

4.1 整体思路:你在检验什么?

KBET,全称 k-Batch Effect Test,直译就是「基于k近邻的批次效应检验」。我个人习惯把它理解成一个「局部公平性检查器」。它不问数据整体是否被批次信息主导,而是逐一核验:在任意一个细胞周围,来自不同批次的邻居数量,是否跟全局期望相符?

你想想看,如果数据里没有批次效应,那么某个细胞的邻居就应该像「随机抽样」一样,各批次出现的比例跟它们在整体中的比例差不多。反过来,如果某个细胞周围的邻居几乎全来自同一批次,那肯定有问题——这就是 KBET 要揪出来的异常。

kBET 单细胞检验流程 选择细胞 i 找 k 个最近邻 统计邻居批次标签 结果 对每个邻居计算卡方统计量 汇总接受率 (Acceptance Rate) 全局批次比例: 控制实验环境 卡方检验 H₀: 局部比例 = 全局比例

4.2 第一步:构建k近邻图

你必须先知道「谁是谁的邻居」。KBET 的第一步就是在整合后的嵌入空间(比如 PCA 降维结果)里,为每个细胞找到最相似的 k 个细胞。

  • 度量标准:欧氏距离最常用。个别场景我用过余弦距离,效果差不多。
  • k 的选择:默认 k=5,但我建议数据集大时调到 10-20,否则邻居太少,统计量不稳定。
  • 输入数据:通常是 top 20-50 个 PC,不是原始基因表达矩阵。

这里有个项目经验。我曾经在 20 万细胞的超大数据集上跑,用的是 FastCD 优化的近似近邻算法,不然内存直接爆掉。

4.3 第二步:计算局部批次分布

近邻图构建完了,接下来做一件简单的事:对每个细胞 i,统计它的 k 个邻居里,每个批次标签出现了多少次。

假设我们有 B 个批次(比如 Batch1、Batch2、Batch3),全局细胞总数 N,各批次数量分别为 N₁、N₂、N₃。那么全局期望比例就是:

p_b = N_b / N    (b = 1, 2, ..., B)

如果数据完美混合,细胞 i 的 k 个近邻中,来自批次 b 的邻居数量应该接近 k × p_b。但真实情况不可能这么理想,所以我们需要一个量化指标来判断这个「接近」到底到不到位。

核心条件:如果在细胞 i 的近邻中,来自某个批次的细胞数量显著多于全局期望(或者显著少于),说明该细胞附近存在批次集中的现象——这就是批次效应。

4.3 第三步:卡方检验——衡量差异有多「显著」

量化工具就是经典的卡方检验,也叫 χ² 检验。它的基本公式很简单:

χ² = Σ [(O_b - E_b)² / E_b]    (对批次 b = 1 到 B 求和)
  • O_b:实际观测到的来自批次 b 的邻居个数。
  • E_b:期望值,即 k × p_b。
  • 求和:对每个批次计算,最后累加出一个总的卡方统计量。

这个 χ² 值越大,说明局部批次分布越不均匀,也就是局部与全局偏差越大。你可能会问:「那多大算大?」KBET 通过卡方分布(自由度 = B-1)计算 p-value,然后跟显著性水平 α(默认 0.05 或 0.01)比较。

实际项目技巧:当近邻数 k 小于批次数量 B 时,卡方检验会失效。KBET 会跳过这类样本,不计算其 p-value。所以有时你会发现报告里有「未测试」的细胞——别慌,这很正常。

4.4 第四步:汇总与接受率

单个细胞的卡方检验结论太细碎,我们需要一个宏观指标。KBET 会统计所有被测试的细胞中,p-value 大于 α(即接受原假设 H₀,认为局部与全局无显著差异)的占比,这就是「接受率」(Acceptance Rate)。

接受率范围 批次效应严重程度 调后建议
90% - 100% 几乎无批次效应 可以放心进入下游分析
70% - 90% 轻微批次效应 可以接受,但注意检查
50% - 70% 中等批次效应 建议重新调整整合参数
< 50% 严重批次效应 整合失败,需要换方法

避坑指南:我曾经见过一位同事只看接受率超过 90% 就觉得万事大吉,结果下游差异分析里还是出现了假阳性。光看平均值会掩盖掉某些「局部的、严重的」批次效应——比如某一群细胞全是Batch1,但其他群混合很好,平均数仍然高。所以一定要结合可视化,再看局部异常区域的细胞比例。

4.5 关于k值的选择——多一点个人建议

前面几次提过 k,这里单独拎出来细说一下。k 就是近邻数量,这个参数直接影响检验敏感度。

  • k 过小:比如 k=2,统计量极不稳定,随机波动看起来就像显著差异,接受率会偏低,给你发假警报。
  • k 过大:比如 k=100,局部「感受野」太大,把不同亚群都混进来了,批次的局部偏差被平滑掉,接受率虚高。
  • 经验值:我一般选 k=10-20 之间。如果细胞总数小于 1000,那 k 可以降到 5-10。

有时候我也会跑一个「k 敏感性分析」:用不同的 k 跑一次,看看接受率怎么变化。如果所有 k 下的趋势一致,那结论就更可靠了。

4.6 KBET 的局限性(顺便告诉你避免踩坑)

算法不是万能的,这里我列出几个实际项目中遇到过的问题:

  1. 非对称批次组成:如果某个批次数量极少,比如只占 2%,那它在局部很难凑够期望数,卡方检验容易失效。KBET 内部会跳过这类情况,但你得知道这个事实。
  2. 空间结构与批次混淆:如果细胞类型本身是按批次分组的(比如 Batch1 全是 T 细胞,Batch2 全是 B 细胞),那么即使整合完美,局部批次分布也不会均匀——因为 T 细胞和 B 细胞本来就离得很远。这时 KBET 的检出不代表批次效应,而是生物差异。所以我的建议是:分层抽样或使用参考批次校正。
  3. 计算效率:原始 KBET 实现对 10 万以上细胞较慢。我现在项目里都用 Cython 优化版本或者分布式实现。

一句话总结:KBET 本质上是一个「近邻内批次标签均匀度的统计检验」。它很实用,但你必须理解它什么时候好用、什么时候可能骗你。

好了,这章就到这里。其实 KBET 的原理没有听起来那么复杂——你只需要记住它就想知道「随机抽查的每个细胞周围,批次分布是否合理」。下次你跑 KBET 时,不妨手算两个细胞的卡方值,你会更有体感。