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,下游工具直接罢工。
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 列的层次关系和属性列的内部结构串起来,你可以用它对照着理解。
记住这个口诀: 九列逐行,Tab 分家;源类坐起,分数链相;属性最杂,键值对加。写之前先想清楚你用的是什么格式(GTF 还是 GFF3),再选引号和分隔符。
2.5 属性质检快速清单
每次修复文件之前,我都会用下面几个条件检查一遍属性列,能省掉 90% 的调试时间。
- 是否包含必须键? GTF:gene_id 和 transcript_id 一个不能少;GFF3:特征类型对应 ID/Parent 不能乱填。
- 引号是否匹配? GTF 双引号必须是成对闭合的。
- 分号位置: 每个键值对之后都有分号(包括最后一个)。
- 空格陷阱: GFF3 的等号两侧不要有空格,GTF 的引号内可以有空格但外部不能有多余空格。
- 特殊字符: 值中有分号、换行符、= 符号的必须按规范编码。
我个人习惯在检查时跑一行 awk -F'\t' '{print $9}' your.gtf | head -5,先看看属性列的原始样子。如果看到等号没加引号,或者分号后面没有空格,基本就能定位到问题。
行了,第 2 章就是这些内容。结构清楚了,下面就该动手修文件了。别急,后面几章我会带你一步一步处理真实错误场景。