数据标准化与批次校正

好,咱们直接进入第三个课题。数据标准化和批次校正,这步要是没做好,后面的模型训练就是跑偏。我刚开始做空间转录组项目时,就吃过这个亏——两个芯片的数据死活整合不到一起,折腾了两周才发现是标准化方法选错了。

说白了,标准化的任务是让不同细胞、不同 spots 之间的表达量具有可比性。而批次校正,是消除不同实验批次带来的技术偏差。这两个概念不一样,但经常一起处理。

为什么需要标准化?

原始 count 数据有几个毛病:

  • 文库大小差异——有的 spot 捕获的 RNA 多,有的少,这不代表生物学差异。
  • 基因长度偏差——虽然空间转录组的 UMIs 计数比 bulk RNA-seq 好一些,但长基因仍然倾向于产生更多 counts。
  • 方差结构复杂——高表达基因方差大,低表达基因方差小,但很多统计模型假设方差齐性。

所以我们需要把数据“拉到一个公平的起跑线上”。

标准化方法,我常用的几种

方法 适用场景 我踩过的坑
LogNormalize 绝大多数空间转录组数据 对低表达噪音敏感
SCTransform 数据高度零膨胀时 计算量巨大,谨慎上全量数据
SCANPY 的 pp.normalize_total 快速批处理、Python 生态 参数 target_sum 设不好会扭曲
Raw counts + 方差稳定 某些深度学习模型需要 不是万能的

我的经验:对于 10x Visium 数据,我习惯先用 LogNormalize 看看全局分布。如果发现很多基因表达模式像“二极管”(要么不表达,要么爆发),那就换成 SCTransform。但 SCTransform 跑 10 万 spots 会很慢,记得上 GPU 或者用 mini-batch。

注意:千万不要在标准化之前直接做降维。我见过有人直接用原始 counts 跑 PCA,结果前两个主成分全是文库大小。这就像两个人比身高,一个站在凳子上一个站在地上——没意义。

批次校正,不解决会出大事

批次效应是个很烦的东西。两个相同组织切片,同一套实验方案,但不同时间做的,数据就会有差异。你说它是批次效应还是真实差异?有时候真分不清。

我这里整理了几种主流方法的对比:

方法 核心思想 Python 实现 我项目中的评价
Harmony 在 PCA 空间做软聚类调整 harmony-pytorch 快,适合大样本,但参数多
ComBat 贝叶斯范式,调整均值和方差 combat.py 经典方法,对批次少时有效
scVI 变分自编码器,隐空间对齐 scvi-tools 效果好但训练时间长
BBKNN 基于图的批次校正 bbknn 保留局部结构,适合聚类

实践建议:我个人在空间转录组项目中,如果样本量小于 5 张切片,建议先试 ComBat。它简单,假设少,而且经常很管用。样本多了以后,Harmony 或 scVI 更合适。

Harmony 怎么用

Harmony 是我的老朋友了。它的思路是先做 PCA 降维,然后迭代优化:找出每个细胞/spot 属于哪个批次,再调整它们在 PCA 空间的位置,让不同批次的分布对齐。说白了,就是“软聚类 + 平移”。

我常用的代码示例:

import harmonypy
import pandas as pd
import numpy as np

# 假设 pca_embedding 是 (n_cells, 30) 的 PCA 结果
# meta_data 包含批次信息列 'batch'

ho = harmonypy.run_harmony(
    pca_embedding,
    meta_data,
    vars_use=['batch'],
    max_iter_harmony=10,
    do_pca=False       # 已经做过 PCA 了
)

corrected_embedding = ho.Z_corr.T

这里有个坑:max_iter_harmony 的默认值是 10,但我遇到过一个样本量特大的数据集,20 次迭代才收敛。你可以在训练过程中监控 loss 变化,如果连续 3 次迭代 loss 没下降,就停了。

ComBat 的实现细节

ComBat 更传统一些。它假设每个基因在不同批次的均值或方差存在偏移,然后用经验贝叶斯去估计这些偏移。说白了,就是把每个基因的分布“校正”到同一水平线上。

看看这段代码:

import pandas as pd
import numpy as np
from combat.pycombat import pycombat

# expression_data: 基因×样本的矩阵
# batch: 样本对应的批次标签
corrected_data = pycombat(expression_data, batch)

# 返回同样形状的校正后矩阵

重要提醒:ComBat 要求输入是完整矩阵。如果你的数据有大量缺失值(空间转录组有些基因在很多 spots 都是零),它可能会报错。我遇到过这种情况:有些基因在某个批次中全部为零,ComBat 直接崩了。解决方法是先过滤掉那些在所有批次中表达太低的基因。

我画的一张图,理清整个流程

下面这张 SVG 图展示了我常用的标准化与批次校正的工作流。从原始数据到最终可用特征,中间会经历好几个关卡。你仔细看每个节点对应的方法和注意事项。

标准化与批次校正核心流程 原始 Counts 数据 选择标准化方法 LogNormalize / SCTransform / normaliz_total 批次标签 样本来源、实验日期 多批次?需要校正吗? 评估批次效应的严重程度 选择批次校正方法 Harmony / ComBat / scVI / BBKNN 校正后特征矩阵 可直接用于降维、聚类、模型训练 关键点 • 标准化不是可选的 • 批次校正前先可视化 • 不同方法对样本量敏感

你看这个流程的走向:先标准化,再判断是否需要批次校正,最后得到干净的矩阵。这个顺序不能乱。我试过先校正再标准化,结果算法里大量零值导致内存爆炸。

实战中常踩的坑

  1. 过度校正——调和过头,真实的生物学差异也被抹掉了。你要保留一些生物学差异!
  2. 忽略空间信息——普通单细胞的方法照搬用到空间转录组,往往效果差。空间数据有额外的空间自相关性。
  3. 批次信息不完整——有些实验室只记得记录“batch 1”和“batch 2”,但忘了记录哪个样本用的试剂盒不一样。这会导致批次效应无法完全消除。

我的一次翻车经历:有一次两个批次的数据,我用了默认参数的 Harmony,校正完一看,UMAP 上两叠数据完美重合。我当时还很高兴,以为问题解决了。结果下游差异基因分析,一个 real marker 都找不到。后来才发现,我把真实的组织区域差异也当做批次效应给校正掉了。所以现在我做校正,都会预留 10% 的基因不参与校正,用来做验证。

参数调优的快问快答

  • Harmony 的 theta 参数——控制校正力度。越小校正越强。我一般从 1.0 开始,如果批次效应还没消失,降到 0.5。
  • ComBat 的 par.prior 参数——是否使用参数化先验。样本量大用 True,否则 False。
  • scVI 的 n_latent——不要设太大,10-20 就够。太大容易过拟合批次特征。
# Harmony 调参示例
ho = harmonypy.run_harmony(
    pca_embedding,
    meta_data,
    vars_use=['batch'],
    theta=1.0,          # 控制力度
    lambda_val=1.0,     # 岭回归正则化
    sigma=0.1,          # 软聚类带宽
    max_iter_harmony=10,
    verbose=False
)

小技巧:任何批次校正做完后,都跑一遍 UMAP,用颜色标记批次。如果不同批次混在一起,但生物学分组(如不同组织区域)也能清晰分开,说明校正效果很好。如果生物学差异也被压平了,赶紧调整参数。