VCF文件组成:三部分结构,一个都不能少

拿到一个VCF文件,第一件事是什么?别急着看数据。我每次都会先扫一眼文件头,确认它是不是“完整”的。

一个标准的VCF文件,说白了就是三块内容拼起来的:元信息行、头部列定义、以及数据行。这三者缺一不可,顺序也不能乱。我见过不少新手把元信息行放错位置,结果工具直接报错退出了。

元信息行:文件的“身份证”

元信息行,也叫注释行。每行以 ## 开头。它们描述了文件的格式版本、数据来源、过滤条件、样本信息等等。你想想看,如果没有这些信息,其他人拿到文件,根本不知道数据是怎么生成的。

常见的元信息行长这样:

##fileformat=VCFv4.2
##fileDate=20250315
##source=myVarScan_v1.0
##reference=GRCh38
##INFO=<ID=DP,Number=1,Type=Integer,Description="Total Depth">
##FILTER=<ID=PASS,Description="All filters passed">
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=AD,Number=R,Type=Integer,Description="Allelic depths">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Read Depth">
##ALT=<ID=DEL:ME:ALU,Description="Deletion of ALU element">

为什么这个重要?因为很多下游工具在解析VCF时,会先读元信息行。比如 ##INFO 定义了后面数据行里 INFO 字段的键值对含义。如果定义和实际数据对不上,轻则解析失败,重则基因型调用错误。

我个人的习惯: 拿到VCF文件,先grep一下 ##fileformat 和 ##reference。确认版本号和参考基因组是否和项目一致。很多匹配问题,根源就在这里。

头部列定义:数据的“表头”

元信息行之后,紧跟着一行以 # 开头(注意只有一个 #)的头部列定义。这行定义了后面数据行每一列的含义。

#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT sample1 sample2

固定列有8个:CHROM、POS、ID、REF、ALT、QUAL、FILTER、INFO。然后是可选的 FORMAT 列,再后面是样本列。我刚开始做生信时,总记不住 FORMAT 和 INFO 的区别——后来想通了,INFO 是关于这个位点的,FORMAT 是描述样本数据的格式。

这里有一个容易踩的坑:样本列的顺序。不同VCF文件可能有不同的样本顺序。如果你写脚本按列号硬编码,遇到样本顺序不同的文件就会出错。我建议用样本名来索引,而不是列号。比如用Python的 samples = header['sample1'] 而不是 sample_col = 9。

数据行:真正的变异信息

头部列定义之后,就是数据行。每行代表一个位点上的变异信息。大家看个例子:

chr1 123456 . A G 100 PASS DP=50;AF=0.5 GT:AD:DP 0/1:25,25:50
chr1 123789 rs12345 T C 99.5 q10 DP=30;AF=0.3 GT:AD:DP 1/1:0,30:30

数据行有9个固定字段,后面跟着样本数据。每个字段用制表符分隔。我一开始写解析脚本时,以为用空格也能凑合——结果被教训了。VCF的字段分隔符必须是制表符,这是硬性规定,没得商量。

重点说说 INFO 和 FORMAT 字段。很多人搞混:

  • INFO:位点级别的信息,用分号分隔的键值对。比如 DP=50 表示这个位点的总深度是50。
  • FORMAT:样本级别的格式描述,用冒号分隔。比如 GT:AD:DP,表示样本数据第一个字段是基因型,第二个是等位基因深度,第三个是样本深度。

我举个例子:

##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=AD,Number=R,Type=Integer,Description="Allelic depths">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Read Depth">
#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT sample1
chr1 123456 . A G 100 PASS DP=50 GT:AD:DP 0/1:25,25:50

你看,FORMAT 定义了 GT:AD:DP,所以样本数据 0/1:25,25:50 就对应:基因型是杂合,参考等位基因深度25,变异等位基因深度25,样本总深度50。这是一个很典型的配对结构。

三部分结构的校验要点

我在项目中遇到过不少文件,元信息行和头部列定义都有,但数据行格式不对。比如 FORMAT 定义了3个字段,但样本数据只给了2个值——这类错误很隐蔽,常规解析器不一定报错。所以我做校验时,会重点检查以下几点:

我曾经踩过的坑: 有一次,一个VCF文件里 ##FORMAT 定义了 GT:AD:DP,但数据行样本数据只写了 0/1:25,漏了 DP。结果下游分析把 AD 当成了 AD:DP 拼接的字符串,后续计算全部混乱。

建立一个检查清单:

检查项目 预期值 常见问题
##fileformat VCFv4.2 或更高 旧版本字段解析可能不一致
固定列数 至少8列 缺少 FORMAT 或样本列时,列数可能少于9
字段分隔符 制表符 误用空格或逗号
FORMAT 字段数 等于样本数据冒号分隔数 字段数不匹配,或顺序与定义不一致
INFO 键值对 与 ##INFO 定义一致 未定义的键、类型不匹配
样本列数与 FORMAT 后的列数 一个样本对应一列 列数不等于样本数+9

说白了,VCF文件的三部分结构,就像房子的地基、框架和墙体——少一层都立不住。校验时别只看数据行,元信息行和头部列定义才是决定文件可用的关键。

一句话总结: 元信息行做声明,头部列做锚定,数据行做承载。这三块对上了,文件才能拿来用。

VCF文件三部分结构图 VCF文件三部分组成结构 元信息行 (##) 头部列定义 (#) 数据行 (无注释标记) ##fileformat, ##INFO, ##FORMAT #CHROM POS ID REF ALT ... chr1 123456 . A G 100 ... ⚠ 顺序不可颠倒 元信息行 → 头部列定义 → 数据行 每部分内容必须连续,中间不能有空行或注释

记住,校验VCF文件,不是简单地检查文件后缀名。我建议写一个快速脚本,逐行检查元信息行数量、头部列定义格式、数据行字段数。只要这三块结构没有问题,文件就成功了一半。