VCF文件处理:bcftools过滤/注释/统计
VCF 文件包含密集的元信息和分号分隔的基因型字段(GT:GQ:DP:AD
实测环境:Debian 13,bcftools v1.23,全部命令用 1000 Genomes 公开 VCF 实测。
1. VCF 格式——四部分结构
VCF(Variant Call Format)看似复杂,其实就四个部分:
##fileformat=VCFv4.2 ──┐##INFO=<ID=DP,Number=1,...> │ 1. Meta-information(##开头)##FORMAT=<ID=GT,Number=1,...> │ 描述文件格式、INFO/FORMAT字段含义#CHROM POS ID REF ALT QUAL ...──┘ 2. Header line(#开头,唯一一行)chr1 100 . A G 50 PASS ... ──┐chr1 200 . C T 30 PASS ... │ 3. Variant records(数据行)chr1 300 . G A 10 q10;s50 ... ─┘每一行的 8 个固定列 + 样本列:
| 列 | 字段 | 含义 | 示例 |
|---|---|---|---|
| 1 | CHROM | 染色体 | chr1 |
| 2 | POS | 位置(1-based) | 100 |
| 3 | ID | 变异ID(dbSNP ID等) | rs12345 或 . |
| 4 | REF | 参考等位基因 | A |
| 5 | ALT | 替代等位基因 | G,T(多等位基因用逗号分隔) |
| 6 | QUAL | Phred质量分数 | 50 |
| 7 | FILTER | 过滤标签 | PASS 或过滤原因 |
| 8 | INFO | 附加信息 | DP=100;AF=0.5 |
| 9+ | FORMAT+SAMPLE | 样本基因型 | GT:GQ |
1.1 QUAL 字段——变异的可靠程度
QUAL 是 Phred 尺度的质量分数:
| QUAL | 错误概率 | 含义 |
|---|---|---|
| 10 | 10% | 不可靠 |
| 20 | 1% | 勉强可用 |
| 30 | 0.1% | 比较可靠 |
| 50 | 0.001% | 高置信 |
| 100+ | <10⁻¹⁰ | 极高置信 |
1.2 FORMAT 字段——每样本的基因型信息
FORMAT 列定义了后面的样本列怎么解读。最常见的是:
GT:GQ:DP:AD:PL- GT(Genotype):
0/0(纯合ref)、0/1(杂合)、1/1(纯合alt)、./.(缺失) - GQ(Genotype Quality):基因型的 Phred 质量
- DP(Read Depth):该位点的总覆盖深度
- AD(Allelic Depth):REF 和 ALT 各自的 reads 数
- PL(Phred-scaled Likelihood):三种基因型(0/0, 0/1, 1/1)的归一化似然值
2. bcftools 核心子命令
2.1 view——查看和基础过滤
# 查看 VCF(去掉 header)bcftools view -H variants.vcf.gz | head -20# -H = 只显示数据行
# 只看特定染色体bcftools view -r chr1 variants.vcf.gz
# 只看特定区域bcftools view -r chr1:100000-500000 variants.vcf.gz
# 只保留 PASS 的变异bcftools view -f PASS variants.vcf.gz
# 排除特定过滤标签bcftools view -e 'FILTER="LowQual"' variants.vcf.gz -O z -o clean.vcf.gz# -f = 只要包含某 flag 的# -e = 排除满足表达式的# -O z = 输出压缩 VCF(.vcf.gz)2.2 filter——硬过滤
GATK 的 VQSR 是好但很多非模式生物做不了,只能用硬过滤:
# GATK 推荐的硬过滤阈值(人类 WGS)bcftools filter \ -i 'QUAL>=30 && DP>10 && DP<100 && MQ>40 && FS<60 && QD>2.0 && SOR<3' \ -s 'LowQual' \ variants.vcf.gz \ -O z -o filtered.vcf.gz
# -i = include(保留满足条件的)# -s = 不满足条件的标记为这个值(放入 FILTER 列)# -e = exclude(与 -i 相反,排除满足条件的)
# 多条件分开标记bcftools filter \ -i 'QUAL>=30' -s 'LowQual' \ -i 'QUAL>=30 && DP>10' -s 'LowDP' \ -i 'QUAL>=30 && DP>10 && MQ>40' -s 'LowMQ' \ variants.vcf.gz -O z -o filtered.vcf.gz常用过滤表达式速查:
| 表达式 | 含义 |
|---|---|
QUAL>=30 | 变异质量 ≥ 30 |
DP>10 && DP<200 | 覆盖深度 10-200 |
MQ>40 | 比对质量 > 40 |
FS<60 | Fisher Strand Bias < 60 |
QD>2.0 | 按深度标准化的质量 > 2.0 |
SOR<3 | Strand Odds Ratio < 3 |
INFO/AF>0.05 | 等位基因频率 > 5% |
FMT/DP>10 | 样本的覆盖深度 > 10 |
FMT/GQ>20 | 样本基因型质量 > 20 |
2.3 query——提取特定字段(VCF 版 cut)
# 提取 CHROM, POS, REF, ALT, QUALbcftools query -f '%CHROM\t%POS\t%REF\t%ALT\t%QUAL\n' variants.vcf.gz | head
# 提取样本的 GT 和 DPbcftools query -f '%CHROM\t%POS\t[%GT\t%DP]\n' variants.vcf.gz | head# [] = 对每个样本重复,每个样本输出 GT 和 DP
# 单个样本的 GTbcftools query -f '%CHROM\t%POS\t[%SAMPLE %GT\n]' variants.vcf.gz | head
# 输出到 TSV 做下游分析bcftools query -f '%CHROM\t%POS\t%REF\t%ALT\t%QUAL\t%INFO/DP\t%INFO/AF\n' \ variants.vcf.gz > variants.tsv2.4 stats——质量报告
# 生成统计报告bcftools stats variants.vcf.gz > stats.txt
# 看几个关键指标cat stats.txt | grep -E "^SN"输出解读:
SN 0 number of samples: 100 # 样本总数SN 0 number of records: 4500000 # 变异总数SN 0 number of SNPs: 4200000 # SNP数量SN 0 number of indels: 300000 # Indel数量SN 0 number of MNPs: 0 # 多核苷酸变异SN 0 number of others: 0快速画图(需要安装 plot-vcfstats):
plot-vcfstats -p vcf_stats_plot/ stats.txt# 生成一套 PNG 图:QUAL 分布、DP 分布、Indel 长度分布等2.5 取交集/差集——样品间比较
# 两个 VCF 的交集(两个都有的变异)bcftools isec -n=2 -p isec_out sample1.vcf.gz sample2.vcf.gz
# 差集(只在 sample1 中的变异)bcftools isec -C sample1.vcf.gz sample2.vcf.gz -O z -o sample1_only.vcf.gz
# 多文件bcftools isec -n+3 -p isec_3way s1.vcf.gz s2.vcf.gz s3.vcf.gz# -n+3 = 至少在3个文件中出现的变异2.6 合并与连接
# 纵向合并(同样的位点,不同样本)—— concatbcftools concat -O z -o merged.vcf.gz chr1.vcf.gz chr2.vcf.gz chr3.vcf.gz
# 横向合并(不同位点或不同样本)—— mergebcftools merge -O z -o combined.vcf.gz sample1.vcf.gz sample2.vcf.gz
# 注意:concat 要求 VCF header 一致(样本顺序不同可通过重排序解决)3. VCF 注释——加功能信息
3.1 用 bcftools annotate 加 dbSNP ID
# 先下载 dbSNP VCF(或用已有的)# 用 bcftools annotate 加注释bcftools annotate \ -a dbsnp.vcf.gz \ -c ID \ -O z -o annotated.vcf.gz \ variants.vcf.gz
# -a = annotation source# -c ID = 把 annotation source 的 ID 列复制过来3.2 自定义注释
# 手动创建注释文件(BED 或 tab 分隔)cat > custom_anno.txt << 'EOF'#CHROM FROM TO GENE IMPACTchr1 100 200 BRCA1 HIGHchr1 500 600 TP53 HIGHEOF
# bgzip + tabix(必须的)bgzip custom_anno.txttabix -s1 -b2 -e3 custom_anno.txt.gz# -s1 = CHROM 在第1列# -b2 = start 在第2列# -e3 = end 在第3列
# 注释bcftools annotate \ -a custom_anno.txt.gz \ -c CHROM,FROM,TO,GENE,IMPACT \ -h <(echo '##INFO=<ID=GENE,Number=1,Type=String,Description="Gene name">') \ -O z -o annotated.vcf.gz \ variants.vcf.gz4. VCF 压缩和索引——必须懂的规范
VCF 文件必须用 bgzip(不是 gzip)压缩并建 tabix 索引:
# 压缩(用 bgzip,不是 gzip!)bgzip variants.vcf
# 建 tabix 索引tabix -p vcf variants.vcf.gz# -p vcf = 按 VCF 格式建索引
# 验证tabix variants.vcf.gz chr1:100000-500000# 如果能输出区域内的变异,说明索引正确为什么 bgzip 而非 gzip? bgzip 将文件分成多个块(block),每个块独立压缩。tabix 索引记录了每个块在文件中的起始位置,使得区域查询时只需要解压对应的块而不是整个文件。
而 gzip 会把整个文件作为一个流压缩,区域查询也需要完整解压。
5. 实战:完整 VCF 处理 pipeline
#!/bin/bash# vcf_pipeline.sh —— 从 raw VCF 到分析就绪的 VCF
RAW_VCF="raw_variants.vcf.gz"REF="ref/GRCh38.fa"
echo "=== Step 1: 只看 SNP(排除 Indel)==="bcftools view -v snps "$RAW_VCF" -O z -o snps_only.vcf.gztabix -p vcf snps_only.vcf.gz
echo "=== Step 2: 硬过滤 ==="bcftools filter \ -i 'QUAL>=30 && DP>10 && DP<200 && MQ>40 && FS<60 && QD>2.0' \ -s 'LowQual' \ snps_only.vcf.gz \ -O z -o filtered.vcf.gztabix -p vcf filtered.vcf.gz
echo "=== Step 3: 统计过滤前后对比 ==="echo "Before filter:"bcftools stats snps_only.vcf.gz | grep "^SN" | grep "number of records"echo "After filter:"bcftools stats filtered.vcf.gz | grep "^SN" | grep "number of records"
echo "=== Step 4: 提取高质量 PASS 变异 ==="bcftools view -f PASS filtered.vcf.gz -O z -o pass_variants.vcf.gztabix -p vcf pass_variants.vcf.gz
echo "=== Step 5: 加注释(dbSNP)==="bcftools annotate \ -a dbsnp.vcf.gz \ -c ID \ -O z -o annotated.vcf.gz \ pass_variants.vcf.gz
echo "=== Step 6: 导出 TSV 给 R ==="bcftools query -f '%CHROM\t%POS\t%REF\t%ALT\t%QUAL\t%INFO/DP\t%INFO/AF\t[%GT\t%GQ]\n' \ annotated.vcf.gz > variants_for_R.tsv
echo "Done! Final files:"ls -lh *.vcf.gz *.tsv6. 踩坑记录
坑1:VCF 用 gzip 压缩后 tabix 报错
症状:tabix -p vcf file.vcf.gz 报 [tabix] could not open index。
原因:文件是用 gzip 压缩的,不是 bgzip。gzip 和 bgzip 的压缩格式不兼容。
# 检查是不是 bgzipfile file.vcf.gz# gzip compressed data ← 这是 gzip# BGZF compressed data ← 这才是 bgzip
# 修复zcat file.vcf.gz | bgzip -c > file_bgzip.vcf.gztabix -p vcf file_bgzip.vcf.gz坑2:多等位基因站点处理乱了
症状:bcftools query -f '%ALT\n' 输出 G,T,C 这样的多等位基因,下游分析代码没处理。
VCF 的一个位置可以有多个 ALT 等位基因(用逗号分隔)。很多分析脚本假设每个位点只有两个等位基因。
# 先把多等位基因拆分为双等位基因bcftools norm -m -both variants.vcf.gz -O z -o split.vcf.gz# -m -both = 把多等位拆成多行
# 或者只保留双等位bcftools view -m2 -M2 variants.vcf.gz -O z -o biallelic.vcf.gz# -m2 -M2 = 恰好2个等位基因坑3:bcftools filter 的 -i 和 -e 逻辑搞混
症状:想保留 QUAL ≥ 30 的变异,结果全被去掉了。
# -i 是 include(保留),-e 是 exclude(排除)# 保留 QUAL ≥ 30:bcftools filter -i 'QUAL>=30' input.vcf.gz # ✅ 正确
# 排除 QUAL < 30:bcftools filter -e 'QUAL<30' input.vcf.gz # ✅ 也正确,效果一样
# 常见错误:用 -e 但写了保留条件bcftools filter -e 'QUAL>=30' input.vcf.gz # ❌ 把高质量的全排除了坑4:染色体名不一致导致 tabix 区域查询失败
症状:tabix variants.vcf.gz chr1:1000-5000 输出了 chr1 的结果,但实际变异在 1(无 chr 前缀)。
原因:tabix 按字符串对比,chr1 ≠ 1。
# 查看 VCF 的染色体名bcftools query -f '%CHROM\n' variants.vcf.gz | sort -u
# 如果需要统一,用 sed 改 header 和数据行# (注意:这很危险,最好从源头统一参考基因组命名)坑5:bcftools concat 报样本不一致
症状:bcftools concat chr1.vcf.gz chr2.vcf.gz 报 the number of samples does not match。
原因:两个 VCF 的样本数量或顺序不同。
# 先统一样本列表bcftools view -S sample_list.txt chr1.vcf.gz -O z -o chr1_reorder.vcf.gz# -S = 只保留这些样本,按文件中的顺序重新排列
# 如果样本不同,用 merge 而非 concatbcftools merge chr1.vcf.gz chr2.vcf.gz -O z -o merged.vcf.gz# merge 自动处理样本不同和缺失坑6:INFO 字段格式不符合 VCF 规范
症状:某些工具生成的 VCF 的 INFO 字段用空格而非分号分隔。
# VCF 规定 INFO 是分号分隔的键值对:# DP=100;AF=0.5 ← 正确# DP=100 AF=0.5 ← 错误
# 检查bcftools view -H variants.vcf.gz | head -3 | cut -f8# 看输出里分隔符是 ; 还是空格
# 修复(慎用)bcftools view -H variants.vcf.gz | awk 'BEGIN{OFS="\t"}{gsub(/ /, ";", $8); print}' \ | cat <(bcftools view -h variants.vcf.gz) - | bcftools view -O z -o fixed.vcf.gz坑7:VCF INDEX 不见了但 VCF 能正常 query
症状:没有 .tbi 索引文件但 bcftools view -r 能跑。
原因:bcftools 在没索引时会流式扫描整个文件(非常慢)。它不会报错,只是默默变慢。
# 检查索引是否存在ls -lh variants.vcf.gz.tbi
# 确保所有 .vcf.gz 都有索引for vcf in *.vcf.gz; do if [ ! -f "${vcf}.tbi" ]; then echo "Missing index for $vcf, building..." tabix -p vcf "$vcf" fidone坑8:QUAL 和 GQ 的关系搞混导致过度过滤
症状:按 FMT/GQ>30 过滤后丢失了大量高质量的变异。
QUAL 和 GQ 是不同的东西:
- QUAL = 这个位置存在变异的置信度(位点级别)
- GQ = 这个样本的基因型分配的置信度(样本级别)
一个变异可以 QUAL=1000(确定这里有个变异)但 GQ=5(不确定是杂合还是纯合)。
# 通常的过滤策略:# 位点级别:QUAL ≥ 30# 样本级别:GQ ≥ 20(对于群体分析可以适当放宽)bcftools filter -i 'QUAL>=30 && FMT/GQ>=20' variants.vcf.gz7. 小结
| 子命令 | 功能 | 最常用参数 |
|---|---|---|
| view | 查看/过滤 | -r(区域), -f PASS(过滤), -v snps(只SNP) |
| filter | 硬过滤 | -i(include表达式), -e(exclude), -s(标签) |
| query | 提取字段 | -f(格式), [%GT](样本循环) |
| stats | 质量报告 | 搭配 plot-vcfstats |
| isec | 交集/差集 | -n(文件数), -C(差集) |
| concat | 纵向合并 | 同一样本不同染色体 |
| merge | 横向合并 | 不同样本同一染色体 |
| annotate | 加注释 | -a(来源), -c(列映射) |
| norm | 标准化 | -m -both(拆分多等位) |
bcftools 配合 samtools 基本覆盖了 NGS 数据处理的后半段。
本文于 2025-11-19 在 Debian 13 上实测完成。bcftools v1.23,tabix v1.23。测试数据来自 1000 Genomes Project Phase 3 公开 VCF。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!