VCF格式简介

VCF,全称Variant Call Format,是生物信息学里最常用的变异数据标准格式。说白了,它就是一张存变异位点信息的表格——你想知道一个样本在chr1:123456这个位置是A还是G?VCF里写得明明白白。

我最早接触VCF是在做肿瘤体细胞突变分析的时候,那时候刚入行,手头拿到一个几百MB的VCF文件,打开一看全是符号和缩写,完全看不懂。嗯,后来花了几天时间把标准啃下来,才发现这东西设计得其实挺优雅。

VCF的诞生与版本演变

VCF最早由1000 Genomes Project团队提出,目的是统一不同测序平台产生的变异结果。说白了一开始大家各写各的格式,有的用GFF,有的用BED,有的自己造个文本——那时候想要合并两个项目的变异数据,够你哭半天的。

VCF 1.0在2010年发布,随后快速迭代到4.x系列。目前主流是VCF 4.2,但VCF 4.3也已经出来好几年了,主要增加了对结构变异和AD字段的标准化。

个人经验:我强烈建议你在项目中始终用VCF 4.2或更新的标准。2015年的时候我接手过一个遗留项目,里面还是VCF 3.3,字段命名乱七八糟,一个INFO里的DP有时候是总深度,有时候是变异深度,坑了我整整一周。

VCF文件结构:三层骨架

一个标准VCF文件由三部分组成:元数据行、头部行、数据行。这三层缺一不可,且顺序固定。

  • 元数据行(##开头的行)——描述文件版本、过滤条件、INFO定义、FORMAT定义等。
  • 头部行(#开头的行,以#CHROM打头)——定义列名,包括固定九列和样本列。
  • 数据行(没有#的行)——每行记录一个变异位点在当前样本集上的情况。

下面这张图能帮你直观理解VCF文件的整体结构,记住它你读VCF的时候心里就有谱了。

VCF文件三层结构 元数据层(##开头) ##fileformat=VCFv4.2 ##INFO=<ID=DP,Number=1,Type=Integer,Description="Total Depth"> ##FILTER=<ID=PASS,Description="All filters passed"> ...(定义INFO、FORMAT、FILTER、ALT等) 头部行(#CHROM开头) #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT sample1 sample2 数据行(逐行记录) chr1 10001 . A G 50 PASS DP=100 GT:GQ:DP 0/1:99:50 1/1:99:50 chr1 10002 rs123 C T 30 q10 DP=80 GT:GQ:DP 0/0:99:40 0/1:80:40 ... (每行8个固定字段 + N个样本字段) 元数据可以有多行,头部只有一行,数据行无限

固定九列:读VCF的必修课

数据行最前面八个字段(加上FORMAT算半个)是固定的,我列个表你一看就明白:

列名 含义 必须吗?
CHROM 染色体或contig名称 是
POS 1-based位置 是
ID dbSNP ID(rs号)或其它标识,没有用"." 是
REF 参考基因组等位基因 是
ALT 替代等位基因,逗号分隔多个 是
QUAL 质量分数(Phred-scaled) 是
FILTER 过滤状态,PASS或具体滤过原因 是
INFO 位点级信息(键=值;键=值) 是
FORMAT 后面样本列的数据格式定义(GT:GQ:DP...) 有样本列时必须

注意:这里 第九列其实是FORMAT,后面跟着的是一个个样本。我见过不少新手把第一个样本列当成第九列,结果数据处理出来全是乱的。你记住:#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT 之后才是样本1、样本2……

INFO与FORMAT:你需要理解的双层存储

INFO存储的是位点级别的信息——整个位点在所有样本上的总结,比如总深度DP=100、所有样本的等位基因计数AC=5。

FORMAT加上样本列存储的是每个样本在这个位点上的具体信息——比如这个样本的基因型GT:0/1、该样本在该位点的深度DP:50。

这种设计是为了压缩存储。举个例子:家系研究中,某个位点所有样本的总深度是100(INFO里写一次),但每个孩子自己的深度可能分别是50、30、20(写在FORMAT对应的样本列里)。

避坑指南:我曾在做批次合并时犯过一个低级错误。两个VCF文件里同一个位点的INFO/DP定义不同,一个写Number=1,另一个写Number=.(可变个数)。结果bcftools merge直接报了类型不匹配错误。从那以后我每次合并前都会先检查元数据中的Number定义是否一致,尤其是AD、PL这类可变长度字段。

基因型字段:GT是核心中的核心

GT字段永远排在FORMAT的第一个位置(格式规范强制要求)。它用0表示参考等位基因,1表示第一个ALT,2表示第二个ALT,依此类推。|分隔代表定相(phased),/分隔代表不定相。

# 示例:一个双等位位点,参考为A,ALT为G
0/0  纯合参考
0/1  杂合(非定相)
1|1  纯合替代(定相)
0/2  杂合,第二个ALT(如果存在多等位基因)

你想想看,一个位点如果ALT不止一个,GT的编号逻辑会变复杂。我见过有人在处理多等位基因时直接用0/1代表第一个替代,结果第二个替代其实存在,但被忽略了——这个坑在批次关联分析中尤其致命,因为不同批次可能调换了ALT顺序。

注意:不同工具生成的VCF可能把ALT顺序排得不一样。比如bcftools normalize会把多个ALT按字母序重排,而GATK则是按检测到的顺序输出。如果你要做批次合并或关联分析,务必先做一次bcftools norm -m -any统一左对齐和交替等位基因排序。

为什么要学VCF结构?批次关联的基础

做批次关联分析,本质上就是把来自不同实验批次、不同测序平台、不同分析流程的VCF文件合并成一张大表。这时候你如果对VCF结构没有深刻理解,等着你的就是各种不兼容的报错:

  • 元数据里ILUMINA vs. ILLUMINA拼写不一致,bcftools merge直接跳过
  • FILTER字段有的写PASS,有的写.,合并后过滤条件复杂化
  • FORMAT定义不同(比如一个文件用AD,另一个文件用DP4),合并后样本字段对不齐

我记得有一次做千人基因组与本地测序数据的合并,发现某批次的VCF里FORMAT=GT:DP,另一批次是FORMAT=GT:GQ:DP,合并后第一批次的GQ全变成了缺失值——后来我们额外在合并脚本里加了--fill-missing参数才补上。这就是为什么我反复强调,开始任何批次分析之前,先跑一遍vcf-validator或bcftools stats看看各文件的元数据一致性。

小技巧:快速查看VCF头部元数据用bcftools head -n 100 your.vcf | head -100,或者直接zless your.vcf.gz | head -100。检查关键字段定义是否一致,推荐写一个小脚本对比##INFO和##FORMAT行。等后续章节我会分享一个我常用的元数据差异检测脚本。

本章小结

VCF格式虽然琐碎,但它的设计非常符合变异数据的特点:位点级和样本级信息分开存储,结构灵活又不失规范。搞定三层结构、固定九列、INFO与FORMAT的差异,以及GT的编码逻辑,你读VCF就不会再发怵了。