3. UMI 计数与过滤:去重原理、有效比例与同源序列处理

哈,聊到 UMI(Unique Molecular Identifier,独特分子标识符)了。这是空间转录组 QC 里我最喜欢的一个环节。为什么?因为它直接关系到你最终拿到的那个基因表达矩阵到底靠不靠谱。

你可能听过一句话:「空间转录组的数据质量,一半看 UMI 去重做得好不好。」这话不夸张。我在好几个项目里都见过,明明组织切片做得很漂亮,结果一跑 QC,有效 UMI 比例低得可怜——说白了,大量数据其实是被 PCR 重复浪费掉了。

好,我们一步步拆开来看。先从最基础的 UMI 去重原理说起。

3.1 UMI 去重原理:到底在去什么?

UMI 说白了就是一段随机的核苷酸序列,长度从 8 bp 到 12 bp 不等。在建库的时候,每一个 mRNA 分子都会被打上一个独一无二的标签——你把它理解为分子条形码就行。

测序后,同一条 mRNA 会产生多个测序 read,但这些 read 都会带着同一个 UMI。所以,如果我们看到多组 read 拥有完全相同的 UMI + 空间条码 + 基因映射,那它们大概率来自同一个原始 mRNA 分子。这时候,我们只保留一条就够了。

嗯,具体流程可以这样理解:

  1. 第一步:读出 UMI 序列。注意,这里 UMI 的碱基质量非常关键。
  2. 第二步:按「空间条码 + 基因 ID + UMI 序列」进行分组。
  3. 第三步:每组只保留一条 read(通常是 UMI 质量最高的那条)。
  4. 第四步:扔掉其他所有重复 read。

这就是最经典的 UMI deduplication。但这里有个坑——如果 UMI 本身有测序错误,那原本是同一个分子的 read,可能因为一两个碱基错误被分到不同组去了。我刚刚接触这个领域时就吃过这个亏,结果去重后 UMI 数目反而膨胀了。嗯,后来我养成了一个习惯:在去重前先做 UMI 校正。

下面这个图可以帮你快速理解 UMI 去重的完整逻辑:

UMI 去重核心流程 原始测序 reads UMI + CB + 基因比对 分组聚合 按 CB + Gene + UMI 分桶 同源序列去重 Hamming 距离 ≤ 1 合并 有效 UMI 比例 (UQI) UMI 去重后 / UMI 去重前 低质量 UMI 剔除 UMI 碱基质量 < Q20 直接丢弃 同源差异 UMI 处理 合并邻居 UMI + 保高质量 最终有效 UMI 文库 → 基因表达矩阵 去重后 UMI 总数 / 总捕获 UMI 数 = UQI 🔔 关键经验: UQI 低于 0.3,说明文库复杂度极低,基因表达矩阵严重失真。我遇到过的 UQI 最低的样本只有 0.12,最后直接废弃了。

3.2 有效 UMI 比例(UQI):你的文库还剩多少有价值的数据?

有效 UMI 比例,我习惯叫它 UQI(UMI Quality Index)。计算公式很简单:

UQI = 去重后 UMI 数 ÷ 去重前 UMI 数

举个例子:一个 spot 捕获了 5000 个 UMI,经过 deduplication 后剩下 1800 个有效 UMI,那 UQI = 0.36。这意味着什么?意味着原始数据里有 64% 的 UMI 对应的是同一个分子——说白了,你的建库效率或者测序深度产生了大量的冗余。

我个人习惯的 UQI 阈值参考:

UQI 范围文库质量说明建议操作
> 0.55文库复杂度极高,数据利用率好信心十足,进入下游分析
0.35 – 0.55中等,可接受正常分析,留意个别低 UQI spot
0.20 – 0.35偏低,存在冗余过多或低质量 UMI强烈建议排查建库和测序过程
< 0.20极低,基因组表达信号被严重压缩考虑重新建库或调整捕获参数

你想想看,如果 UQI 只有 0.1,意味着你 90% 的 UMI 其实都是噪音。这时候你用这些数据做差异表达分析,结果能信吗?反正我是不敢直接用。

3.3 低 UQI 的常见病因排查

