4. 序列ID(seqid)命名规范与陷阱

序列ID,也就是GFF3文件里第一列的seqid,是每个注释行与基因组序列之间最直接的桥梁。很多人觉得它不过是个名字,写对了就行——但我在项目中见过无数次,因为seqid的拼写、大小写、甚至一个多余的空格,导致整个分析流程直接崩掉。

今天我们就来把seqid的坑一个个填平。

4.1 GFF3标准对seqid的要求

翻一下GFF3的官方规范(Sequence Ontology Project),对seqid其实只说了两句话:

  • 必须与FASTA文件夹中的序列标识符完全一致。
  • 不能包含空白字符(空格、制表符、换行符)。

但实际项目里,你遇到的基因组文件可能来自不同来源。有的用chr1,有的用1,有的带scaffold_前缀……每个名称都有它的脾气。

来源 常见seqid示例 问题指数
Ensembl 1、MT、X 低(有惯例)
UCSC chr1、chrX、chrM 中(chr前缀需确认)
NCBI RefSeq NC_000001.11、NT_123456 高(版本号和点号)
自组装基因组 Contig1、scaffold_01、chr1_v2 高(无统一标准)

4.2 大小写问题:别以为“chr1”和“CHR1”是一个东西

我之前接手过一个项目,GFF3文件里写的是chr1,FASTA的序列头却是1。下游工具比如bedtools、gffread直接报错说“找不到序列”。花了半天排查,最后发现只是多了个"chr"前缀。嗯,这种错误最烦人——你根本不会怀疑是名字问题。

更隐蔽的是大小写。Linux默认的文件系统是区分大小写的,但很多数据库(比如MySQL的默认排序规则)不区分。所以如果你用内存数据库加载GFF3时,Chr1和chr1可能被当作同一个东西,但写回文件时又分开了。

我的一个习惯:在项目开始前,先确认一个“字典文件”,把所有seqid列出来,做成一个对照表,存成纯文本。然后用脚本检查GFF3里的seqid是否都在这个表里。这一步虽然笨,但能挡住99%的命名错误。

4.3 特殊字符与转义

GFF3文档明确说了seqid不能包含以下字符:

空格  制表符  换行符  |  回车符  %

但实际数据里,我见过以下奇奇怪怪的字符:

  • 连字符和点号:scaffold_1.v2——点号在GFF3中不是保留字符,但有些解析器会把点号当作URL编码的一部分,容易出bug。
  • 冒号:chr1:1000-2000——这其实是区域表示法,不该出现在seqid里,但我真见过有人这么写。
  • 分号:chr1;version2——分号是GFF3的注释起始符,写在seqid里会导致整行被当作注释。

另外,%字符是不允许的,但有人会用%20代替空格——这也不行。GFF3规范要求seqid必须能用正则表达式[a-zA-Z0-9_:.*^$#@!-]描述,但实际更严格:最好只包含字母、数字、下划线、连字符和点号。

4.4 版本号与后缀

NCBI的RefSeq序列ID通常带版本号,比如NC_000001.11。那个“.11”是版本号,是seqid的一部分。很多人在做对照时只写NC_000001,然后匹配不上。

还有更坑的:同一个基因组的不同版本,比如GRCh37和GRCh38,它们的seqid可能一模一样(比如都叫1),但序列内容不同。如果你不把版本信息嵌入seqid或附加元数据,后续分析完全不知道用的是哪个版本。

警告:千万不要假设“没有版本号的seqid”就是最新版。你最好在GFF3文件的##gff-version 3之后的注释块里,加上一行##genome-build GRCh38.p13,明确标注构建版本。

4.5 多序列ID与别名

有些基因组有多个名称,比如线粒体可能叫chrM、M、MT、mitochondria。如果你手里的GFF3文件用了chrM,但FASTA用的是M,那就要做别名映射。

我曾经在一个植物基因组项目里,遇到三种不同的命名系统:

GFF3里用: scaffold_1   scaffold_2   ...
FASTA用: SCAF00001    SCAF00002    ...
第三方注释用: chr1A   chr1B        ...

我当时写了一个Python脚本,先读.fai索引文件获取所有序列名,再读GFF3文件,检查每个seqid是否存在于索引中。不存在就报错并输出可能的候选(比如去掉前缀、加上后缀等)。那套脚本后来成了我们团队的质检标准。

4.6 工具对seqid的处理差异

不同的工具对seqid的处理方式不一样,这是最容易踩坑的地方。我整理了一个对比表:

工具 对seqid大小写是否敏感 是否自动修剪 是否允许点号
bedtools 敏感 否 是
gffread 敏感 是(会去掉chr前缀) 是
htseq-count 敏感 否 是
R中的rtracklayer 敏感 否 是(但会强制转为字符型)
GffUtils (Python) 敏感 否 是
IIT (二代测序) 不敏感(系统内部转换) 是 可能报错

看到没?gffread会自作主张把chr1变成1——如果你的分析链路混合使用了多个工具,这种“自动修剪”就可能让上下游衔接不上。我个人建议,在管道的第一个步骤就统一使用一个“标准化函数”,将seqid转为一致格式,后面所有工具都基于这个标准格式运行。

4.7 避坑指南:我的一些血泪总结

  • 永远不要靠人眼看:检查seqid一致性,用脚本。哪怕只有几兆大小的小文件,也要跑一遍。
  • 正则表达式检查:先用一个简单脚本,提取所有seqid,去重,然后跟FASTA索引对比。
  • 注意隐藏字符:Windows下编辑的GFF3文件可能在seqid末尾带\r。用cat -A看一眼,或者用sed 's/\r//'清理。
  • 别名映射表:当基因组文件来自不同来源时,建一个map.tsv文件,每一行表示“别名 -> 标准名”。这比硬编码更可靠。

最后,送你一个我常用的检查命令:

# 提取GFF3所有seqid并排序去重
cut -f1 sample.gff3 | sort -u > seqid_in_gff.txt

# 提取FASTA索引中的序列名
cut -f1 genome.fasta.fai | sort -u > seqid_in_fasta.txt

# 对比差异
diff seqid_in_gff.txt seqid_in_fasta.txt

如果有输出,那就是seqid不匹配。赶紧去修吧。

核心原则:seqid是GFF3文件里最容易被忽视但后果最严重的字段。花费5分钟做一致性检查,能省下5小时的debug时间。我每次开始新项目,第一件事就是跑这个diff。
seqid 命名陷阱与对策 大小写不一致(chr1 vs CHR1) 特殊字符(空格、冒号、%) 版本号/后缀不匹配 隐藏回车字符(\r) 别名映射错误或缺失 统一大小写(全部小写) 只允许字母数字下划线连字符 版本号保留且与FASTA一致 用 sed 's/\r//' 清理 建立别名映射表(map.tsv) 对策:在管线第一步执行seqid标准化,然后基于标准名运行所有下游工具
小技巧:如果你在用Python的gffutils,可以在创建数据库时指定disable_infer_transcripts=True,这样它会完全按照你给的seqid创建表,不会做任何自动修正。推荐这么做,方便出问题时追查。