4. 基因检测效率:每个 spot 检测到的基因数量与基因饱和度曲线

聊完了测序质量和比对,咱们来看看更实在的东西——基因检测效率。说白了,就是你花了那么多钱做实验,每个 spot 到底检测到了多少基因?数据饱和了没有?

我个人习惯把这两个指标放在一起看,因为它们就像一枚硬币的两面。一面是“量”——每个 spot 能抓到多少种基因;另一面是“质”——测序深度增加了,还能不能挖出新基因来。

4.1 每个 spot 检测到的基因数量

这是最直接的 QC 指标。通常我们用 n_genes_by_counts 或者 total_genes 来表示。这里的核心问题是:多少算正常?多少算异常?

我建议先看中位数,别被平均值骗了。曾经我在一个脑组织的 Visium 数据里,平均值看着有 2500,但一看分布,发现有一半的 spot 低于 1500。这就是典型的分布偏倚。

核心经验:对于 10x Visium 平台,组织切片的 spot 中位数基因数通常在 1000-3000 之间。低于 500 的 spot,我通常会标记为低质量。

具体怎么算?看代码:

import scanpy as sc
import numpy as np

# 加载数据
adata = sc.read_visium('path/to/spatial/')

# 计算每个 spot 的基因数
sc.pp.calculate_qc_metrics(adata, expr_type='counts')

# 查看分布
median_genes = np.median(adata.obs['n_genes_by_counts'])
print(f'Spot 中位基因数: {median_genes}')

# 标记低质量 spot
adata.obs['low_genes'] = adata.obs['n_genes_by_counts'] < 500
print(f'低质量 spot 数: {adata.obs["low_genes"].sum()}')

这里有个坑要注意。有些 spot 基因数特别高,比如超过 6000,不一定就是好事。为什么?因为可能是组织边缘或褶皱区域捕获了太多背景 RNA。嗯,我处理过一个淋巴瘤样本,边缘区域基因数暴增到 8000 多,但那些基因大部分是核糖体蛋白和线粒体基因——说白了,技术噪音。

避坑指南:基因数过高(超过整体中位数的 3 倍或更多)的 spot,建议先检查组织形态学图像。如果对应 H&E 图像上该区域有明显组织扭曲或边缘效应,直接过滤掉更安全。

4.2 基因饱和度曲线

基因饱和度曲线,听起来挺玄乎,其实没那么复杂。它回答的就是一个问题:再测更多 reads,我还能不能发现新基因?

曲线的横轴是测序深度(每个 spot 的总 UMI 数),纵轴是检测到的基因数。曲线陡峭上升,说明还没饱和;曲线开始变平,说明快饱和了。

曲线形态 含义 我的建议
持续上升,斜率 > 0.3 远未饱和,深度不足 考虑加测,或接受低检测率
中度弯曲,斜率 0.1-0.3 部分饱和,尚可接受 正常范围,继续分析
接近平坦,斜率 < 0.1 接近饱和,深度充足 完美,可以放心做差异分析

绘制饱和度曲线其实很简单,用 Python 就能搞定:

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

# 从 adata 中提取 UMI 数和基因数
umi_per_spot = adata.obs['total_counts']
genes_per_spot = adata.obs['n_genes_by_counts']

# 绘制散点图 + 拟合曲线
plt.figure(figsize=(8, 6))
sns.scatterplot(x=umi_per_spot, y=genes_per_spot, s=5, alpha=0.5)

# 分箱计算均值用于拟合曲线
bins = np.linspace(umi_per_spot.min(), umi_per_spot.max(), 50)
bin_means = []
for i in range(len(bins)-1):
    mask = (umi_per_spot >= bins[i]) & (umi_per_spot < bins[i+1])
    if mask.sum() > 0:
        bin_means.append(genes_per_spot[mask].mean())
    else:
        bin_means.append(np.nan)

plt.plot(bins[:-1], bin_means, color='red', linewidth=2, label='饱和度曲线')
plt.xlabel('每个 spot 的总 UMI 数')
plt.ylabel('检测到的基因数')
plt.title('基因饱和度曲线')
plt.legend()
plt.show()

我曾经在一个新手的数据里看到,饱和度曲线在中低深度区域就变得很平——这其实是 library complexity 太差。为什么?因为建库时 PCR 扩增偏好性导致只有少数高表达基因被放大,其余的基因压根就检测不到。这就是个典型的实验质量问题,不是测序深度能补救的。

实用技巧:计算出每个 spot 的“每百万 UMI 新增基因数”(n_genes_per_10k_umi),也就是基因检测效率的归一化值。这个值在不同样本间可比性更好。公式很简单:n_genes_per_10k_umi = n_genes / total_counts * 10000。如果这个值低于 100,我建议提高警惕。

4.3 综合判断:这两个指标怎么看?

单独看一个指标容易误判。举个例子:一个 spot 基因数很高,但饱和度曲线也很陡,说明深度还远远不够,高基因数只是因为组织本身基因表达丰富。这时候盲目过滤反而会丢掉好数据。

反过来,基因数低 + 曲线平坦,这才真正值得警惕。说白了,就是既没检测到足够基因,深度增加也救不回来。这种 spot 我建议直接标记排除。

下面这张图可以帮你理清判断逻辑:

基因检测效率判断逻辑 获取基因数与饱和度数据 基因数是否在正常范围(1000-3000)? 是 看曲线斜率 (下一层判断) 否 检查组织图像 (边缘/褶皱?) 曲线近乎平坦? 曲线仍陡峭? 是 建议标记为低质量 (深度也无法挽救) 是 继续分析 (可考虑加深测序) 提示:始终将基因数与饱和度曲线结合判断

4.4 实际项目中的阈值设置

你可能会问,阈值到底设多少?说实话,没有放之四海而皆准的数值。不过我可以分享几个我在不同平台上的经验值。

对于 10x Visium v2 试剂盒:

  • 理想:中位基因数 > 2000,饱和度曲线在 15000 UMI 后趋于平坦
  • 可接受:中位基因数 1000-2000,曲线仍在缓慢上升
  • 低质量:中位基因数 < 500,或曲线在低深度就饱和

对于 Slide-seq v2:

  • 由于 bead 尺寸小,每个 spot 的中位基因数通常在 500-1500 之间
  • 饱和度曲线通常更平坦,因为深度普遍偏低

这里要特别提醒:不要把不同平台的阈值搬来直接用。曾经有个团队用 Visium 的阈值去过滤 Slide-seq 数据,结果滤掉了 80% 的 spot,差点整个实验报废。

个人习惯:我会先用 R 包 DropletUtils 里的 brimq 方法做一次无偏估计,看数据本身的“自然阈值”在哪里。然后再结合生物学知识微调。这样比硬性规定一个数字要科学得多。

好了,基因检测效率这块的核心内容就这么多了。记住一句话:基因数看分布,饱和度看趋势,两者结合才能做出靠谱的过滤决策。