为什么 UQI 会低?我在项目中遇到过三种典型情况:

  • PCR 循环过多:这是最常见的原因。PCR 扩增越狠,同一个 UMI 的复制品就越多。去重后有效比例自然下降。
  • 测序深度过高:测得太深,每个 UMI 被覆盖几十甚至上百次。表现上看是数据量大,但有效 UMI 数并没有增加。
  • UMI 碱基质量差:低质量 UMI 无法被正确读取,导致去重算法出错——原本一个 UMI,由于测序错误被识别成多个,去重不干净。

我记得有个项目,建库时 PCR 循环数从 12 增加到了 18,结果 UQI 直接从 0.45 掉到了 0.19。你说亏不亏?那时候我就在想,一定要在实验设计阶段就把 PCR 循环控制好。

小技巧:

如果你用的是 10x Genomics 的 Visium 数据,可以检查一下 possorted_genome_bam.bam 中每张标签的 UB tag 对应的 UMI 质量。如果 UMI 质量普遍低于 Q20,那就是测序仪器或试剂出了问题。

3.4 同源序列处理:一个 UMI 漏网之鱼都不能放过

好,讲到这里,我们进入最麻烦的一个环节——同源序列处理。什么是同源序列?简单说就是:两个或更多不同的 UMI,它们原本来自同一个 mRNA 分子,但由于 UMI 扩增或测序过程中产生了 1~2 个碱基的变异,导致它们看起来像是不同的 UMI。

举个例子:

原始 UMI: ATCGATCG
错误 UMI1: ATCGATCC  (最后一个碱基 T→C)
错误 UMI2: ATCGATCG  (正确)
错误 UMI3: ATCGATCA  (最后一个碱基 T→A)

如果不去管同源序列,这些看起来是 3 个不同的 UMI,但其实是同一个分子被重复统计了。结果就是:你的 UQI 比实际还要虚高,但基因表达量却被低估了,因为同一个分子被分到了多个桶里。

怎么处理?我推荐的做法是 Hamming 距离 ≤ 1 的 UMI 合并。意思是:如果两个 UMI 的序列差异不超过 1 个碱基,就把它们视为同一个分子,合并并保留质量更高的那一条。

具体的处理流程,我在一个项目中写了个小脚本:

# 伪代码示例:同源 UMI 合并
# 输入:按 CB + Gene 分组的 UMI 集合
# 输出:合并后的唯一 UMI 列表

def dedup_umi_similar(umi_set, max_hamming_distance=1):
    """
    基于 Hamming 距离合并相似 UMI
    """
    import itertools
    
    # 按 UMI 质量排序(降序)
    sorted_umis = sorted(umi_set, key=lambda x: x.quality, reverse=True)
    
    final_umis = []
    used = set()
    
    for umi in sorted_umis:
        if umi.seq in used:
            continue
        # 查找与当前 UMI 差异 ≤ 1 的所有邻居
        for other_umi in sorted_umis:
            if other_umi.seq in used:
                continue
            if hamming_distance(umi.seq, other_umi.seq) <= 1:
                used.add(other_umi.seq)
                # 只保留质量最高的那个(已在 sorted_umis 中排好)
        final_umis.append(umi.seq)
    
    return final_umis

嗯,这里要注意:Haming 距离的阈值设置要谨慎。我之前设过 2,结果把一些确实不同的稀有分子也合并了,导致假阴性。现在我一般用 1,除非数据质量极差(UMI 错误率 > 5%)才考虑放宽到 2。

避坑指南:

我曾经遇到过一个坑:某批次数据 UMI 长度是 10 bp,但我用的去重工具默认了 8 bp 的 UMI 长度。结果所有 UMI 的最后 2 个碱基都被截断了,去重时错把很多不同分子合并了。那个项目的 UQI 明明很高,但下游分析却一塌糊涂——最后排查了整整两天才发现是参数问题。所以,一定要确认 UMI 长度和你的工具配置一致。

3.5 总结一下本章重点

好了,内容不少,我来帮你收一下:

  • UMI 去重的核心是把「同分子多 read」合并成一条,减少 PCR 冗余。
  • UQI(有效 UMI 比例)是衡量文库复杂度最直接的指标,建议保持在 0.35 以上。
  • 低 UQI 排查方向:PCR 循环 > 测序深度 > 碱基质量。
  • 同源序列处理要善用 Hamming 距离 ≤ 1 的合并策略,但不要过拟合。