Skip to main content

Fast VCF to Polars/Pandas DataFrame converter

Project description

vcfv

Fast VCF parser to Polars/Pandas DataFrame with INFO and FORMAT field expansion.

from vcfv import read_vcf

df = read_vcf("variants.vcf", parse_info="wide", parse_format="wide")

Why vcfv?

Existing VCF parsers either load the entire file into memory at once, keep INFO and FORMAT as raw strings, or are built on aging dependencies. vcfv is built on Polars which gives you:

  • Lazy evaluation — filters and projections are pushed down before reading
  • Native INFO and FORMAT expansion — split into typed DataFrame columns in a single call
  • Simple API — one function, sensible defaults
  • Gzip support — reads .vcf.gz transparently

Installation

pip install vcfv

With Pandas support:

pip install vcfv[pandas]

Usage

Basic

from vcfv import read_vcf

# Returns a Polars DataFrame
df = read_vcf("variants.vcf")

INFO and FORMAT expansion

# Expand INFO fields into typed columns (INFO_AC, INFO_AF, INFO_DP, ...)
df = read_vcf("variants.vcf", parse_info="wide")

# Expand FORMAT fields per sample (Sample1_GT, Sample1_DP, ...)
df = read_vcf("variants.vcf", parse_format="wide")

# Both at once
df = read_vcf("variants.vcf", parse_info="wide", parse_format="wide")

Lazy evaluation

import polars as pl
from vcfv import read_vcf

# Returns a LazyFrame — nothing is read until .collect()
lf = read_vcf("variants.vcf", lazy=True)

# Combine with any Polars lazy operations before collecting
df = lf.filter(pl.col("CHROM") == "chr1").collect()

Filtering

# Filter by minimum QUAL score — rows with QUAL=. are excluded
df = read_vcf("variants.vcf", min_qual=30.0)

# Filter by genomic region — format: "chrom:start-end"
df = read_vcf("variants.vcf", region="chr1:1000000-2000000")

# Select specific output columns
df = read_vcf("variants.vcf", fields=["CHROM", "POS", "REF", "ALT"])

# Combine filters freely
df = read_vcf(
    "variants.vcf",
    parse_info="wide",
    parse_format="wide",
    min_qual=30.0,
    region="chr1:1000000-2000000",
    fields=["CHROM", "POS", "REF", "ALT", "INFO_AF"],
)

Gzip support

df = read_vcf("variants.vcf.gz")

Chunked iteration for large files

from vcfv import iter_vcf

for chunk in iter_vcf("large.vcf", chunk_size=10_000):
    process(chunk)  # each chunk is a Polars DataFrame

Pandas output

from vcfv import read_vcf_pandas

df = read_vcf_pandas("variants.vcf")  # returns a Pandas DataFrame

Incomplete INFO/FORMAT headers

Some VCF files have INFO or FORMAT fields in the data that are not declared in the header. By default vcfv reads keys only from the header (infer_info_keys_from="header") which is fastest. If you suspect your file has undeclared fields you can scan a sample of rows or the entire file:

# Scan first 50,000 rows to detect undeclared INFO keys
# Emits a UserWarning if any undeclared keys are found
df = read_vcf("variants.vcf", infer_info_keys_from=50_000)

# Scan the entire file — safe but slower (streams in chunks, no full RAM load)
df = read_vcf("variants.vcf", infer_info_keys_from="all")

# Same options available for FORMAT keys
df = read_vcf("variants.vcf", infer_format_keys_from="all")

API reference

read_vcf

read_vcf(
    path: str | Path,
    *,
    lazy: bool = False,
    parse_info: False | "wide" | "struct" | "auto" = "auto",
    infer_info_keys_from: int | "header" | "all" = "header",
    parse_format: False | "wide" | "struct" | "auto" = "auto",
    infer_format_keys_from: int | "header" | "all" = "header",
    min_qual: float | None = None,
    region: str | None = None,
    fields: list[str] | None = None,
) -> pl.DataFrame | pl.LazyFrame
Parameter Type Default Description
path str | Path Path to .vcf or .vcf.gz file
lazy bool False Return a LazyFrame instead of DataFrame
parse_info see below "auto" How to handle the INFO column
infer_info_keys_from int | "header" | "all" "header" Where to discover INFO field keys
parse_format see below "auto" How to handle FORMAT and sample columns
infer_format_keys_from int | "header" | "all" "header" Where to discover FORMAT field keys
min_qual float | None None Keep only rows where QUAL >= min_qual. Rows with QUAL=. are excluded
region str | None None Genomic region filter in format "chr1:1000000-2000000"
fields list[str] | None None Select specific output columns by name

