4. 数据质量控制 (QC) 与过滤

到了这一步,其实有个灵魂拷问摆在你面前:原始数据敢直接用吗?

我个人习惯是,先扫一眼数据质量再动手跑任何聚类。因为如果底子就是烂的,后面调出一朵花来也没用。做这个项目的时候,我一开始拿到的那套公开肿瘤数据,直接丢进去算,结果发现标记数分布和线粒体比例都很不对劲——嗯,下面会详细说这个问题。

4.1 为什么先做数据质量控制?

Visium 平台的原理其实就是原位捕获 + 测序。它是切片贴片方式,难免有些点(spot)质量不好,比如组织边缘的 spot 可能捕获细胞太少,或部分 spot 捕获了掉下来的细胞碎片。

  • 标记数太少:统计信息弱,后续聚不了类。
  • UMI 重复太高:表示扩增偏差,不是真实的生物信号。
  • 线粒体比例过高:通常是细胞受损或凋亡的标志。

在一个真实项目里,我见过一份数据,一次切片大约 3%-5% 的 spot 是碎片区。如果不过滤掉,这些“低质量邻居”会干扰降维和聚类算法,最后出来的图有奇怪的噪声簇。

⚠ 注意:过滤标准应该基于数据本身的分布来定,而不是硬套文献。前面那个项目我一开始套了别人的阈值,结果把一大半 good spot 也干掉了,后面重跑浪费了半天。

4.2 检查哪些指标?

无非三件事:总 UMI 数、检测到的基因数、线粒体百分比。你想想看,这三个指标基本覆盖了“捕获够不够”、“信噪比高不高”、“细胞是否健康”。

指标 含义 通常低质量判断
nCount_UMI 每个 spot 的总 UMI 计数 过低(< 500)或极端高异常
nFeature_RNA 每个 spot 检测到的独有基因数 过低(< 200 或 100)
percent.mt 线粒体基因 UMI 占总 UMI 比例 过高(> 20% 或 25%)

其实,我习惯在过滤前先画一个 Violin 图和 Scatter 图看一下分布。你一眼就能看出哪些是尾巴、哪些是极端值。

4.3 过滤脚本怎么写?

在 R 里,以 Seurat 为例,你可以这样做:

# 计算线粒体比例
tumor_data[["percent.mt"]] <- PercentageFeatureSet(tumor_data, pattern = "^MT-")

# 可视化
VlnPlot(tumor_data, features = c("nCount_UMI", "nFeature_RNA", "percent.mt"),
        ncol = 3, pt.size = 0.01)

# 手动过滤
tumor_data <- subset(tumor_data,
      subset = nFeature_RNA > 200 &
               percent.mt < 20 &
               nCount_UMI > 500)

嗯,这里要注意:如果你用 Python 的 Scanpy,语法略有不同,但逻辑一样。

import scanpy as sc

# 读入 h5 文件
adata = sc.read_visium("path/to/spaceranger/output")

# 计算 QC 指标
adata.var["mt"] = adata.var_names.str.startswith("MT-")
sc.pp.calculate_qc_metrics(adata, qc_vars=["mt"], percent_top=None, log1p=False)

# 过滤
adata = adata[adata.obs.n_genes_by_counts > 200, :]
adata = adata[adata.obs.pct_counts_mt < 20, :]
adata = adata[adata.obs.total_counts > 500, :]

还记得吗?我曾经过滤完忘记更新高变基因列表,结果后面 PCA 降维时总感觉不对劲。后来就养成了习惯:过滤后一定要重新跑一下找高变基因和归一化。

4.4 流程图:数据进至 QC 过滤

画个流程来总结一下这一节。

数据质量控制与过滤流程图 原始 Visium 数据 计算 QC 指标 nCount + nFeature + mt% 分布可视化 & 设定阈值 Violin / Scatter / 设置下上限 执行过滤 subset 低质量 spot 过滤后数据集 用于后续降维/聚类

流程其实很直接,但每一步的阈值可视化不能省。为什么?因为你凭肉眼看到分布才知道哪些 spot 是离群。

4.5 避坑指南:过滤标准不要太死

我记得刚用 Visium 数据那会儿,总喜欢参考公共数据集的通用阈值。但 Visium 的捕获效率、切片厚度、组织类型不同,UMI 分布差异会很大。

  • 组织类型敏感:比如脑组织的线粒体比例通常较高,可以适当放宽松一些到 25%。
  • nCount 上限也要看:过高的 nCount 可能是来自组织边缘污染的双 spot,我一般会画箱线图去掉上分位数+3 IQR 以上的点。
  • 千万不要一开始就过滤掉低表达基因:那个可以在后面找高变基因时再做。
💡 个人经验:我那回在过滤时,保留了所有 nFeature > 200 且 mt% < 20 的 spot,画出来聚类还不错,但有一个小病理区一直出现混聚,后来发现是那批 spot 的 nCount 异常偏低,放宽了阈值后就没问题了。

4.6 检查过滤效果

过滤完了,我喜欢再做一个数据分布的总结,比如看看还剩多少个 spot,以及 UMI 分布的情况。

# 在 R 里
dim(tumor_data)  # 看看过滤前后的 spot 数量变化
summary(tumor_data$nFeature_RNA)
summary(tumor_data$percent.mt)

# 在 Python 里
print(adata.shape)
sc.pl.violin(adata, 'n_genes_by_counts')
sc.pl.violin(adata, 'pct_counts_mt')

如果过滤后 spot 数量骤减(比如超过了 20%),我建议你回头检查一下阈值是不是太严,或者组织切片本身有没有物理瑕疵。

4.7 整个章节关键点总结

🎯 核心记住:
1. 过滤不是为了让数据变漂亮,而是剔除明显的技术噪声。
2. 三个关键指标:总 UMI、基因数量、线粒体占比。
3. 阈值不准教条,必须看数据分布。
4. 始终画图确认过滤前后差异。

好,QC 这块先说到这。准备好了干净的数据,下一步才能放心调那个最佳分辨率参数。