质量分数编码体系:Phred质量分数、ASCII偏移量与编码辨别

做生物信息分析的人,每天跟FASTQ打交道。但很多人只盯着序列看,忽略了那一串质量字符里的门道。说实话,质量分数才是决定比对、变异检测可信度的关键。今天我就把这套编码体系掰开揉碎讲清楚。

Phred质量分数:从测序仪到置信度

Phred质量分数,最早是Phred软件用来评估碱基识别准确率的。它的核心公式很简单:

Q = -10 × log10(P)

其中P是碱基识别错误的概率。举个例子,Q=30代表错误概率0.001,也就是千分之一。Q=20,错误概率百分之一。你想想看,Q值越高,测序越靠谱。

我在一次人类全外显子组分析项目中,发现测序公司给的Q值普遍偏低。后来查记录,原来是测序仪跑到了后期,化学试剂消耗导致质量下降。当时我建议客户重新补测了部分文库,才避免了大量假阳性变异。这个经历让我养成了习惯:拿到FASTQ第一件事就是看质量分数分布。

Q值错误概率碱基准确率
101/10 = 0.190%
201/100 = 0.0199%
301/1000 = 0.00199.9%
401/10000 = 0.000199.99%

ASCII偏移量:两个不同的阵营

问题来了:Q值是个整数,怎么存进FASTQ文件?答案是用ASCII字符。不同机构用了不同的偏移量。这里要记住两大阵营:

  • Sanger / Illumina 1.8+:ASCII偏移量33,Q范围0–93,字符范围从'!'到'~'
  • Solexa / early Illumina:ASCII偏移量64,Q范围3–62(实际常用),字符范围从'@'到'~'

为什么会偏移?早年Solexa自己定义了一套,后来Illumina收购后统一到Sanger标准。但很多老数据集流传至今,如果不搞清楚,直接把质量分数当同样标准用,分析结果会一塌糊涂。

我建议:处理任何来源不明的FASTQ时,优先用程序自动辨认编码标准。手猜很容易出错。
注意:Illumina 1.3–1.7版本用的偏移量64,但Q值范围与1.8+不同。个别公共数据库里的数据仍可能混用。我曾经从NCBI下载一批RNA-Seq数据,里面居然混了两种编码… 那晚我排查了三天才找到原因。

如何辨别编码标准?三个方法

辨别方法不复杂,但需要经验。我总结出三个思路:

  1. 观察字符范围:查看质量字符串里有没有出现在32以下(即ASCII码小于'!')或42以上(即大头针符号'*'等)。如果出现'!'(ASCII 33),说明是Sanger编码。如果最低字符是'@'(ASCII 64),那就是Illumina 1.8+之前的编码。
  2. 计算Q值分布:将ASCII字符转成数字后减去偏移量,看看Q值是否落在合理区间(0–41常见)。如果减64后出现大量负值,则偏移量应为33。
  3. 查看头信息:有些FASTQ头里会写明平台版本。例如“@SRR001666.1 1 1101 length=36”这种格式不告诉你编码。但Illumina Casava 1.8以后的版本会在头中包含“instrument:run:flowcell:lane:...”并用空格分隔,且质量分数采用Sanger标准。

说实话,最稳妥的还是写个小脚本自动检测。下面是我平时用的检测函数:

def guess_quality_encoding(qual_str):
    """根据一条质量字符串推断偏移量"""
    ascii_codes = [ord(ch) for ch in qual_str]
    min_code = min(ascii_codes)
    max_code = max(ascii_codes)
    
    if min_code >= 33 and max_code <= 74:
        # Sanger + Illumina 1.8+
        return 33
    elif min_code >= 59 and max_code <= 104:
        # Solexa / Illumina 1.3-1.7
        return 64
    else:
        # 无法确定,返回None
        return None

使用的时候,从FASTQ前1000条序列中各取一条质量字符串,统计最小值做判断。记住,不能只看一条,因为测序末位质量低可能导致字符接近'!',容易误判。

经验值:Sanger编码的字符最低通常是'!'(33),最高到'h'(104左右)。Illumina 1.8+最低为'!',最高到'J'(74)。如果看到大量小写字母(如'a' 'b'),说明用的是64偏移,否则是33偏移。

实战:用Python批量检测FASTQ编码

我写过一个比较完整的检测脚本,从文件里随机抽样判断。这里给出核心部分:

import gzip

def detect_fastq_encoding(filepath):
    # 判断是否gzip
    open_func = gzip.open if filepath.endswith('.gz') else open
    with open_func(filepath, 'rt') as f:
        line_count = 0
        min_codes = []
        for line in f:
            line_count += 1
            # FASTQ结构:@head, seq, +, qual
            if line_count % 4 == 0:  # 质量行
                qual = line.strip()
                codes = [ord(c) for c in qual]
                min_codes.append(min(codes))
            if line_count >= 1000:
                break
        overall_min = min(min_codes)
        if overall_min >= 33 and overall_min < 59:
            return 33
        elif overall_min >= 59:
            return 64
        else:
            return None

这个脚本只统计前250条序列(1000行÷4 = 250条),效果足够。我曾在一个公开数据集上跑它,发现最小字符是'B'(ASCII 66),果断判断64偏移,后续分析一切正常。

两个编码标准的详细对比

特性Sanger / Illumina 1.8+Solexa / Illumina 1.3–1.7
ASCII偏移量3364
最小字符'!' (33)'@' (64)
常用Q值范围0–41(实际常用2–40)3–62
对应公测系列Phred+33Phred+64
是否兼容Illumina Hiseq 2000/2500是(1.8+)否
数据处理工具默认支持绝大多数现代工具需指定参数

这里多说一句:很多新手直接用python的`ord()`减去33就以为得到真实Q值,如果文件实际是64偏移,算出来的Q值全都偏高30多,那后续QC就全错了。

Phred质量分数编码流程 测序仪碱基识别 计算错误概率P (如P=0.001) Q = -10*log10(P) (例Q=30) 选择ASCII偏移量 33(Sanger)或64(Solexa) 生成ASCII字符 写入FASTQ质量行 ⚠ 辨别关键:查看最小ASCII码;Sanger编码最小字符为'!'(33),Solexa编码最小为'@'(64) 现代测序平台(Illumina 1.8+)统一使用偏移量33,但旧数据仍可能混用64偏移

上面这个流程总结了从碱基call到FASTQ质量字符串的转换。你可以看到,偏移量的选择决定了最终字符落在哪个区间。

避坑指南

最后分享几个我踩过的坑:

  • 不要相信文件后缀或描述:我曾经遇到一个标记为“.fastq”的文件,实际是旧的Illumina 1.3格式,里面质量字符全是'@'和'A'。查了半天才发现。
  • 混合数据最麻烦:如果一条序列的质量字符串里同时出现低质量字符和高质量字符,比如既有'!'又有'@',那肯定是混合编码(几乎不可能存在,但确实见过)。这时需要分离来源。
  • 单端和双端文件的编码可能不同:有人把R1和R2跑在不同版本bcl2fastq上导出,结果两个文件编码不一致。建议同时检测。
一句话总结:拿到FASTQ,先用脚本检测偏移量;偏移量确定后,后续所有QC和比对工具的参数才不会出错。

掌握了Phred质量分数的原理和编码辨别方法,你就能在数据分析的第一步就卡住潜在错误。我个人的习惯是,写一个自动检测函数嵌入到预处理流程里,每次跑数据都自动输出编码类型。这样既省心,又避免人为失误。