第一章 GFF3 文件概述与格式基础
好,咱们正式开始。GFF3 这个东西,说白了就是生物学家用来描述基因组上各种“特征”的一种标准格式。基因在哪?外显子怎么拼?这个基因有几种剪接体?这些小信息,全靠 GFF3 来记录。
我最初接触它的时候,第一次看那个文件,简直头皮发麻——全是 tab 分隔的字段,还经常混着注释和 FASTA。但后来用得多了就发现,只要搞清楚那 9 列到底在说什么,这格式其实非常简洁。
一句话总结: GFF3 是“序列特征注释”的格子本,每一行代表一个特征(feature),由 9 个 tab 分隔的列组成。
1.1 核心格式规则
GFF3 文件是纯文本,每行一个特征。注释行以 # 开头。真正的数据行必须包含 9 列,列之间用 Tab 分隔,不能用空格。我把这 9 列的定义列在下表,你对照着看会清晰很多。
| 列号 | 列名 | 说明 | 个人踩坑点 |
|---|---|---|---|
| 1 | seqid | 参考序列 ID(染色体、scaffold 等) | 不能有大写以外的字符?其实大小写都可以,但要跟参考基因组 fasta 的 ID 完全一致。我遇到过因为多了一个空格导致后续标注全部错位的案例 |
| 2 | source | 注释来源(软件、人工、数据库) | 这里常用“.”代表未知。但很多下游工具会过滤掉 source 为空的条目,所以最好写上工具名称,比如 BRAKER2 |
| 3 | type | 特征类型(gene, mRNA, CDS, exon 等) | 大小写敏感!SO 术语库中 exon 是小写,CDS 是大写。我曾经把 CDS 写成 cds,结果 IGV 直接不显示 |
| 4 | start | 特征起始位置(1-based) | 一定是正整数。如果是负链特征,start 是左边界?不,start 永远取较小的坐标值(5' 端) |
| 5 | end | 特征终止位置(包含) | 必须 ≥ start。区间长度 = end - start + 1。有一次我写的脚本算错了,导致一个外显子长度为 0,直接被 GFF3 验证器报错 |
| 6 | score | 可信度分数(常用 0–1000) | 如果工具没给分,写“.”。注意:很多质控工具会把这个字段当作锚点,若它是小数一定要保留原样,别取整 |
| 7 | strand | 链方向:+ 正链,- 负链,. | 缺失写“.”,但建议都标上。如果正负链混着写,后续做剪接体统计时会被误解 |
| 8 | phase | CDS 相位(0, 1, 2, .) | 只对 CDS 特征有意义。我曾见过有人把外显子的 phase 也填了数字,导致翻译框架计算全乱套 |
| 9 | attributes | 属性键值对(key=value; 分号分隔) | 这里坑最多。ID 必须是唯一的,Parent 必须指向已存在的 ID。我习惯用一个 check_gff3_attributes.py 的小脚本提前跑一遍 |
一个小技巧: 写代码解析 GFF3 时,永远用
split('\t') 分割行,而不是按照空格。因为第九列的属性里也可能有空格,用 split() 默认按空白字符会搞乱列计数。
1.2 层级结构与 ID 规则
GFF3 最牛的地方,就是用 ID 和 Parent 把特征串成树。比如:
chr1 BRAKER2 gene 1000 9000 . + . ID=gene001;Name=ABC1
chr1 BRAKER2 mRNA 1000 8500 . + . ID=transcript_g001.1;Parent=gene001
chr1 BRAKER2 exon 1000 1500 . + . ID=exon_g001.1.1;Parent=transcript_g001.1
chr1 BRAKER2 CDS 1200 1500 . + 0 ID=cds_g001.1.1;Parent=transcript_g001.1
看到没?mRNA 的 Parent 指向了 gene001,exon 和 CDS 的 Parent 指向了那个 mRNA。这样,一个基因下面可以挂多个转录本,每个转录本下面再挂外显子和 CDS。这种层级结构也是质控的检查重点——任何孤儿特征(有 Parent 但 Parent 不存在)都是不合格的。
下面这张图,我画出了最典型的 GFF3 层级结构,你一看就明白特征之间的父子关系了。
我曾经犯过的错: 在写批量转换脚本时,把所有特征的
Parent 都写成了 gene001,但每个 mRNA 的 ID 是自动生成的(比如 gene001.t1),结果外显子和 CDS 全部指向了基因,而不是 mRNA。这样的文件到了 IGV 里,基因结构显示就是一条直线,没有外显子间隔。后来我花了一整天用 gff3_validate 才找出问题——所以,一定要验证 GFF3 的层级完整性。
1.3 第九列 attributes 的常见陷阱
第九列是坑最多的地方。我总结几个常见的:
- ID 重复: 同一个文件里,ID 必须全局唯一。如果你有两个不同的基因都叫
gene001,后面的会覆盖前面的。检查时可以用grep -oP '(?<=ID=)[^;]+' file.gff3 | sort | uniq -d看看。 - 值中包含特殊字符: 分号
;是属性分隔符,等号=是键值分隔符。如果基因名字本身就带分号?别慌,用 URL 编码%3B代替。我见过一个物种的基因名叫ABC1;DEF2,直接写进去导致解析器以为有两个属性。 - 属性名拼写: 官方推荐的小写属性名包括
ID,Parent,Name,Alias,Note,Dbxref,Ontology_term。大小写敏感,Name和name是两个不同的属性。很多工具只认Name。 - 缺少必须属性: 按照规范,每个特征必须有
ID(除非是假想特征如region)。但有些工具生成时漏掉,这种文件在大部分下游流程中直接报废。
我自己的避坑心得: 每次处理新 GFF3 文件前,先用
gff3-validator 跑一遍(NCBI 提供的小工具),它会提示缺失 Parent、ID 重复、不合理相位等问题。如果文件太大,至少用 head -1000 取样验证,别等到分析到一半才报错。
1.4 格式验证小工具的使用
这里给一个我常用的命令行检查组合(假设你安装了 gff3-validator):
# 基本语法验证
gff3-validator my_annotation.gff3
# 检查是否存在孤儿特征(有 Parent 但该 Parent 不存在)
awk -F'\t' '{if($9 ~ /Parent=/) print}' my_annotation.gff3 | \
grep -oP '(?<=Parent=)[^;]+' | \
sort -u > /tmp/parents.txt
awk -F'\t' '{if($9 ~ /ID=/) print}' my_annotation.gff3 | \
grep -oP '(?<=ID=)[^;]+' | \
sort -u > /tmp/ids.txt
comm -23 /tmp/parents.txt /tmp/ids.txt # 输出孤儿特征对应的 Parent
# 统计特征类型分布(快速质控)
awk -F'\t' '{print $3}' my_annotation.gff3 | sort | uniq -c | sort -rn
嗯,这些都是我早期踩坑之后慢慢攒下来的脚本。你想想看,如果一个 50MB 的 GFF3 文件,读到一半报“Parent 不匹配”,你得花多少时间定位?所以,每次上游交付文件,我都先跑一遍这个组合拳。