归一化与缩放:LogNormalize 与 SCTransform 对比、ScaleData 的作用
在做单细胞 RNA-seq 分析时,有个环节让我头疼了好一阵子——归一化。说白了,这步搞不清楚,后面聚类的结果就容易翻车。你想想看,每个细胞里捕获到的基因数量、测序深度都不一致,直接拿原始 UMIs 去比较,那肯定不靠谱。
我个人习惯把归一化分成两个层面来讲。一是「标准化」,也就是把每个细胞的总表达量抹平;二是「缩放」,让所有基因的方差变得更加均匀。今天咱们就聊聊 LogNormalize、SCTransform 和 ScaleData 这三个家伙。
LogNormalize:传统派的坚守
为什么要用 LogNormalize?这个方法是 Seurat 默认的标准流程,它做的事情其实很简单:先算出每个细胞的总 UMI 数(也就是 library size),然后把所有细胞的表达量都缩放到同一个尺度。具体来说:
LogNormalize 的核心逻辑:
计数值 = log1p(当前基因UMI / 细胞总UMI × 缩放因子)
# 默认缩放因子 10000
# 相当于把每个细胞的标准总量定义为 10000
我在项目早期踩过坑。有次处理一批质量不太好的数据,有些细胞总 UMI 才几百,有些却好几万。直接跑 LogNormalize 之后,那些低质量的细胞照样活跃在聚类图里,搞得我花了大半天去调参数。后来才反应过来——LogNormalize 对 library size 极度敏感。如果你的数据批次效应明显或者细胞质量参差不齐,它的表现就不够稳定了。
| 特点 | LogNormalize | SCTransform |
|---|---|---|
| 核心模型 | 全局缩放 + log1p | 正则化负二项回归 |
| 高变基因选择 | 独立步骤(FindVariableFeatures) | 内嵌在模型中 |
| 方差处理 | 未显式建模,深度残差明显 | 显式建模,方差与深度解耦 |
| 速度 | 快(线性缩放) | 中等(回归拟合) |
| 内存占用 | 低 | 较高(存储回归参数) |
| 典型场景 | 同批次、质量高、细胞均一 | 批次效应明显、细胞类型差异大 |
SCTransform:革新派的崛起
说实话,我第一次接触 SCTransform 的时候,内心是抵触的。又要多装包,又多了一层参数调整。后来在一次整合多个样本的项目里,我才真正体会到它的价值。
我记得有一次,同事给了我一组数据,包含来自三个不同时间点的样本。用 LogNormalize 跑完之后,时间点之间差异明显大于细胞类型之间的差异——典型的批次效应。改用 SCTransform 之后,情况好了很多。为什么呢?
SCTransform 的核心是 正则化负二项回归。它不再简单地对每个细胞做等比缩放,而是对每个基因的 UMI 计数建立一个模型,将测序深度作为协变量(就是回归里的 x 变量)。这样做的目的很明确:把测序深度对表达量的影响从数据中「剥离」出来。
SCTransform 标准用法:
# 注意:SCTransform 替代了 NormalizeData、FindVariableFeatures、ScaleData 三步
# 一行命令完成归一化、选高变基因、缩放
pbmc_small <- SCTransform(pbmc_small, vars.to.regress = "percent.mt")
为什么它能够更好处理批次效应?因为在回归模型里,你可以把批次、线粒体比例、细胞周期评分等作为 vars.to.regress 加进去,相当于在做归一化的同时就把这些干扰因素去掉了。这在 LogNormalize 流程里需要额外再跑 ScaleData 时才处理。
什么时候该用 SCTransform?
- 样本量偏大(比如上千个细胞)
- 存在明显批次效应
- 有明确需要回归掉的技术变量(线粒体比例、细胞周期等)
- 想要一步到位、减少后续调参
什么时候别硬上?
- 内存不够(比如只有 8GB RAM)
- 数据非常干净,细胞类型差异明显
- 你想保留更细粒度的方差信号
ScaleData 的作用:你真的了解它吗?
好多人以为 ScaleData 就是简单的标准化(让每个基因均值为 0,方差为 1)。对,也不对。它的核心作用其实是 为 PCA 做准备。
你想想看,PCA 是基于协方差矩阵的分解,如果基因的表达量尺度不一样(有的基因均值为 2,有的为 8),那 PCA 计算时大尺度的基因天然占据主导。这显然不合理。ScaleData 就是把所有基因拉到同一个对比度上:
# 标准缩放
scaled_value = (gene_expression - mean(gene)) / sd(gene)
# 结果:均值为 0,方差为 1
但这个步骤里藏了一个很重要的问题——你到底是先归一化再缩放,还是先缩放再归一化?我见过不少人搞反了。正确的顺序一定是先做 LogNormalize 或 SCTransform,再做 ScaleData。因为 ScaleData 是在归一化之后对基因进行缩放,而不是在原始 UMI 上。
避坑指南: 我用 ScaleData 时最容易犯的一个错误是忘记设置 features 参数。如果你不给它指定基因范围,它默认会对所有基因做缩放——包括那些低表达、高噪声的基因。这会让 PCA 的结果莫名其妙地变差。
# 正确方式:只缩放高变基因
pbmc_small <- ScaleData(pbmc_small, features = VariableFeatures(pbmc_small))
这里还有个小问题。如果你用的是 SCTransform,那 ScaleData 这一步其实可以跳过,因为 SCTransform 在返回的矩阵里已经做好了标准化(残差)。但如果你在 SCTransform 之后又想加新的回归变量,或者只想对部分基因做单独缩放,那 ScaleData 依然可以用。
| 归一化方法 | 是否还需要 ScaleData? | 建议 |
|---|---|---|
| LogNormalize | 是 | 放在 FindVariableFeatures 之后,只对高变基因缩放 |
| LogNormalize + 自定义变量回归 | 是 | 在 ScaleData 中通过 vars.to.regress 处理额外变量 |
| SCTransform | 通常不需要 | 除非你要对特定基因重新缩放或者加新的回归变量 |
做不做 ScaleData?这个得你想清楚
有个常见的困惑是:我是不是每次都必须跑 ScaleData?答案是不一定。比如你只是做差异表达分析,不准备跑 PCA 或者 UMAP,那 ScaleData 完全可以省略。
但我建议,只要你的流程里包含 PCA(聚类几乎必然用到),就老老实实跑一次。我之前就因为没用标准化做聚类,结果得出了错误的结论——PCA 前几个主成分完全被几个高表达基因撑住了,细胞类型的分辨反而差。加上 ScaleData 之后,效果立马不一样了。
简单总结一下:
- LogNormalize 和 SCTransform 是两类不同的归一化路径,选一个。
- 如果选了 LogNormalize,后面必须补 ScaleData。
- 如果选了 SCTransform,ScaleData 可有可无,看场景。
- 不管选哪种,ScaleData 的 features 参数一定要限制到高变基因。
嗯,到这里咱们把归一化到缩放这条线理清楚了。这块内容虽然基础,但做项目时最容易出问题的地方往往就在这里。搞清楚了,后续的 PCA 和聚类才能打得准。