数据质量指标体系

质量控制这件事,说白了就是建立一套标准。

标准不清晰,后头的分析全是空中楼阁。我最早做RNA-seq项目时,上来就直接跑比对,结果一大堆垃圾数据混进去,后续差异表达算出来全是假阳性。嗯,从那以后我再也不敢跳过质控了。

多组学数据的核心指标,其实就三大类:测序质量、比对率、覆盖度。这三者是递进关系,一个环节出问题,后面的指标必然受影响。

多组学质控核心指标体系 测序质量 比对率 覆盖度 质量得分(Q20/Q30) 碱基分布 / GC含量 总比对率 唯一比对率 全基因组覆盖深度 覆盖均一性 三者存在递进关系: 测序质量 → 比对率 → 覆盖度,环环相扣

一、测序质量 —— 一切的基础

测序质量得分,行业内通行标准是 Phred 质量分数 (Q-score)。公式很简单:Q = -10 × log₁₀(P),P 是碱基识别错误的概率。

举个例子:Q30 意味着错误率 1/1000,也就是 99.9% 的准确率。Q20 对应 1% 的错误率。我个人习惯,对于常规全基因组测序,底线设在 Q30 > 80%。低于这个数,我会直接要求重测。

核心阈值参考:

  • Q20 (99% 准确率): 至少 90% 碱基达到
  • Q30 (99.9% 准确率): 至少 80% 碱基达到
  • GC 含量偏差: 理论值与实测值偏差 < 5%
  • N 占比: 未识别碱基占比 < 1%

一个小技巧: 看 FASTQC 报告时,别只看平均值。我记得有一次项目里 Q30 均值有 88%,但后 50bp 的序列直接掉到了 Q15。这种情况,截断处理比直接扔掉更划算。

我个人建议把 FASTQC 跑出来的结果做成这样一份表:

样本 总 reads Q20 占比 Q30 占比 GC 偏差 结论
正常样本50M93.2%86.7%+1.2%通过
问题样本45M81.4%68.3%+8.5%不通过
边缘样本48M89.1%78.9%-3.4%需截断

下面是一份典型的 FASTQC 质控报告截图,注意看末尾的警告:

Filename: sample_01.fastq
Total Sequences: 52678342
Filtered Sequences: 0
Sequence length: 150
%GC: 42.5

>>Per base sequence quality  pass
>>Per tile sequence quality  pass
>>Per sequence quality scores  pass
>>Per base N content  warn
>>Sequence Duplication Levels  fail
>>Overrepresented sequences  fail
>>Adapter Content  fail

嗯,这里要注意:FASTQC 里的 "fail" 并不一定代表这组数据完全不能用。它只是告诉你某个指标偏离了期望分布。比如 Adapter Content 出现 fail,你用 cutadapt 或 Trimmomatic 切一下就行。

二、比对率 —— 映射的质量衡量

测序质量过关了,下一步就是比对率。你想想看,如果有一半的 reads 都怼不到参考基因组上,那这组数据基本上就是废的。

数据类型 总比对率期待值 唯一比对率期待值 说明
WGS 人全基因组≥ 95%≥ 80%参考基因组完整度高
WES 全外显子≥ 90%≥ 75%探针捕获效率影响很大
RNA-seq≥ 70%≥ 55%转录组有重复区域
植物 / 非模式生物≥ 60%≥ 45%参考基因组质量差

我曾经踩过一个坑: 有一个植物 RNA-seq 项目,比对率只有 53%。我当时第一反应是转录组重复高,但后来发现是因为参考基因组里线粒体序列被污染了。比对时,多拷贝 reads 全怼到了线粒体区域。所以,比对率不达标先查参考基因组质量。

比对率异常的情况,我通常按以下步骤排查:

  1. 检查重复率: 如果重复率超过 40%,说明可能是 PCR 扩增偏差或建库有问题。
  2. 检查 rRNA 占比: 对于 RNA-seq,rRNA 如果超过 10%,说明 rRNA 去除不彻底。
  3. 检查比对参数: STAR 或 HISAT2 的参数设置,特别是 --outFilterMultimapNmax 和 --outFilterMismatchNmax。
# STAR 比对参数示例(我常用的)
STAR --genomeDir /ref/GRCh38 \
     --readFilesIn sample_01_R1.fastq sample_01_R2.fastq \
     --outSAMtype BAM SortedByCoordinate \
     --outFilterMultimapNmax 10 \
     --outFilterMismatchNmax 3 \
     --outFilterMismatchNoverLmax 0.05 \
     --alignIntronMin 20 \
     --alignIntronMax 1000000

