4. 数据预处理(Visium):质控指标、过滤低质量斑点、标准化、高变基因选择

拿到 Visium 原始数据后,第一件事不是跑分析,而是做质控。这一点我吃过亏——第一次做整合时,上来就标准化,结果低质量斑点的噪声把整合效果全带偏了。

说白了,预处理就是把“脏数据”洗成可用的样子。对于 Visium 来说,我们需要关注四个步骤:质控指标评估、过滤低质量斑点、标准化、高变基因选择。

Visium 数据预处理核心流程 原始数据 (Spaceranger 输出) 质控指标评估 n_counts, n_genes, mt% 过滤低质量斑点 阈值剪切 + 手动核查 标准化 LogNormalize / SCTransform 高变基因选择 vst / mean.var.plot 输出 💡 注意: 每次过滤后都要重新检查 质控指标的分布变化 不要一次性过滤太多

4.1 质控指标,看什么?

我个人习惯先扫一眼三个核心指标:

  • n_counts:每个斑点的 UMI 总数。代表测序深度和捕获效率。
  • n_genes:检测到的基因数。代表斑点本身的细胞复杂度。
  • percent.mt:线粒体基因占比。代表细胞损伤或死亡的比例。

为什么会重点关注这三个?我在项目中遇到过一件事:有些样本质控看起来不错,但一查 percent.mt 已经飙到 30% 以上——结果整合后 batch effect 特别严重。其实早该在第一步就把它过滤掉。

我的习惯:先运行 scater 或者 Seurat 的 plotQCMetrics 看一眼分布,再决定阈值。别一上来就设死数字。

4.2 过滤低质量斑点

阈值怎么设?没有标准答案。但我能给你一套实用的参考:

指标 经验阈值范围 说明
n_counts > 500 且 < 15,000 过低说明斑点可能空;过高可能是双细胞或组织碎片
n_genes > 250 低于此值,基因多样性不够,后续分析噪声大
percent.mt < 20% 超过 20%,细胞状态堪忧

但注意,这些不是铁律。你想想看,不同组织类型差异很大——脑组织的 n_counts 通常比肿瘤组织低很多。我曾经做一个小鼠脑样本,过滤阈值放到才 200 个基因,结果发现有些有生物学意义的稀疏区域被砍掉了。后来我改用双倍 MAD(中位数绝对偏差)方法来自动判定阈值。

代码示例:用 Seurat 做基础过滤

library(Seurat)
library(ggplot2)

# 读取 Visium 数据
visium_data <- Load10X_Spatial(
  data.dir = "path/to/spaceranger/outs",
  filename = "filtered_feature_bc_matrix.h5"
)

# 计算质控指标
visium_data[["percent.mt"]] <- PercentageFeatureSet(
  visium_data, pattern = "^MT-"
)

# 直观检查分布
VlnPlot(visium_data,
  features = c("nCount_Spatial", "nFeature_Spatial", "percent.mt"),
  ncol = 3, pt.size = 0.1)

# 过滤低质量斑点
visium_data <- subset(visium_data,
  subset = nCount_Spatial > 500 &
           nCount_Spatial < 15000 &
           nFeature_Spatial > 250 &
           percent.mt < 20)
我曾经踩过一个坑:用 Seurat 的 subset 过滤后忘记检查剩余斑点数。有一次过滤得太狠,本来 4000 个斑点只剩 800 个,后续统计分析都没显著性。所以过滤后一定要跑 dim(visium_data) 看看总数,做到心里有数。

4.3 标准化

标准化这一步,说白了就是消除测序深度带来的差异。Visium 不同切片间的测序深度可能差好几倍,直接比较会误导下游分析。

我个人最推荐两种方式:

  • LogNormalize:Seurat 默认方法。对每个斑点,将 UMI 数除以总 UMI 再乘以 scale.factor,最后 log1p 转换。
  • SCTransform:基于正则化负二项回归,效果更好,但计算量大一些。

代码示例:LogNormalize 标准化

# 标准化(Seurat 默认)
visium_data <- NormalizeData(visium_data,
  normalization.method = "LogNormalize",
  scale.factor = 10000)

# 或者用 SCTransform(需要提前安装 sctransform)
visium_data <- SCTransform(visium_data,
  vars.to.regress = "percent.mt",
  verbose = TRUE)

这里有个知识点:SCTransform 内部直接包含了高变基因选择,所以当你用 SCTransform 后,可以跳过 4.4 节的手动高变基因筛选。但如果你用 LogNormalize,就得自己选一次。

我的建议:如果是单样本探索,用 LogNormalize 够了。如果要做跨样本整合,我强烈推荐 SCTransform——它减少了好多人工调参的麻烦。

4.4 高变基因选择

高变基因(HVG)就是那些在不同斑点间表达差异最大的基因。选择它们能降低维度、聚焦信号。你想想看,如果 20,000 个基因全跑进去,PCA 都会被 housekeeping 基因淹没。

Seurat 提供了两种方法:

  • vst:基于方差稳定变换,速度最快。适合大数据集。
  • mean.var.plot:基于均值-方差关系,传统方法,效果也不错。

选多少高变基因?我一般选 2000 个。太少会丢失信息,太多会引入噪声。我记得有一次选 5000 个,结果 PCA 的前 10 个 PC 解释方差反而下降了——说明过多基因稀释了信号。

代码示例:高变基因选择

# 识别高变基因(用 vst 方法)
visium_data <- FindVariableFeatures(visium_data,
  selection.method = "vst",
  nfeatures = 2000)

# 查看选中的基因
top10 <- head(VariableFeatures(visium_data), 10)
print(top10)

# 将数据缩放到标准范围(准备后续 PCA)
visium_data <- ScaleData(visium_data,
  features = rownames(visium_data))
注意:SCTransform 已经内置了高变基因选择,如果用 SCTransform 标准化了,就不要重复调用 FindVariableFeatures,否则会覆盖掉 SCTransform 的结果。

到了这一步,你的 Visium 数据就备好了。接下来可以进入 PCA 降维、聚类分析,或者直接和 Slide-seq 做整合。但我要强调一句:预处理的质量直接决定后续整合的成败。我见过太多项目因为质控太松或者标准化方法选错,整合后 batch effect 满天飞,怎么调都调不回来。

把这一步走扎实,后面就顺了。