2. GFF/GTF 文件结构解析:第 1-9 列详解

说实话,我第一次拿到 GFF 文件的时候,看着那堆用 Tab 分隔的字符串,头都是大的。后来做基因注释项目,天天跟这些列打交道,慢慢就摸透了。这第 2 章,我就把 9 列结构掰开揉碎讲给你听,顺便聊聊属性列里那些容易踩的坑。

2.1 九列,一个都不能少

GFF/GTF 文件是严格的 9 列格式,列之间用 制表符 分隔。我用表格对一下每列的意思,方便你对照。

列号 列名 内容说明 常见错误
1 seqid 序列 ID(染色体、scaffold 名) 包含空格或特殊字符(如 "chr1" 写成 "chr 1")
2 source 注释来源(软件名、数据库名) 空值直接用点(.)代替
3 type 特征类型(gene, mRNA, exon…) 大小写不一致("exon" vs "Exon")
4 start 起始坐标(1-indexed) 坐标方向写反,或 start > end
5 end 终止坐标 坐标超出参考序列长度
6 score 可信度分数(可用点.代替) 非数值字母混入
7 strand 链方向:+ 正链,- 负链,. 未知 写成 0/1 而非 +/-
8 phase CDS 相位(0,1,2,仅用于 CDS) 非 CDS 行也填了数字,或 CDS 行漏填
9 attributes 属性键值对(分号分隔) 引号漏加、分号多余、键名拼错

每列都要求是原子值——你不能在一列里塞两个东西。比如第 1 列如果写成 chr1;chr2,下游工具直接罢工。

我踩过的坑: 曾有个 GTF 文件,第 2 列 source 填成了 "Augustus; Maker",用了分号而不是点。结果 gffread 解析时报错,花了半天才找到原因。记住:一列只放一个值,没有就用点 . 占位。

2.2 属性列(第9列)—— 最复杂的部分

属性列是整个文件的灵魂,也是最容易出问题的地方。GTF 和 GFF3 对属性列的写法要求不太一样,我单独拎出来讲。

GTF 属性格式

GTF 的属性列格式是:

gene_id "ENSG000001"; transcript_id "ENST000004";

要求:键后跟空格,值必须用双引号包起来,多个键值对用分号空格 ; 分隔,最后一个键值对后面也得分号。

硬性规定:

  • gene_id 和 transcript_id 必须存在(GTF 规范强制要求)
  • 值中不能有双引号内未转义的特殊字符
  • 键名尽量用小写,但不会区分大小写

GFF3 属性格式

GFF3 的属性列格式是:

ID=gene001;Name=BRCA2;Parent=scaffold1

要求:键=值(没有空格),值不需要引号(除非值本身包含特殊字符),用 ; 分隔。禁止使用空格,如果值中有空格必须用 %20 编码。

关键差异:

维度 GTF GFF3
键值分隔符 空格,值加引号 等号,值不加引号
键值对分隔 分号+空格 分号
值中空格 可直接写(在引号内) 必须用 %20 编码
保留键名 gene_id, transcript_id 必填 ID, Name, Parent 保留但非强制

我个人习惯是:GTF 文件保持双引号统一,GFF3 文件保证等号两边绝对不要加空格。 很多工具对这一点非常敏感,多一个空格就报错。

一个小技巧: 如果你不确定属性列写没写对,跑一个 gtf2gff3 或 gff3check(这都是 genometools 里的命令),它会明确告诉你哪里有格式问题。

2.3 引号与转义—— 不起眼但致命的细节

为什么引号容易出事?因为有些工具有自己的解析逻辑。我举几个真实场景。

双引号 vs 单引号

GTF 规范只认 双引号。单引号会被当成普通字符,导致属性值被截断。下面的写法就有问题:

gene_id 'ENSG000001'; transcript_id 'ENST000004';  # 错误!应该用双引号

值内包含引号

假如基因名就叫 gene"X"(虽然有违常识,但难免遇到),GTF 里你需要用 " 实体编码:

gene_id "gene"X""; transcript_id "tx001";

GFF3 里则要用 %22:

ID=gene%22X%22;Name=gene-X

分号在值里

有些注释系统会把分号放在描述字段中,比如 Note=mutation; important。在 GFF3 里,分号是属性分隔符,所以必须用 %3B 编码:

ID=mutation;Note=mutation%3Bimportant

我曾经遇到一个难题: 一个公共 GTF 文件中,某个 transcript 的 transcript_id 值里居然包含换行符。在 Linux 下用 wc -l 数行数,结果比实际多出几千行。后来我用 sed 把换行符替换成空格,才恢复正常。所以检查属性列时,别忘了用 od -c 看看有没有不可见字符。

2.4 完整结构示意图

下面这张 SVG 图把 9 列的层次关系和属性列的内部结构串起来,你可以用它对照着理解。

GFF/GTF 文件 9 列结构 1: seqid 2: source 3: type 4: start 5: end 6: score 7: strand 8: phase 9: attributes (最重要) 属性列内部结构 GTF: gene_id "ENSG1"; transcript_id "T1"; GFF3: ID=gene001;Name=BRCA2;Parent=scaff1 ✅ 正确示范 chr1\t.\tgene\t1\t1000\t.\t+\t.\t gene_id "BRCA2"; ❌ 常见错误 chr1\t.\tGene\t1 1000\t.\t+\t0\t gene_id=BRCA2; (GTF混用等号) * 每一列必须用 Tab 分隔,不要使用空格或逗号

记住这个口诀: 九列逐行,Tab 分家;源类坐起,分数链相;属性最杂,键值对加。写之前先想清楚你用的是什么格式(GTF 还是 GFF3),再选引号和分隔符。

2.5 属性质检快速清单

每次修复文件之前,我都会用下面几个条件检查一遍属性列,能省掉 90% 的调试时间。

  1. 是否包含必须键? GTF:gene_id 和 transcript_id 一个不能少;GFF3:特征类型对应 ID/Parent 不能乱填。
  2. 引号是否匹配? GTF 双引号必须是成对闭合的。
  3. 分号位置: 每个键值对之后都有分号(包括最后一个)。
  4. 空格陷阱: GFF3 的等号两侧不要有空格,GTF 的引号内可以有空格但外部不能有多余空格。
  5. 特殊字符: 值中有分号、换行符、= 符号的必须按规范编码。

我个人习惯在检查时跑一行 awk -F'\t' '{print $9}' your.gtf | head -5,先看看属性列的原始样子。如果看到等号没加引号,或者分号后面没有空格,基本就能定位到问题。

行了,第 2 章就是这些内容。结构清楚了,下面就该动手修文件了。别急,后面几章我会带你一步一步处理真实错误场景。