VCF文件格式基础

VCF,全称Variant Call Format,是生物信息学里最常用的变异文件格式。我刚开始做生信那会,整天跟VCF打交道,第一感觉就是——这格式看着吓人,其实拆开来看就几块东西。

你想想看,一个标准的VCF文件,无非就是三大部分:头信息(Header)、主体记录(Records),以及最关键但很多人忽略的——样本字段(Sample Fields)。咱们一个一个说。

头信息(Header)

头信息以 ## 开头,每一行都定义了一个元数据。我习惯先快速扫一眼头信息,就知道这个VCF是谁跑的、用了什么参考基因组、有哪些INFO和FORMAT字段。

常用的头信息类型:

  • ##fileformat=VCFv4.2 —— 版本号,现在主流是4.2或4.3
  • ##fileDate=20231015 —— 文件创建日期,说实话经常不准,不少工具会漏掉
  • ##reference=file:///ref/hg38.fa —— 参考基因组路径,嗯,这里要注意:路径写绝对还是相对容易导致迁移问题
  • ##INFO=<ID=AF,Number=A,Type=Float,Description="Allele Frequency"> —— 定义INFO字段的结构
  • ##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype"> —— 定义样本格式字段
  • ##FILTER=<ID=LowQual,Description="Low quality"> —— 过滤器说明

我个人经验:写完代码批量处理VCF之前,一定要先 grep "^##" 把头信息提取出来看一眼,否则后面解析INFO字段类型对不上就麻烦了。

避坑指南: 头信息里 Number 的值除了数字,还有 A(按等位基因数)、R(按参考+等位基因总数)、G(按基因型数)、.(可变长度)。
我曾经写过一个脚本,默认 Number=1 解析 INFO=AF,结果人家是 Number=A,直接索引越界。从那以后我都是先读头信息动态判断。

主体记录(Records)

头信息下面,从 #CHROM 开始才是真正的记录行。每一行包含9个固定字段,然后跟样本列。我列出这些字段,你大概看一眼:

列号名称说明避坑要点
1CHROM染色体名称可能带"chr"前缀,一定要统一
2POS左对齐的位置(1-based)很多人搞错0-based还是1-based
3IDdbSNP rs号或点号空值用 "." 表示,别用 " " 或 "NA"
4REF参考碱基可以是多个碱基,注意大小写
5ALT替代等位基因,逗号分隔多个ALT时顺序很重要
6QUAL质量分数(Phred scale)缺失用 ".",数值可能很大
7FILTER过滤状态,通过用 "PASS"多个过滤器用分号隔开
8INFO键值对,分号分隔浮点数精度可能丢失
9FORMAT样本列格式定义,冒号分隔顺序必须与样本数据一致
10+样本列每个样本一个列,冒号分隔缺失用 "." 或 "./."

这里我要特别提一下 POS 和 REF。VCF强制使用左对齐标准化。什么意思?如果一个indel出现在重复序列里,不同工具叫出来的位置可能不一样。我建议用 bcftools norm 做一次标准化,不然合并多个VCF时会疯掉。

基因型格式(FORMAT字段)

FORMAT字段直接定义了每个样本的数据结构。常见的有:

  • GT —— 基因型,比如 0/1 表示杂合,1|1 表示纯合。分隔符 / 表示无相位,| 表示有相位
  • AD —— 每个等位基因的深度,比如 10,5 表示REF深度10,ALT深度5
  • DP —— 该位点总覆盖深度
  • GQ —— 基因型质量分数(Phred scale)
  • PL —— 三种基因型的归一化似然值(Phred scale)

举个实际的样本字段例子(假设FORMAT=GT:AD:DP:GQ:PL):

0/1:15,8:23:88:100,0,200

解析出来就是:杂合,REF深度15,ALT深度8,总深度23,GQ=88,PL值表示最可能基因型是0/1。

我的习惯: 用pysam读取VCF时,遇到样本字段最好用 record.samples[0].alleles 获取等位基因索引,别自己手动拆GT字符串,因为处理相位符号和缺失值时容易出错。pysam已经帮你做好了。

常用INFO标签解读

INFO字段是变异级别的注释,常见的就那么几个:

标签类型含义个人经验
AFFloat人群等位基因频率注意有些VCF的AF是逗号分隔的多个值,对应多个ALT
ANInteger有效等位基因总数用于计算AC时要注意
ACInteger每个ALT的等位基因计数如果ALT有3个,AC就有3个值
DPInteger该位点总深度(跨所有样本)与样本级别DP不同,别搞混
MQFloat映射质量(Mapping Quality)低MQ可能表示结构变异假阳性
QDFloat质量除以深度(QualByDepth)我自己常用QD<2.0做硬过滤
FSFloatFisher链平衡检验的P值(Phred scale)FS>60通常表示链偏置严重

你肯定注意到了,INFO字段里的标签名都是大写、无空格。这是VCF规范,写pysam脚本时直接 record.info['AF'] 就能获取。

一图看懂VCF结构

下面这张SVG图是我自己画的结构拆解,建议收藏:

VCF 文件结构图 头信息 (Header) ##fileformat=VCFv4.2 ##INFO=... ##FORMAT=... ##FILTER=... 固定列(9列) CHROM POS ID REF ALT QUAL FILTER INFO FORMAT 样本列(≥1列) Sample1: GT:AD:DP:GQ:PL Sample2: GT:AD:DP:GQ:PL 格式定义 示例记录行 chr1 12345 . A G 45.0 PASS AF=0.02;DP=100 GT:AD 0/1:60,40
重要提醒: 上面图的“示例记录行”里,我故意写的是 chr1 带chr前缀。很多公共数据(比如1000 Genomes)是不带chr的,而Ensembl又带。你用pysam处理时务必先用 record.chrom 检查规范,或者预先统一染色体命名。

说白了,VCF没那么神秘。你只要搞懂头信息->固定列->样本列这个三层结构,再加上几个常见标签的含义,就能轻松用pysam进行后处理了。我推荐你拿一个真实的小型VCF文件,用 less -S 看一下原始内容,再对比上面的SVG图,立马就通透。