vcf-variant-filtering — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited vcf-variant-filtering (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Raw VCF files produced by variant callers (GATK HaplotypeCaller, bcftools mpileup, DeepVariant, etc.) contain a mixture of true variants and artifacts from sequencing errors, alignment issues, and low-coverage regions. Computing summary statistics -- Ts/Tv ratio, variant counts, allele frequency distributions -- on unfiltered data yields unreliable results because false-positive calls disproportionately inflate transversion counts and depress the Ts/Tv ratio. This guide covers how to detect whether a VCF is raw, how to apply appropriate quality filters, when filtering is not appropriate, and how to interpret the resulting statistics correctly.
The QUAL column in a VCF file represents the Phred-scaled probability that the variant site is polymorphic. A QUAL score of 30 means a 1-in-1000 chance the call is wrong; a QUAL of 20 means 1-in-100. Variant callers assign QUAL scores based on read evidence, base qualities, and mapping qualities. Low-QUAL variants (below 20-30) are enriched for sequencing errors and alignment artifacts. Filtering on QUAL is the simplest and most widely used first-pass quality control step.
Common QUAL thresholds and their interpretation:
| QUAL Score | Error Probability | Typical Use |
|---|---|---|
| 10 | 1 in 10 | Very permissive; rarely appropriate for final calls |
| 20 | 1 in 100 | Lenient filtering; useful for somatic calling with supporting evidence |
| 30 | 1 in 1,000 | Standard default for germline variant filtering |
| 50 | 1 in 100,000 | Stringent; used in clinical or high-confidence applications |
| 100+ | Extremely low | Highly supported variants; may over-filter low-coverage regions |
For GATK-based workflows, VQSR or hard filtering on INFO annotations (QD, FS, MQ, etc.) may supplement or replace QUAL filtering. GATK's recommended hard filters for SNPs include QD < 2.0, FS > 60.0, MQ < 40.0, MQRankSum < -12.5, and ReadPosRankSum < -8.0.
The transition-to-transversion (Ts/Tv) ratio is a key quality metric for variant call sets. Transitions (A<->G, C<->T) are chemically favored over transversions (all other substitutions) due to the molecular structure of nucleotide bases. Expected Ts/Tv values serve as benchmarks:
A Ts/Tv ratio significantly below the expected range indicates contamination by false-positive transversion calls, which are the hallmark of sequencing errors. After proper quality filtering, the Ts/Tv ratio should rise to the expected range for the assay type.
A "raw" VCF is the direct output of a variant caller before any quality filtering has been applied. A "filtered" VCF has had quality thresholds applied, either by hard filtering (QUAL, DP, QD, etc.) or by model-based filtering (GATK VQSR, CNN). Distinguishing between the two is critical because computing statistics on raw data without disclosure leads to incorrect conclusions.
Indicators that a VCF is raw or unfiltered:
sample_raw_variants.vcf). (missing) for all recordsIndicators that a VCF has already been filtered:
PASS, LowQual, VQSRTrancheSNP99.90to100.00)##FILTER= and ##bcftools_viewCommand= lines)The FILTER column in VCF format has specific semantics defined by the VCF specification:
. (dot) -- filter status has not been applied; the variant is unassessedPASS -- the variant passed all filtersA common misconception is that . means the variant passed. In reality, . means no filter has been evaluated, so the variant's quality is unknown. Another misconception is that PASS in every row means the file is unfiltered -- some callers (e.g., DeepVariant) mark all emitted variants as PASS because they only output high-confidence calls.
To inspect the FILTER column programmatically:
# Count occurrences of each FILTER value
bcftools query -f '%FILTER\n' input.vcf | sort | uniq -c | sort -rn | head
# Check if any non-'.' FILTER values exist
bcftools query -f '%FILTER\n' input.vcf | grep -v '^\.$' | headUnderstanding the FILTER column is the first step in any VCF quality assessment. Always inspect it before deciding whether additional filtering is needed.
Settle these with the user before writing any analysis code.
decisions:
- id: D1
param: analysisGoal
kind: required
source: user
ask: "What is the filtered call set for - a rare-disease search, a somatic variant list, a population study, or a genotyping panel?"
default: null
- id: D2
param: filteringApproach
kind: required
source: user
depends_on: [D1]
ask: "Fixed hard thresholds, or a recalibration model trained on the cohort?"
default: "hard filters; recalibration needs a cohort large enough to train on"
- id: D3
param: qualityThresholds
kind: required
source: user
depends_on: [D2]
ask: "Where should depth, genotype quality, and strand-bias cuts sit?"
default: null
- id: D4
param: frequencyFilter
kind: required
source: user
depends_on: [D1]
ask: "Should variants common in reference populations be removed, and at what frequency?"
default: "no frequency filter"
- id: D5
param: consequenceFilter
kind: required
source: user
depends_on: [D1]
ask: "Should the set be restricted by predicted consequence - coding only, high-impact only, or everything?"
default: "everything"
- id: D6
param: inheritanceModel
kind: optional_conditional
source: user
depends_on: [D1]
ask: "Is there a family structure to filter on - de novo, recessive, compound heterozygous?"
default: "none"
skip_if: "single unrelated sample"Every decision here hangs on D1, and that is the point: there is no generally correct filter. A rare-disease search removes anything common in the population; a population-genetics study needs exactly those variants kept. Applying a remembered threshold set without asking what the call set is for silently answers a different question.
Is the VCF raw or unfiltered?
├── Yes (FILTER='.', many low-QUAL variants)
│ ├── Does the user ask for RAW statistics specifically?
│ │ ├── Yes → Do NOT filter; compute on data as-is, note it is raw
│ │ └── No → Apply QUAL>=30 filter before computing statistics
│ └── Does the user specify a custom threshold?
│ ├── Yes → Use their threshold
│ └── No → Default to QUAL>=30
├── No (FILTER has PASS/other values, filtered header present)
│ ├── Does the user ask for additional filtering?
│ │ ├── Yes → Apply requested filter
│ │ └── No → Compute statistics on existing filtered set
│ └── Does the Ts/Tv ratio look suspicious despite filtering?
│ └── Yes → Investigate; may need stricter thresholds
└── Uncertain
└── Inspect FILTER column and QUAL distribution to determine status| Scenario | Action | Rationale |
|---|---|---|
| Raw VCF, general statistics requested | Apply QUAL>=30 before computing | Low-QUAL variants are enriched for errors and bias all statistics |
| Raw VCF, user explicitly asks for raw stats | Compute as-is, report that data is unfiltered | Respect the user's intent; they may be evaluating caller performance |
| Raw VCF, user specifies threshold (e.g., QUAL>=50) | Use the user-specified threshold | User has domain-specific requirements |
| Pre-filtered VCF (FILTER=PASS present) | Compute without additional QUAL filter | Double-filtering may remove true variants unnecessarily |
| VCF with mixed FILTER values | Filter to PASS-only with bcftools view -f PASS | Non-PASS variants were flagged for a reason |
| VQSR-filtered VCF | Respect existing tranche filtering | VQSR is a more sophisticated filter than simple QUAL thresholds |
| Small panel or targeted sequencing | Consider lower QUAL threshold (e.g., 20) | Small panels have different error profiles and fewer variants |
bcftools query -f '%FILTER\n' input.vcf | sort | uniq -c | sort -rn | head to get a quick overview. # Preferred: use bcftools for Ts/Tv computation
bcftools stats input.vcf | grep ^TSTV
# Preferred: use bcftools for variant counts
bcftools stats input.vcf | grep ^SN # Filter and compute statistics in one pipeline
bcftools view -i 'QUAL>=30' input.vcf | bcftools stats - > filtered_stats.txt. for all records, apply QUAL>=30 filtering first.. (unassessed), PASS (passed all filters), and named filters (failed). Use bcftools query -f '%FILTER\n' | sort | uniq -c to inspect.bcftools stats for Ts/Tv computation. It is well-tested, fast, and handles all VCF edge cases correctly.##bcftools_viewCommand, ##GATKCommandLine) and the FILTER column for non-dot values before adding more filters. # View header for caller and filter information
bcftools view -h input.vcf | grep -E '##(FILTER|source|GATKCommandLine|bcftools)'
# Check FILTER column values
bcftools query -f '%FILTER\n' input.vcf | sort | uniq -c | sort -rn | head # Check QUAL distribution
bcftools query -f '%QUAL\n' input.vcf | awk '{if($1<30) low++; else high++} END {print "QUAL<30:", low+0, "QUAL>=30:", high+0}'. and many low-QUAL variants exist, proceed to Step 3. # Apply QUAL>=30 filter
bcftools view -i 'QUAL>=30' input.vcf -Oz -o filtered.vcf.gz
bcftools index filtered.vcf.gz # Ts/Tv ratio from filtered VCF
bcftools view -i 'QUAL>=30' input.vcf | bcftools stats - | grep ^TSTV | cut -f5
# Full summary numbers
bcftools view -i 'QUAL>=30' input.vcf | bcftools stats - | grep ^SN
# Per-sample statistics
bcftools stats -s - input.vcf | grep ^PSC # Count variants before filtering
bcftools view -H input.vcf | wc -l
# Count variants after filtering
bcftools view -i 'QUAL>=30' input.vcf | bcftools view -H | wc -l
# Compare Ts/Tv before and after
echo "=== Before filtering ==="
bcftools stats input.vcf | grep ^TSTV
echo "=== After QUAL>=30 filtering ==="
bcftools view -i 'QUAL>=30' input.vcf | bcftools stats - | grep ^TSTVbcftools query -f '%FILTER\n' and examine the QUAL distribution. This takes seconds and prevents hours of wasted analysis on unreliable data.bcftools view and bcftools stats in a pipe rather than writing intermediate files. This is faster, uses less disk, and reduces the chance of analyzing the wrong file.bcftools-variant-manipulation -- Detailed bcftools usage for subsetting, merging, annotating, and manipulating VCF files beyond simple filteringgatk-variant-calling -- Upstream variant calling with GATK HaplotypeCaller, including VQSR and hard filtering configurationsamtools-bam-processing -- BAM file quality control and processing steps that precede variant calling and affect downstream VCF quality~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.