第4节:归一化的目的与常见方法概览

拿到 Visium 数据后,第一件事是什么?好多人以为直接跑聚类。我刚开始也是这么干的,结果批次效应把组织切片分成了若干“条纹”团,完全看不出生物学结构。

所以今天聊 归一化。说白了,就是让不同细胞、不同批次之间的表达量能放在同一个尺度下比较。不然测序深度高的样本表达量“虚高”,低深度的“虚低”,你分不清是真差异还是技术噪声。

为什么需要归一化?

Visium 的原始数据是 UMI 计数矩阵。每个 spot(空间位点)捕获的 mRNA 数量相差很大——有的 spot 测了 5 万 UMIs,有的只有 1 万。如果直接拿原始计数做分析,高 UMI 的 spot 会主导所有统计指标。

我见过一个案例:同一张切片,因为组织边缘细胞密度低,导致边缘 spot UMI 只有中心的 1/10。不归一化的话,边缘 spot 的所有基因表达都“看起来”下调了。其实只是因为测到的东西少。

核心目的:消除技术差异(测序深度、捕获效率、批次效应),保留真实的生物学变异。

归一化要解决哪几个问题?

  • 测序深度差异:每个 spot 的总 UMI 不同,需要缩放。
  • 基因长度 / GC 含量偏差:Visium 是 3′ 端 polyA 捕获,基因长度影响较弱,但 GC 偏差偶尔出现。
  • 批次效应:不同试剂盒、不同操作时间、不同测序跑带来的系统性偏移。
  • 数据过离散与零膨胀:空间转录组数据零值比例高,需要专门的处理。

常见归一化方法一览

我按自己的使用经验,把主流方法分成了三类。先看这张图,后面再细聊。

空间转录组数据归一化流程概览 原始 UMI 计数矩阵 Spot × Gene 选择归一化方法 根据数据特性与下游需求 LogNormalize log(CPM+1) 经典快速 SCTransform 正则化负二项回归 ScaleData / 空间滤波 Z-score / 局部背景校正 归一化后表达矩阵 可直接用于降维 / 差异分析 ⚠ 注意 不要对原始计数直接做 Log 先缩放再 Log,防止 log(0) 爆炸

三类方法怎么选?

我自己的经验是:

  • LogNormalize(Seurat 的默认)—— 快,稳定,适合快速试跑。但遇到零膨胀严重的数据,它对低表达基因的压缩会抹掉差异。
  • SCTransform —— 我近两年最常用。它用负二项分布建模 UMI 计数,自动考虑方差随均值的变化。对 Visium 这样高零率的空间数据效果很好。不过计算量稍大,一个 5000 spot 的样本要跑 2-3 分钟。
  • ScaleData + 空间滤波 —— 当你想显式考虑空间邻居关系时,可以用局部背景校正(比如 SPOTlight 或 BayesSpace 里的预处理)。但那一套一般跟下游方法绑定,不单独抽出来讲。
我的建议:新项目先用 LogNormalize 跑通全流程,快速查看聚类、标记基因是否合理。确认方向后,再用 SCTransform 或更专业的空间归一化重新跑一次,看结论是否稳定。

归一化流程对比表

方法核心公式 / 思想优点缺点适用场景
LogNormalizelog( (UMI / totalUMI) × 10000 + 1 )速度极快,解释简单对零膨胀数据敏感,低表达差异被压缩快速初筛、小样本
SCTransform正则化负二项回归,保留方差结构处理零值与过离散效果好计算较慢,对超大数据集需调参空间转录组、高零率数据
ScaleData基因表达标准化为 Z-score消除基因间尺度差异,适合热图丢失绝对表达量信息,不适合差异表达可视化、降维输入
空间局部归一化基于相邻 spot 背景拟合去除组织边缘效应实现复杂,参数敏感组织边缘细胞密度不均匀

避坑提醒

  • 千万别用原始 counts 直接跑 PCA —— 高表达基因会霸占主成分。我见过有人用原始 counts 跑 UMAP,结果第一维度是总 UMI 数,根本没有生物学意义。
  • 批次校正前必须先归一化 —— 顺序不能乱。先归一化消除幅度差异,再做批次校正消除系统性偏移。
  • 不要混用不同归一化方法的输出 —— 比如不能把 LogNormalize 的结果和 SCTransform 的结果放到同一个 Seurat 对象里做整合,内部尺度不同。
  • 记录归一化参数 —— 我以前偷懒没记,结果后续 reviewer 要求换方法重跑,又得从头调,浪费时间。建议把归一化代码写进 R 脚本,每次留个日志。

实操小例

用 Seurat 做 LogNormalize 就一行:

pbmc <- NormalizeData(pbmc, normalization.method = "LogNormalize", scale.factor = 10000)

换成 SCTransform 更简单:

pbmc <- SCTransform(pbmc, vars.to.regress = "percent.mt", verbose = FALSE)

仔细看第二行,我传了 percent.mt 作为要回归掉的变量。这是 SCTransform 的特色——可以把线粒体比例等技术因素直接 regress out。我觉得这比后面再跑 ScaleData 里的 vars.to.regress 更简洁。

一句话总结:归一化不是可选项,是质量保障的前置步骤。选对方法,后边的批次校正和聚类才能站得住脚。