VCF 格式基础
大家好,我是老刘,做了好多年生信流程开发和数据库整合的工作。今天开始咱们聊 VCF 格式。说实话,VCF 是基因组分析里绕不过的坎儿,不管是叫“变异检出”还是“注释降维”,最终你都得跟它打交道。我最早接触 VCF 时,觉得它不就是个文本表格嘛,后来才发现里面暗藏玄机——尤其是那些元信息行和 FORMAT 字段,稍不留神就把样本数据搞串了。
1.1 VCF 文件长什么样?
VCF(Variant Call Format)是纯文本,每一行要么以 ## 开头(元信息),要么以 # 开头(列标题),要么是数据行。咱们先看一段最简单的 VCF 文件片段,感受一下整体结构:
##fileformat=VCFv4.2
##fileDate=2025-01-15
##source=GATK4.2
##contig=<ID=chr1,length=248956422>
##INFO=<ID=DP,Number=1,Type=Integer,Description="Total Depth">
##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 SAMPLE2
chr1 12345 . A G 100 PASS DP=50 GT:AD:DP 0/1:25,25:50 1/1:0,30:30
你看,前几行全是 ## 打头的元信息,记录了这个文件的版本、来源、参考基因组、字段定义等等。单独一行 #CHROM POS ... 是列标题。最后两行是数据行,每行代表一个变异位点及其样本的基因型信息。我个人习惯先看文件开头几行元信息,确认版本和 FORMAT 定义后再处理后面数据——不然你根本不知道 GT、AD、DP 这些缩写对应啥意思。
1.2 元信息行(## 开头的行)
元信息行是 VCF 的“配置文件”。它们用 key=value 的格式,常见的几类我列在下面:
- fileformat:VCF 版本号,目前主流是 VCFv4.2 或 v4.1
- fileDate:文件创建日期
- source:生成该文件的软件或流程
- reference:参考基因组路径或版本
- contig:参考序列的 ID 和长度,可以有多条
- INFO:INFO 列中每个字段的定义,包括
ID、Number、Type、Description - FORMAT:样本列中用到的每个格式字段的定义
- FILTER:过滤标签(如
PASS、LowQual)的定义 - ALT:替代等位基因的全局定义
我的一点小经验:解析 VCF 时,先把所有 ##INFO 和 ##FORMAT 行解析成字典,后面读数据行时直接查表,效率高多了。我曾经在一个 100 万行的 VCF 上每次重复解析元信息,结果跑了 3 个小时才反应过来——其实 parse 一遍就够了。
1.3 列定义(#CHROM 那行)
#CHROM 那行固定有 8 个必选列,之后是 FORMAT 列(可选),再往后就是样本列。我整理了一张表,咱们对照着看:
| 列名 | 含义 | 例子与注意事项 |
|---|---|---|
| CHROM | 染色体 / 参考序列名称 | chr1、chrX,注意与参考基因组一致 |
| POS | 变异在染色体上的起始位置(1-based) | 对于插入/缺失,POS 通常是第一个碱基位置 |
| ID | 变异标识符,可用 dbSNP rs号 或 . 表示未知 |
多个 ID 用分号分隔 |
| REF | 参考等位基因碱基序列 | 必须是参考基因组上的实际序列 |
| ALT | 替代等位基因序列(多个用逗号分隔) | 对于缺失可用 . 或 <DEL> 结构型变异 |
| QUAL | 变异质量分数,Phred 尺度的整数或浮点数 | 值越高越可靠,. 表示未知 |
| FILTER | 过滤状态,PASS 表示通过,. 表示未过滤 |
可以是多个标签,用分号分隔 |
| INFO | 以 key=value 形式存放的位点级信息(多个用分号) |
例如 DP=50;AF=0.23 |
| FORMAT | 样本列中各字段的排列顺序 | 如 GT:AD:DP,字段间用冒号分隔 |
| SAMPLE1... | 每个样本的变异数据,按 FORMAT 定义的顺序用冒号分隔 | 例如 0/1:25,25:50 |
关键点:FORMAT 列像一把“钥匙”,样本列的值必须严格按这个钥匙排列。我经常用 split(':')) 来拆解,再通过 FORMAT 列的字段名索引。
1.4 样本列与 FORMAT 字段(GT / AD / DP 详解)
样本列是 VCF 最灵活也最让人头疼的部分。我用项目里最常碰到的三个字段展开讲讲:
- GT(Genotype):用
/或|分隔的两个等位基因索引。0 表示 REF,1 表示第一个 ALT,2 表示第二个 ALT……比如0/1表示杂合,1/1表示纯合 ALT。|常用于表示 phased 基因型。 - AD(Allelic Depth):每个等位基因的支持 reads 数量,顺序与 REF + ALT 对齐。例如
25,25表示 REF 有 25 条 reads,ALT 有 25 条。注意多等位基因时列数会增加。 - DP(Read Depth):该样本在这个位点的总覆盖深度(所有等位基因之和)。
避坑指南:我曾经处理过一个肿瘤 WES 数据,发现 DP 和 AD 数据不一致——就是 AD 各等位基因之和与 DP 对不上。后来发现是 GATK 的某个旧版本 bug,先把不同区域的 reads 合并时漏掉了部分。之后我就养成了一个习惯:凡是从 VCF 提取 DP 信息时,一定用 sum(AD) 再跟 DP 做一次交叉验证。
再看一个稍微复杂点的 FORMAT 定义例子:
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=AD,Number=R,Type=Integer,Description="Allelic depths for the ref and alt alleles">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Read depth">
##FORMAT=<ID=GQ,Number=1,Type=Integer,Description="Genotype Quality">
##FORMAT=<ID=PL,Number=G,Type=Integer,Description="Normalized, Phred-scaled likelihoods">
这里 Number=R 表示 AD 的个数等于 REF + ALT 的数量(即可变数值)。Number=G 表示 PL 的数量等于基因型组合数。你想想看,如果某个位点有 ALT= A,G(两个替代),那么 AD 就有 3 个值(REF, A, G),PL 则有 6 个值(3 种等位基因的 6 种可能二倍体基因型)。
1.5 VCF 的骨架结构
把上面这些串起来,VCF 文件其实就是三层结构:元信息(文件头)→ 列标题 → 数据行。每个数据行又包含位点信息和样本信息。我用一张 SVG 图帮你把逻辑捋一遍:
嗯,这张图基本把 VCF 的骨架画清楚了。我建议你脑子里始终装着这个三层模型:解析时先搞定元信息,然后按列标题建字段映射,最后逐行读取。至于 GT、AD、DP 这些字段,记住它们的定义和排列规则就行——毕竟后面做注释、统计、过滤,都离不开它们。
小贴士:如果你用 Python 处理 VCF,我最推荐 pysam 库里的 VariantFile 类,它能自动解析元信息和列结构,直接给你返回格式化的 Variant 对象。但为了打好基础,我建议你先手写几次解析函数,理解原理后再上库。