第4节:归一化的目的与常见方法概览
拿到 Visium 数据后,第一件事是什么?好多人以为直接跑聚类。我刚开始也是这么干的,结果批次效应把组织切片分成了若干“条纹”团,完全看不出生物学结构。
所以今天聊 归一化。说白了,就是让不同细胞、不同批次之间的表达量能放在同一个尺度下比较。不然测序深度高的样本表达量“虚高”,低深度的“虚低”,你分不清是真差异还是技术噪声。
为什么需要归一化?
Visium 的原始数据是 UMI 计数矩阵。每个 spot(空间位点)捕获的 mRNA 数量相差很大——有的 spot 测了 5 万 UMIs,有的只有 1 万。如果直接拿原始计数做分析,高 UMI 的 spot 会主导所有统计指标。
我见过一个案例:同一张切片,因为组织边缘细胞密度低,导致边缘 spot UMI 只有中心的 1/10。不归一化的话,边缘 spot 的所有基因表达都“看起来”下调了。其实只是因为测到的东西少。
核心目的:消除技术差异(测序深度、捕获效率、批次效应),保留真实的生物学变异。
归一化要解决哪几个问题?
- 测序深度差异:每个 spot 的总 UMI 不同,需要缩放。
- 基因长度 / GC 含量偏差:Visium 是 3′ 端 polyA 捕获,基因长度影响较弱,但 GC 偏差偶尔出现。
- 批次效应:不同试剂盒、不同操作时间、不同测序跑带来的系统性偏移。
- 数据过离散与零膨胀:空间转录组数据零值比例高,需要专门的处理。
常见归一化方法一览
我按自己的使用经验,把主流方法分成了三类。先看这张图,后面再细聊。
三类方法怎么选?
我自己的经验是:
- LogNormalize(Seurat 的默认)—— 快,稳定,适合快速试跑。但遇到零膨胀严重的数据,它对低表达基因的压缩会抹掉差异。
- SCTransform —— 我近两年最常用。它用负二项分布建模 UMI 计数,自动考虑方差随均值的变化。对 Visium 这样高零率的空间数据效果很好。不过计算量稍大,一个 5000 spot 的样本要跑 2-3 分钟。
- ScaleData + 空间滤波 —— 当你想显式考虑空间邻居关系时,可以用局部背景校正(比如 SPOTlight 或 BayesSpace 里的预处理)。但那一套一般跟下游方法绑定,不单独抽出来讲。
我的建议:新项目先用 LogNormalize 跑通全流程,快速查看聚类、标记基因是否合理。确认方向后,再用 SCTransform 或更专业的空间归一化重新跑一次,看结论是否稳定。
归一化流程对比表
| 方法 | 核心公式 / 思想 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| LogNormalize | log( (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 更简洁。
一句话总结:归一化不是可选项,是质量保障的前置步骤。选对方法,后边的批次校正和聚类才能站得住脚。