parse_info and parse_format options

Value Behaviour
"auto" Picks the best strategy automatically (see below)
"wide" Each field becomes a separate typed column (INFO_AF, Sample1_GT, ...)
"struct" Fields stored as a list of strings per row — memory efficient for many samples
False No parsing — raw strings kept as-is

parse_format="auto" selects:

  • "wide" for ≤ 20 samples
  • "struct" for ≤ 200 samples
  • False for > 200 samples

parse_info="auto" always selects "wide".

infer_info_keys_from and infer_format_keys_from options

Value Behaviour
"header" Read keys from ##INFO/##FORMAT header lines — fastest, default
int (e.g. 10_000) Scan first N rows and merge with header keys — emits UserWarning for undeclared keys
"all" Stream entire file in chunks to find all keys — safe for incomplete headers, slowest

iter_vcf

iter_vcf(
    path: str | Path,
    chunk_size: int = 10_000,
    *,
    parse_info: False | "wide" | "struct" | "auto" = "auto",
    infer_info_keys_from: int | "header" | "all" = "header",
    parse_format: False | "wide" | "struct" | "auto" = "auto",
    infer_format_keys_from: int | "header" | "all" = "header",
    min_qual: float | None = None,
    region: str | None = None,
    fields: list[str] | None = None,
) -> Iterator[pl.DataFrame]

Yields chunk_size rows at a time as Polars DataFrames. Accepts all the same parameters as read_vcf.

Benchmarks

Tested on 500,000 variants, 10 runs, trimmed mean (top/bottom 10% dropped). RAM measured via psutil RSS delta per subprocess (includes Rust/C heap). Tested on: Windows 11, AMD Ryzen 7 7730U, 16 GB RAM.

vcfv

Mode Mean Median Stdev RAM
eager (INFO+FORMAT expanded, header) 3.39s 3.38s ±0.31s 570 MB
lazy (INFO+FORMAT expanded, header) 3.55s 3.57s ±0.11s 571 MB
eager (INFO+FORMAT expanded, all) 9.71s 9.70s ±0.32s 646 MB
eager (no expansion) 0.15s 0.14s ±0.01s 273 MB
lazy (no expansion) 0.15s 0.15s ±0.01s 277 MB

Baselines (no INFO/FORMAT expansion — apples-to-apples)

Tool Mean Median Stdev RAM
scikit-allel 2.10s 2.10s ±0.05s 115 MB
pandas raw TSV 3.23s 3.21s ±0.08s 358 MB

vcfv with no expansion is ~14x faster than scikit-allel (0.15s vs 2.10s).

Neither scikit-allel nor pandas natively expand INFO or FORMAT fields — vcfv is the only tool in this comparison that does so in a single call.

To reproduce: python benchmarks/benchmark.py your_file.vcf --runs 10

Supported formats

Format Support
.vcf ✅ Full lazy streaming
.vcf.gz ✅ Decompressed into memory before parsing
.bcf ❌ Not supported yet

Requirements

  • Python ≥ 3.11
  • Polars ≥ 1.0
  • PyArrow ≥ 14.0

Contributing

Issues and pull requests are welcome. Please open an issue before submitting a PR for larger changes.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vcfv-0.2.0.tar.gz (15.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vcfv-0.2.0-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file vcfv-0.2.0.tar.gz.

File metadata

  • Download URL: vcfv-0.2.0.tar.gz
  • Upload date:
  • Size: 15.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for vcfv-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4f598ab81203d7718d780325841f7fba4e74e6f2517b03c7cee6dd7925c5c713
MD5 194b2e77bd8a1922498e045962a3bc51
BLAKE2b-256 4990857425c061a7a0f35a80024d264809a7a929abc671c77ee4a49beb4c8c8f

See more details on using hashes here.

File details

Details for the file vcfv-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: vcfv-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for vcfv-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 63da096bb7fb4a16f26f748f977e3fca30137421a7a54649ddbee3adfe8dbbbe
MD5 491879654eb7d6bd82437cfc41e9f8b5
BLAKE2b-256 d4a51e92e36715b33ba796c5e86d0238c4f86a15e864948cc2363cbf876c530e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page