这里的几个参数为什么要这么设?

  • outFilterMultimapNmax 10:允许每个 read 最多比对到 10 个位置。太高容易引入多拷贝基因的噪音,太低又会丢掉真正的同源基因。
  • outFilterMismatchNmax 3:最多允许 3 个错配。这是 150bp 读长的常见设置,错配率控制在 2% 以内。

三、覆盖度 —— 深度与广度

覆盖度这个指标,其实包含两个维度:

深度: 平均每个碱基被多少条 reads 覆盖(一般说“30x”就指平均 30 条 reads)。

广度: 有多少比例的基因组区域被覆盖到了(通常要求 ≥ 99%)。

我举个例子你就会明白。假如一个基因组的覆盖度是 30x,但某个关键区域只有 1x 覆盖,那么在这个区域里,一个碱基的测序错误就能让你得出错误的结论。

应用场景 推荐覆盖深度 最低广度要求
全基因组测序 (WGS) 变异检测30-40x≥ 99% 区域 ≥ 10x
全外显子组测序 (WES)100-200x≥ 95% 靶向区域 ≥ 50x
RNA-seq 基因表达定量20-50M reads≥ 70% 基因 ≥ 10 counts
单细胞 RNA-seq50000 reads/cell≥ 80% 细胞 ≥ 2000 genes

查看覆盖度时,我习惯用 samtools depth 统计全基因组分布,再用一个简单的 Python 脚本做分位数统计。你总不能光看平均值就结束了吧?

# 获取染色体 1 上所有位点的覆盖深度
samtools depth -r chr1 sample_01.bam > coverage_chr1.txt

# 覆盖深度分布统计
awk '{
  if($3 >= 1) covered++
  total++
  sum+=$3
  if($3 >= 10) covered_10x++
  if($3 >= 20) covered_20x++
  if($3 >= 30) covered_30x++
} END {
  print "平均深度:", sum/total
  print "覆盖率 (≥1x):", (covered/total)*100 "%"
  print "覆盖率 (≥10x):", (covered_10x/total)*100 "%"
  print "覆盖率 (≥20x):", (covered_20x/total)*100 "%"
  print "覆盖率 (≥30x):", (covered_30x/total)*100 "%"
}' coverage_chr1.txt

输出示例:

平均深度: 32.4
覆盖率 (≥1x): 99.8%
覆盖率 (≥10x): 97.2%
覆盖率 (≥20x): 91.5%
覆盖率 (≥30x): 80.3%

一个实用经验: 我处理 WGS 数据时,要求 ≥ 10x 的覆盖率必须达到 99% 以上。如果达不到这个数,CNV 的检测质量会变得非常差。对于外显子捕获的数据,重点看 捕获效率 (PCT_TARGET_BASES_20X),这个值低于 90% 说明探针设计或杂交效率有问题。

四、核心指标速查表

最后,我整理了一份各平台常见指标的合格范围。你可以直接把它贴在工位旁当参考。

平台类型 关键指标 合格范围 警戒阈值
Illumina WGSQ30 比例> 85%< 80%
总比对率> 95%< 90%
覆盖均一性 (均匀度)Gini < 0.2Gini > 0.3
PacBio HiFi平均精度> Q20< Q15
总产量> 15Gb / cell< 10Gb / cell
次读长> 10kb< 5kb
ONT 超长读长中位 read 长度> 20kb< 10kb
质量得分> Q10< Q7

这些指标之间的关系不是孤立的。测序质量差,直接导致比对率下降;比对率下降,有效覆盖度自然就缩水。所以,你做质控时一定要按顺序检查:先看 FASTQC,再看比对日志,最后算覆盖度。跳一步,就可能让后续分析徒劳无功。

我个人习惯把每个样本的质控结果汇总成一个 JSON 文件,方便自动化判断。这样项目跑完后,直接扫一眼就知道哪些样本需要重测或剔除。

{
  "sample_01": {
    "Q30_pct": 87.3,
    "mapping_rate_pct": 96.5,
    "unique_mapping_pct": 82.1,
    "mean_depth": 33.2,
    "coverage_10x_pct": 99.1,
    "gc_deviation_pct": 0.8,
    "status": "PASS"
  },
  "sample_02": {
    "Q30_pct": 72.4,
    "mapping_rate_pct": 88.7,
    "unique_mapping_pct": 74.3,
    "mean_depth": 22.5,
    "coverage_10x_pct": 94.6,
    "gc_deviation_pct": -6.2,
    "status": "FAIL"
  }
}

这样一份 JSON,配合一个简单的条件判断脚本,就能自动化完成大样本量的质控分诊。嗯,效率高了不少。