FASTA与FASTQ格式详解:测序数据解析与转换全指南
前几天帮同事排查一批测序数据她双击一个.fastq文件Windows直接弹窗提示文件格式或文件扩展名无效。她以为文件损坏了急得不行。我过去看了一眼文件本身好好的只是她用Excel打开了fastq格式的文本文件。这种事我在不同场合见过太多次。FASTA和FASTQ是生物信息学里出镜率最高的两种文件格式——前者是序列的“名片”后者在名片背后多附了一份“体检报告”。它们看起来都是纯文本但格式规则完全不同解析方式也完全不同。如果你是刚接触生物信息学、经常要处理next-generation sequencing数据或者需要写脚本做格式转换的人这篇文章把两个格式的来龙去脉、解析要点和踩过的坑一次讲透。1. 从“文件打不开”说起文件格式不是扩展名而是内容的约定1.1 当你试图用Excel打开fastq时发生了什么很多人第一次接触测序数据时习惯性双击文件系统弹窗问“要用什么方式打开”。选Excel之后要么一片乱码要么出现“文件格式或文件扩展名无效”的警告。这里有个核心误解fastq和fasta本质上是纯文本文件但它不是给表格软件读取的通用文本。它的内容有严格的语义规则——哪一行是标识、哪一行是序列、哪一行是质量值这些规则写在每一行里而不是靠扩展名告诉软件。Excel遇到这种结构不知道如何处理自然报错。你完全可以用文本编辑器VI/VSCode/Notepad打开FASTQ看一眼前几行就能明白它是什么。但注意一个几十GB的测序文件用文本编辑器打开会直接把内存吃满所以生产环境中推荐用head命令或zcat命令预览。1.2 FASTA和FASTQ各自承担什么角色在一个典型的全基因组测序或转录组分析流程里FASTA和FASTQ分工非常明确。FASTA存储参考基因组、转录本、蛋白序列。它只关心“序列是什么”不关心“这个碱基测得多准”。比对工具建立索引比如BWA的index、STAR的genomeGenerate用的都是FASTA。FASTQ存储测序仪直接产出的reads。除了序列本身还保存每个碱基的质量分数也就是测序仪对这个碱基判读的自信程度。质控、过滤、比对等几乎所有下游分析输入都是FASTQ。用一句话概括FASTA是“标准答案册”FASTQ是“考生答卷每道题的涂卡置信度”。两者形态相似但应用阶段完全不同互相之间不能无脑替代。1.3 为什么总有人把两者搞混我会说这不能全怪使用者。这两个格式长得太像了都是文本、都以一个特殊符号开头、核心内容都是ATCG。很多教程又是混着讲初学者连格式名字都记错更别说区分规则。从技术角度讲两者最大的差异在于信息密度和结构约束。FASTA对换行几乎没有限制FASTQ则严格规定四行一组。理解了这个本质后面所有细节都顺理成章。2. FASTA格式拆解一个“”号加自由文本但坑在细节里2.1 标识行的规范与常见误解FASTA的标准结构是两段式第一行以“”开头后面紧跟序列标识和可选的描述文字从第二行开始是序列本身可以换行直到下一个“”开头的行为止。例如chr1 chromosome 1 ACGTACGTACGT ACGTACGTACGT chr2 TTTTCCCCAAAAGGGG“”开头的行叫标识行header line。问题来了“”后面能写什么在NCBI/ENA等数据库导出的FASTA里标识行通常遵循“accession 描述”的固定模式比如NM_001301717.2 Homo sapiens cadherin 1 (CDH1), mRNA解析时常见的做法是把第一个空格前的部分当作序列ID空格后的所有内容当作描述。但历史数据里还混着旧式的NCBI格式gi|123456|ref|NM_123456.1| description这种格式用“|”分割多个字段。如果你的脚本只按空格切分得到的ID会变成一长串带竖线的字符串后面做比对或去重时就麻烦了。更稳妥的方案是规范化解析时只取第一列然后用正则或split(|)进一步提取真正的accession号。我自己习惯在写解析脚本时把ID方式和描述信息分开处理宁可多写两行也不在清洗环节埋雷。标识行还有一个容易被人忽略的约束“”后面不能直接换行。一个空的标识行在很多工具里不会报错但会生成一个没有名字的序列记录后续如果有脚本按“”后的第一个词建立文件名或者做批量映射就会翻车。2.2 序列行的换行自由度两行还是两百行FASTA对序列行的长度没有任何强制要求可以一整条序列放一行也可以几十个碱基就换一次行。参考基因组就是这么干的——每条染色体序列按60或80个字符一行的方式排列。这个设计在工作原理上很宽容但对写解析脚本的人是个陷阱。很多人第一次写FASTA解析器时容易直接取“”后的第二行作为完整序列。遇到单行序列数据没问题一旦遇到换行排列的参考基因组取到的序列只是第一条染色体的一小段后面全部丢失。正确的FASTA解析逻辑是遇到“”开头说明新序列开始从下一行起把所有不以“”开头的内容按顺序拼接起来遇到下一个“”或文件结束输出拼接后的完整序列用Python演示def parse_fasta(filepath): records [] seq_id None seq_lines [] with open(filepath, r) as fh: for line in fh: line line.strip() if not line: continue if line.startswith(): if seq_id is not None: records.append((seq_id, .join(seq_lines))) seq_id line[1:].split()[0] seq_lines [] else: seq_lines.append(line) if seq_id is not None: records.append((seq_id, .join(seq_lines))) return records这种写法对所有合法的FASTA文件都安全。如果你在生产环境里不想自己造轮子直接用Biopython的Bio.SeqIO.parse(handle, fasta)即可。2.3 合法字符与非标准字母N和U容易让新手懵FASTA序列里会出现A、T、C、G、N这是基本盘。N表示未知碱基或gap区域的占位。RNA序列则是A、U、C、G比如某些病毒基因组。蛋白质序列则使用20种氨基酸单字母代码外加BAsx、ZGlx、X未知氨基酸、*终止密码子等字符。如果写校验脚本强烈建议按数据类型做不同的字符集白名单而不是笼统地接受所有字母。一个典型的隐患是把RNA数据当成DNA处理U没有在DNA白名单里被过滤或替换后产生错误结果。还有一个容易忽略的情况是FASTA文件里混入冗余的空白字符空格、制表符虽然很多解析器会自动忽略但如果你写了严格比对逻辑最好先做一层清洗。3. FASTQ格式的核心四行铁律、Phred质量值和编码偏移3.1 一次读完四行而不是“一行一行猜”FASTQ最根本的规则是每四行代表一条read。各行含义如下EAS139:136:FC706VJ:2:2104:15343:197393 1:Y:18:ATCACG CTCAAGGTTGTTGCAAGACGGGAGGTGGTGTAGGAGCAAAGATCCTGAG 第1行以“”开头是read的标识符通常包含仪器编号、flowcell编号、lane、tile及坐标等元数据第2行碱基序列第3行以“”开头可以只写一个“”也可以重复第1行的标识符早期一些平台会这么做第4行质量字符串包含与第2行序列每个碱基一一对应的质量字符我在写解析脚本时始终遵循一条原则按四行一组推进而不是用“开头就是新read”的正则。之所以这样是因为质量行里也可能出现“”字符ASCII 64对应Phred质量值31的Sanger编码。你按行扫描以为发现了一个新read实际只是上一行的质量字符串里恰好有个“”。这个问题非常经典我在后面的踩坑案例里会详细展开。3.2 Phred质量分数Q20和Q30到底意味着什么质量字符串里的每个字符不是随便写的它是Phred质量分数的ASCII编码。Phred质量分数定义是Q -10 * log10(P)其中P是该碱基判读错误的概率。举个例子Q20P 1/100即每100个碱基里预计有1个错误Q30P 1/1000每1000个碱基里预计有1个错误Q40P 1/10000每10000个碱基里预计有1个错误也就是说Q后面的数字越大这个碱基越可信。这个分数最早来自华盛顿大学的Phred软件后来被Illumina等平台沿用。日常做质控时大家常说的“Q30比例”指的就是reads中质量值大于等于30的碱基所占比率。3.3 ASCII偏移大坑Phred33还是Phred64质量分数不能直接以数字形式写入文件那样太占空间。标准做法是把Q值加上一个偏移量再转成对应的ASCII字符。这样质量值0到40就用一串可见字符来表示。Phred33也叫Sanger编码ASCII Q 33。Q20对应ASCII 53即字符“5”Q30对应ASCII 63即字符“?”。当前绝大多数新数据都是这种编码。Phred64ASCII Q 64。Q20对应ASCII 84即字符“T”。这是早期Illumina分析流程1.3到1.7版本用的编码现在基本看不到了但老数据里偶尔会遇到。Solexa编码更老不同公式几乎只在2008年以前的数据里出现遇到概率极低。判断一个FASTQ是33还是64最直接的方法是扫描质量行的ASCII最小值。如果质量行的所有字符ASCII都小于等于74以I、H、G、F为常见高质量标志或者最小值在59以下基本是Phred33。如果质量行出现了大量ASCII大于等于75的字符比如h、i、j这类小写字母后的字符就要怀疑是Phred64。我常用的一个快速判断脚本长这样import gzip def guess_phred_offset(fastq_path, lines_to_scan2000): opener gzip.open if fastq_path.endswith(.gz) else open min_ascii 255 with opener(fastq_path, rt) as fh: count 0 for idx, line in enumerate(fh): if idx % 4 3: line line.strip() for ch in line: min_ascii min(min_ascii, ord(ch)) count 1 if count lines_to_scan: break if min_ascii 64: return 64 return 33这里的逻辑很简单Phred64编码下Q值最小也不会低于2左右ASCII最小约66Phred33编码下哪怕质量再差最小ASCII等于33的“!”也低于64。用最小值作为判据即可。3.4 质量字符串长度必须等于序列长度这是FASTQ最常见的校验点。第2行序列有多少个字符第4行质量字符串就必须有多少个字符。稍有不符这条read就应该被判定为损坏。出现长度不一致常见原因有几种序列行被意外截断文件传输中断、磁盘写满Windows和Unix换行符混用质量行末尾多出一个\r序列行里混入了空格或制表符肉眼看不出来但字符计数对不上FastQC、seqtk等标准工具遇到长度不一致时通常直接标注异常。你自己写脚本时也应该做这个检查因为后面比对工具遇到质量长度不匹配的fastq行为会非常诡异——有的直接报错有的静默忽略质量值还有的错位解析导致边际信息全乱。3.5 质量行里的字符你根本想不到质量字符串的取值范围是整个ASCII可见字符区间从!到~而不仅仅是字母和数字。这意味着“”、“”、“”、“#”这些在其它场景有特殊意义的字符都可能出现在质量行里。这直接决定了解析方式。如果你写一个解析器逻辑是“遇到开头就当作新read”遇到质量行里的就会误判行结构。唯一的正确做法是按固定四行一组读取读取过程中校验第1行是否以开头、第3行是否以开头、第2行和第4行长度是否一致。除此之外不要对行内容做任何假设。4. 转换实操FASTQ与FASTA互转的正确姿势4.1 别在搜索框里找“格式转换器”很多人遇到fastq转fasta第一反应是搜索“fastq转fasta工具”或“文件格式转换器下载”。我建议你直接放弃这个念头。测序原始文件动辄几个GB到几百GB网页版转换器传不上去本地下载的所谓转换器很多不过是包了一个脚本外壳有的还是十几年前用VB/C#写的在新系统上打开就报“mscomctl.ocx文件格式不再支持”之类的错误折腾半天纯属浪费时间。真正可靠的做法永远是命令行或自己写几行脚本。转换逻辑本身并不复杂掌握原理后你能在任意环境下搞定。4.2 fastq转fasta不是“删掉后两行”这么简单表面上看fastq转fasta就是保留第1行的标识把换成和第2行的序列丢弃第3、第4行。但实际上有很多细节要注意。第1行的换成后面内容不能改动遇到空行或行尾换行符不统一时要统一处理注意文件可能是gzip压缩的不能直接open读取如果一条read的序列本身就比较长输出为fasta时建议保持与输入一致的单行序列方便后续工具读取下面这段代码是我常用改法按四行分块读取同时做了基本校验import gzip def fastq_to_fasta(fastq_path, fasta_path): opener gzip.open if fastq_path.endswith(.gz) else open with opener(fastq_path, rt) as fq, open(fasta_path, w) as fa: while True: header fq.readline() if not header: break seq fq.readline() plus fq.readline() qual fq.readline() if not (header and seq and plus and qual): raise ValueError(文件提前结束四行结构不完整) if not header.startswith(): raise ValueError(第1行不是以开头文件可能已损坏) if not plus.startswith(): raise ValueError(第3行不是以开头文件可能已损坏) fa.write( header[1:].strip() \n) fa.write(seq.strip() \n)注意我用了strip()而不是只去掉换行符目的是同时清理掉Windows的\r。这个细节在很多脚本里被忽略后面我会用实际案例说明它有多坑。4.3 反向转换fasta转fastq最大的问题是质量值从哪来fasta转fastq不像想象中那么简单因为FASTA里没有质量信息。如果你只是想把fasta变成fastq格式以适配某个流程你必须决定质量值用什么。常见做法是给所有碱基设一个固定质量值比如全部写成“I”Phred33编码下代表Q40或者全部写成“5”Q20。这相当于告诉下游工具“这条序列置信度很高/中等”。但务必想清楚这样生成的文件并不包含真实的碱基质量只适合格式占位、测试流程等场景绝不能用它来做真正的变异检测或定量分析。如果你手头的fasta本身就是从某条fastq转过来的那就直接找回原始fastq而不是伪造质量值。反向转换的正确使用场景只有一个上下游工具要求输入必须为fastq而你的数据源确实没有质量信息。4.4 awk和sed一行流快速但不无脑在Linux服务器上处理单行的fastq序列行不允许换行awk一行流确实好用awk NR%41{print substr($0,2)} NR%42{print} input.fastq output.fastased版sed -n 1~4s/^//p; 2~4p input.fastq output.fasta但这里有个前提只适用于标准四行fastq且序列不分行。如果你的fastq来自老平台或者经过某些工具转存序列可能被拆成多行这个一行流就会出错——因为NR%4的分组逻辑会被打乱。生产环境里我会先跑一个行数统计确认文件是4的倍数再决定能不能用awk/sed。4.5 压缩文件gzip数据别先解压学会直接读绝大多数测序平台输出的fastq都是gzip压缩的后缀是.fastq.gz。处理时不需要先解压再用完全可以直接读取。# 预览 zcat sample.fastq.gz | head -n 8 # 转换 zcat sample.fastq.gz | awk NR%41{print substr($0,2)} NR%42{print} output.fasta # 统计行数 zcat sample.fastq.gz | wc -l在Python里用gzip.open的rt模式直接打开即可。千万不要图方便先gunzip一个100GB的fastq解压后能占300GB磁盘纯属自找麻烦。5. 踩坑实录四段真实排查过程5.1 质量行里的骗过了我的正则序列数翻了三倍有一次我在处理一批双端测序数据写了一个“极简版”fastq转fasta脚本逻辑是for line in file: if line.startswith(): # 新的read结果输出的fasta序列数比预期多了一倍多序列长度也普遍变短。我一开始以为是数据本身的问题直到用head -n 20对比原始文件才发现质量行里出现了大量”“字符。我的脚本把这些当作新read的起点于是原本一组的四行被切成两半整个文件全乱套。排查过程其实很简单先用wc -l统计总行数发现不是4的倍数再用awk NR%41提取真正的第1行数量才和预期一致。这个案例告诉我处理fastq时千万不要靠内容猜边界四行结构才是唯一可靠的分组依据。5.2 Windows下生成的fastq质量字符串每个都多了一个字符另一个项目里合作方从Windows机器上传来一批fastq。我跑比对时工具报错提示序列长度和质量长度不匹配。打开文件一看每隔四个字符后冒出来一个奇怪的^M符号——这是Windows系统行尾的\r。因为\r算一个ASCII字符质量行的有效字符只有39个加上\r就成了40个和序列的39个碱基对不上。解决方式分两种写脚本读取时统一用rstrip(\r\n)清理行尾如果文件已经落到Linux上用dos2unix工具把整个文件转成Unix行尾从那以后我在所有解析脚本里都统一用rstrip(\r\n)而不是strip()或rstrip(\n)避免类似的坑再出现。5.3 老数据的Phred64被我当成33质量值整体偏移在我处理一个2011年左右的RNA-seq旧数据集时FastQC报“Per base sequence quality”整个图异常偏高质量值全都顶到上限。我抽了一行质量字符串发现全是h、i、j这类ASCII超过100的字符明显不对劲。用脚本统计最低ASCII发现最小也有75左右这才意识到这是Phred64编码。如果当时没发现继续按33解析下游用质量阈值过滤reads时会把大量低质量数据放进来或者把高质量数据误杀结果会非常难看。从那以后我处理任何来源不明的fastq第一件事就是跑一遍偏移量判断脚本。现在新版Illumina的bcl2fastq默认输出33这个问题主要出现在老数据或者第三方转存的文件里但不能因此放松警惕。5.4 多行FASTQ重组成标准四行古早数据带来的额外工作我还遇到过一批来自早期Solexa平台的fastq序列行被按照每行50个字符的样子切成了多行。这种格式不是标准fastq但当年确实有工具这么输出。处理思路是先把序列行和质量行分别拼起来再造标准四行结构def merge_multiline_fastq(input_path, output_path): reads [] with open(input_path, r) as fh: lines [line.strip() for line in fh if line.strip()] idx 0 # 先按开头的规则找出每个read的起始位置 starts [i for i, line in enumerate(lines) if line.startswith()] for si in range(len(starts)): start starts[si] end starts[si 1] if si 1 len(starts) else len(lines) block lines[start:end] header block[0] seq .join(block[1:-2]) plus block[-2] qual .join(block[-1:]) reads.append((header, seq, plus, qual)) # 校验长度并写出标准4行fastq这个脚本需要注意如果序列中恰好有以开头的行比如质量行里出现了上面的start检测会误判。稳妥的做法是先看文件的物理结构确认这种多行格式的规律再写对应的重组逻辑。好在这种格式现在基本绝迹遇到也只是历史项目里的个案。6. 常用工具与自查清单避免格式问题变成数据灾难6.1 快速判断文件类型的几个命令在服务器上拿到一个数据文件我建议先做这几个动作全程不超过30秒# 查看文件类型 file sample.fastq.gz # 预览前8行 zcat sample.fastq.gz | head -n 8 # 统计总行数 zcat sample.fastq.gz | wc -l # 统计reads条数真正的第1行数量 zcat sample.fastq.gz | awk NR%41 | wc -l如果总行数不是4的倍数基本可以断定文件有损坏或格式非标准。注意不要用grep -c ^来统计reads条数因为质量行里的会造成误计数。6.2 处理fasta/fastq的常用工具seqkit国产开源工具专门处理fasta/fastq支持统计、筛选、转换、洗牌、抽样等功能简单命令就能处理非常大的文件samtools在处理比对结果时常用但也可以做fastq到bam的转换seqtk老牌工具轻量级支持多种fastq/fasta操作BioPython的SeqIO适合写脚本时做标准解析个人经验是能直接用现成工具的不要自己写脚本。seqkit的seqkit stats命令能输出每个文件的reads数、总碱基数、平均长度、GC含量等关键指标这是我最先跑的命令。6.3 一分钟格式自查清单检查项目操作正常标准文件总行数zcat file | wc -l是4的整数倍首行起始符号zcat file | head -1fastq是fasta是序列字符白名单随机抽100行序列只含ATCGN或氨基酸代码序列长度与质量长度awk或Python比对必须完全一致质量字符串ASCII范围Python脚本统计最小ASCII判断是33还是64行尾格式file命令或cat -A查看统一为Unix换行符这六项检查做完文件格式方面的隐患基本能排除掉。再往下就是内容层面的问题比如接头污染、重复比率、GC偏差那是FastQC等质控工具的工作范围。最后一个建议写任何与fastq、fasta相关的脚本前先准备好一个已知正确的小样本作为基准。比如用seqkit sample抽前1000条reads跑通脚本后再对整个文件执行。格式问题最怕批量操作后才暴露前置校验永远比事后修复省时间。