API reference#

The complete public Python API. See Reading VCZ for a worked, example-driven introduction to opening datasets and iterating variants, and Format conversion for the VCF, PLINK and BGEN writers.

Quickfind#

Querying VCZ data#

vcztools.open_zarr(file_or_url, /, *[, ...])

Open a Zarr store with configurable backends.

vcztools.VczReader(root, *[, ...])

Central reader for VCZ (Zarr-based VCF) files.

vcztools.BcftoolsFilter(reader, *[, ...])

Bcftools -i/-e expression compiled into a VariantFilter.

Detecting missing and fill sentinels#

vcztools.is_missing(arr)

Return a boolean array indicating which values are missing sentinels.

vcztools.is_fill(arr)

Return a boolean array indicating which values are end-of-vector (fill) sentinels.

vcztools.trim_fill(arr)

Drop trailing fill from a 1-D array.

Complete reference#

vcztools.open_zarr(file_or_url: str | Path, /, *, mode: str = 'r', zarr_format: int | None = None, backend_storage: str | None = None, storage_options: dict | None = None)#

Open a Zarr store with configurable backends.

An already-built zarr.Group or zarr.abc.store.Store is passed through (wrapped in zarr.open() for the latter).

Otherwise resolution depends on backend_storage:

  • None (default — local-only): .zip path → zarr.storage.ZipStore; local path → zarr.storage.LocalStore. URLs (str with ://) and non-empty storage_options raise.

  • "fsspec": explicit zarr.storage.FsspecStore via its from_url classmethod. Local paths become file:// URIs. storage_options is forwarded to fsspec.

  • "obstore": zarr.storage.ObjectStore over obstore.store.from_url. storage_options is unpacked as kwargs to from_url (e.g. client_options, retry_config).

  • "icechunk": an Icechunk storage built by make_icechunk_storage. storage_options is forwarded to the chosen storage constructor (S3 / Azure); for local Icechunk paths non-empty options raise.

class vcztools.VczReader(root, *, readahead_workers: int | None = None, readahead_bytes: int | None = None)#

Central reader for VCZ (Zarr-based VCF) files.

Owns the zarr root and provides metadata properties and variant iteration at both chunk and row granularity, via variant_chunks() and variants().

The reader starts with no configured selection — every real sample and every variant is iterated by default. Call set_samples() to restrict the sample selection before iterating.

Use as a context manager (with VczReader(root) as reader:) so the reader’s resources are released deterministically on exit.

Virtual fields#

Besides stored arrays, these computed variant-axis fields may be named in variant_chunks() fields or in a filter expression. Availability depends on the store; see virtual_field_names. They are addressable by name but never auto-emitted when fields is None.

variant_index

Global (store-wide) 0-based index of the variant.

variant_AC

Allele count in genotypes.

variant_AN

Total number of alleles in called genotypes.

variant_AF

Allele frequency.

variant_NS

Number of samples with data.

variant_N_ALT

Number of non-empty ALT alleles.

variant_N_MISSING

Number of samples with all-missing genotypes.

variant_F_MISSING

Fraction of samples with all-missing genotypes.

Parameters#

root

An already-opened zarr.Group pointing at the VCZ dataset. Use vcztools.open_zarr() to open a path (local, remote, or zip) with the desired backend before constructing the reader.

readahead_workers

Worker count for the readahead thread pool. None (default) uses 32. The pool is created at construction; this parameter has no post-init knob.

readahead_bytes

Cap, in bytes, on the cross-chunk readahead window. None (default) uses 256 MiB. 0 pins pipeline depth at 1 (one chunk prefetched ahead of the consumer); the pipeline cannot go lower.

property sample_ids#

Selected sample IDs as a numpy array, in selection order.

set_samples(sample_ids, *, complement=False, ignore_missing_samples=False) None#

Configure the sample selection by sample ID.

sample_ids is a sequence of sample names, in the order the caller wants them in the output. An empty sequence is valid and means “no samples in output”. With complement=True the selection is every non-null sample except those named, in header order.

Unknown names raise ValueError unless ignore_missing_samples=True, in which case they are dropped with a warning. Duplicate names raise (non-complement) or are deduped (complement). Null (sample_id == "") samples are never selectable: the empty string is the reserved null-sample ID and requesting it raises ValueError. Not calling set_samples() (or set_sample_indexes()) selects every non-null sample.

Must be called before iterating; raises RuntimeError if the selection is already configured. Selecting a proper subset makes sample-dependent virtual fields (AC/AN/AF/NS …) recompute to reflect the subset on the next iteration. See variant_chunks().

set_sample_indexes(sample_indexes) None#

Configure the sample selection by raw integer index.

The lower-level counterpart of set_samples(): accepts a list or ndarray of integer indexes into the VCZ sample_id array, in the order the caller wants. An empty sequence is valid and means “no samples in output”. Out-of-range indexes raise ValueError, as does any index referring to a null sample (sample_id == ""). Duplicates are permitted. Must be called before iterating; raises RuntimeError if the selection is already configured.

set_variants(variants) None#

Configure the variant selection.

Accepts a sorted 1-D array of global variant indexes, which is bucketed into a chunk plan internally. A pre-built chunk plan (a list of ChunkRead) may also be passed for callers that already have one, e.g. from a region/target query.

May be called multiple times; each call replaces the prior selection. A variant_chunks() generator already iterating is unaffected — it snapshots the plan at start.

set_variant_filter(variant_filter: VariantFilter | None) None#

Configure (or clear) the variant filter.

variant_filter is any object implementing the VariantFilter protocol (e.g. a BcftoolsFilter), or None to clear a previously-set filter. By default a sample-scope filter evaluates over the user’s sample selection (bcftools query FMT-scope post-subset semantics).

May be called multiple times; each call replaces the prior filter. A variant_chunks() generator already iterating is unaffected — it snapshots the filter at start.

materialise_variant_filter() None#

Resolve the configured variant filter into a fixed selection.

Iterates variant_chunks() collecting the global indexes of surviving variants, then replaces (variant_filter, variant_chunk_plan) with a chunk plan over those variants. No-op if no filter is configured.

Only variant-scope filters are supported. Sample-scope filters require the per-cell mask emitted during iteration; resolve them by iterating variant_chunks() directly. Raises ValueError on a sample-scope filter.

property contig_ids#

Contig IDs as a numpy StringDType array.

property num_variants: int#

Total variants in the store (before any plan/filter).

property num_samples: int#

Total samples in the store (raw axis length, before any sample selection or null-sample filtering).

property raw_sample_ids: ndarray#

Full sample_id array from the store, including any null-string entries. For the post-subset order used when encoding rows, see sample_ids.

property field_names: frozenset[str]#

Set of real (Zarr-backed) field names present in the store.

Virtual fields (e.g. variant_AC on a store without a stored variant_AC array) are not included here even though variant_chunks() can yield them — they are addressable by name but not auto-discovered by the default-emit path. See virtual_field_names for the available virtual set.

property virtual_field_names: frozenset[str]#

Names of virtual fields whose dependencies are satisfied by the current store.

A name appears here when every dependency of its registry entry is present in the store; genotype-derived fields such as variant_N_MISSING are therefore absent on annotations-only stores that lack call_genotype. These names can be requested in the fields argument of variant_chunks().

get_field_info(name: str) FieldInfo#

Return a FieldInfo snapshot for the named field. Reads Zarr metadata on first access, then memoizes per-field. Raises KeyError if the field is absent.

For a stored field, returns the real on-disk metadata. For a virtual field with no stored counterpart, synthesises FieldInfo from the registry entry. For a virtual field that also exists as a stored array, the stored array wins (its attrs.description is preserved); the registry only substitutes its description if the stored array has none.

variant_chunks(*, fields: list[str] | None = None, start: int = 0, force_recompute=False)#

Yield dict[str, np.ndarray] per variant chunk that passes the current sample and variant selection.

fields names the fields to read; None (default) emits every stored field. A field’s value spans the variants axis (and, for FORMAT fields, the selected samples axis).

start is an offset into the sequence of variant chunks: iteration begins at the start-th chunk. start=0 (default) iterates every chunk; a start past the last chunk yields nothing; negative start raises ValueError.

force_recompute controls recomputation of virtual fields (see virtual_field_names) that have a same-named stored array. True recomputes every requested virtual field; an iterable scopes it to the named fields; False (default) forces none. A virtual field with no stored counterpart is always computed. When a sample subset is active, a sample-dependent virtual field (AC, AN, AF, NS, N_MISSING, F_MISSING) is recomputed to reflect the subset even with force_recompute=False, so a filter on the field and its emitted value always agree.

The returned iterator overlaps the consumer’s per-chunk work with the assembly of the next chunk; close() it promptly to release the in-flight chunk. Argument validation is eager: start < 0 and fields == [] are reported on the call itself rather than on the first next().

Virtual fields (see virtual_field_names) may be named in fields alongside stored arrays. "variant_index" is one: its per-chunk array holds the global (store-wide) int64 index of each surviving variant. Virtual fields are not auto-emitted when fields is None.

variants(*, fields: list[str] | None = None, force_recompute=False)#

Yield dict[str, scalar/1d-array] per variant row.

force_recompute is forwarded to variant_chunks().

class vcztools.FieldInfo(name: str, dtype: dtype, shape: tuple[int, ...], dims: tuple[str, ...], attrs: dict)#

Schema snapshot for a single store field.

Materialized once per field via VczReader.get_field_info() and cached on the reader. External callers (VCF header generation, etc.) should never reach into the Zarr store for metadata themselves — go through this dataclass instead.

vcztools.is_missing(arr: ndarray) ndarray#

Return a boolean array indicating which values are missing sentinels.

vcztools.is_fill(arr: ndarray) ndarray#

Return a boolean array indicating which values are end-of-vector (fill) sentinels. Flag (boolean) fields have no fill, so the mask is all-False for them.

vcztools.trim_fill(arr: ndarray) ndarray#

Drop trailing fill from a 1-D array.

Returns a view up to and including the last non-fill element, or an empty slice if every element is fill. Only trailing fill is trimmed; interior fill (a malformed vector) is left in place.

class vcztools.VariantFilter(*args, **kwargs)#

Variant-filter interface consumed by vcztools.VczReader.

Implementations need not inherit from this Protocol — any object that provides the three members below will satisfy it. The reader uses referenced_fields to decide which VCZ arrays to fetch and scope to decide how to combine the mask returned by evaluate() with the per-variant region/target mask.

A "variant"-scope filter MUST return a 1-D bool array of length n_variants. A "sample"-scope filter MUST return a 2-D bool array of shape (n_variants, n_samples). By default the filter sees the user’s sample selection (bcftools query semantics).

evaluate(chunk_data: Mapping[str, ndarray]) ndarray#
property referenced_fields: set[str]#
property scope: Literal['variant', 'sample']#
class vcztools.BcftoolsFilter(reader, *, include=None, exclude=None)#

Bcftools -i/-e expression compiled into a VariantFilter.

Accepts the same expression syntax as bcftools view -i / -e. Mutually exclusive: only one of include or exclude may be non-None. When neither is set the filter is a no-op; callers should skip the filter in that case rather than evaluate it.

The first argument is the target VczReader. Its field_names and virtual_field_names form the resolution surface the parser uses to map bare VCF names like DP to their VCZ equivalents (call_DP vs variant_DP). Both expression parsing and field-name resolution happen at instantiation time.

vcztools.write_vcf(reader, output, *, header_only: bool = False, no_header: bool = False, no_version: bool = False, encode_threads: int | None = None, fill_tags: frozenset | None = None) None#

Write the VCF text for reader to output.

output is either a filesystem path (str / pathlib.Path) or a writable text file-like object (anything with a .write method, including sys.stdout); paths are opened in text mode. VCF output is plain, uncompressed text.

Unlike write_plink() and write_bgen(), a configured variant filter on the reader does not need to be resolved first: records are streamed and filtered on the fly, so both a configured variant filter and a sample subset are honoured as-is.

header_only=True writes only the VCF header (the ## meta lines and the #CHROM column line) and no variant records.

no_header=True suppresses the header entirely, emitting only the variant records.

no_version=True omits the vcztools version and command line from the header.

For bcftools view -G semantics — omitting the FORMAT field and every sample column from both the header and the records — clear the reader’s sample selection first with reader.set_samples([]).

encode_threads sizes the worker pool that encodes each chunk’s records; None (default) selects the default (4). Encoded blocks are written in record order so the output is deterministic.

fill_tags is a set of VCF INFO tag names (e.g. {"AC", "AN"}) to emit as recomputed values, overriding any stored counterpart and injecting the corresponding INFO header lines. A tag with no corresponding virtual field available for the store raises ValueError.

Write PLINK 1 binary fileset for reader under stem output.

output is a filesystem stem (str or pathlib.Path) taken verbatim — "foo" produces foo.bed plus, by default, foo.bim and foo.fam. The .bed payload is always written; bim and fam toggle the matching sidecars.

The reader’s variant filter, if any, must already be resolved into a fixed selection via materialise_variant_filter(); a still-configured filter raises NotImplementedError. This keeps BIM rows and BED rows aligned. Sample-scope filters are not supported in PLINK 1 binary output: the .bed format is fixed-width per variant, so per-sample filtering doesn’t translate.

vcztools.write_bgen(reader, output, *, sample_path=None, bgi_path=None, embed_header_samples: bool | None = None, compression_level: int | None = None, encode_threads: int | None = None, unphased: bool = False, variant_id_field: str | None = None, fixed_variant_size: bool = False, total_string_length: int | None = None, pad_byte: bytes | None = None)#

Write an Oxford BGEN payload for reader to output.

output is either a filesystem path (str / pathlib.Path) or a writable binary file-like object (anything with a .write method, including sys.stdout.buffer); paths are opened via vcztools.utils.open_file_like() in "wb" mode. The function never seeks the output.

sample_path and bgi_path request the optional Oxford .sample text sidecar and the bgenix .bgen.bgi SQLite sidecar at the given filesystem paths; None (default) skips that sidecar. The .sample is written first; the .bgi is written last using the variant-block byte offsets accumulated while streaming the BGEN payload.

The reader’s variant filter, if any, must already be resolved into a fixed selection via materialise_variant_filter(); a still-configured filter raises NotImplementedError. This keeps variant-block ordering and .bgi entries aligned with the BGEN payload.

Variant scope: biallelic only (multi-allelic raises ValueError). Genotype source: hard calls from call_genotype encoded as 1.0 probability on the called genotype (8-bit precision round-trips exactly). Phase: per-variant from call_genotype_phased if the field exists in the store; otherwise unphased.

embed_header_samples controls whether the BGEN header carries sample IDs. When False, the SAMPLE_IDS_PRESENT flag is cleared and the sample-id block is omitted. Most downstream tools require sample IDs from either the BGEN header or a .sample sidecar; if neither is produced the function logs a warning.

compression_level is forwarded to zlib.compress() for each variant’s genotype probability block; accepts -1..9 (-1 = zlib default ≈ level 6; 0 = stored, still framed as zlib; 9 = maximum). The default is 1 — fast compression — on the variable-size path, and 0 (the only valid value) on the fixed_variant_size=True path. Hard-call BGEN payloads are short, low-entropy byte runs (mostly 1.0/0.0 in 8-bit form, repeated across samples), so the marginal compression above level 1 is small relative to the CPU cost: level 6 (zlib default) typically shrinks the file by ~10-30% but spends several times more CPU. Since the BGEN flag word always advertises COMPRESSION_ZLIB regardless of level, every reader handles the output.

encode_threads sizes the worker pool that runs per-slice _prepare_chunk() + per-variant _encode_variant_block for each chunk. Slice bytes are written back to the output in variant order on the main thread so byte layout and .bgi offsets stay deterministic. None selects the default (4).

unphased=True forces every variant’s phased flag to 0, ignoring call_genotype_phased if present. Use this when the downstream tool only accepts unphased BGEN (e.g. qctool’s -snp-stats, whose ToGP setter rejects per-haplotype-per- allele probabilities).

variant_id_field chooses which BGEN slot — "rsid" (default) or "varid" — carries the zarr variant_id. The other slot is the padding field; on the variable-size path it is written as the literal "." for every variant, on the fixed-size path it is b"." + pad_byte * (slack - 1) per variant.

fixed_variant_size=True switches output to the random-access fixed-stride encoding produced by BgenEncoder — every variant block is exactly 28 + total_string_length + zlib_stored_size(geno_size) bytes wide. The path requires uniform ploidy across the store (all haploid or all diploid); mixed-ploidy stores must leave fixed_variant_size=False. Requires compression_level to be None (default) or 0 — any other value raises ValueError.

total_string_length overrides BgenEncoder’s default combined byte budget (64) for the five BGEN string slots when fixed_variant_size=True. Only valid alongside fixed_variant_size=True.

pad_byte overrides BgenEncoder’s default padding byte (b".") used to fill the padding slot beyond its leading b".". Only valid alongside fixed_variant_size=True.

class vcztools.FormatEncoder(reader: VczReader, *, bytes_per_variant: int, prefix_bytes: bytes, iterator_fields: list[str], encode_threads: int | None = None, encode_block_bytes: int | None = None)#

Fixed-size, random-access byte-stream encoder over a VCZ store.

Abstract base of BedEncoder and BgenEncoder; not instantiated directly. Provides the shared streaming API — POSIX-style read(), bulk write_to(), and the size properties — while each subclass supplies the format-specific encoding of a single variant chunk.

Construction is I/O-free; bytes are produced lazily as they are read. A single encoder is not thread-safe, but multiple encoders may share one VczReader, each running an independent variant-chunk iteration. Use as a context manager (with BedEncoder(reader) as enc:) so the encoder’s iterator and thread pool are torn down on exit; close() does the same and does not close the underlying reader.

property bytes_per_variant: int#

Encoded byte length of a single variant block.

close() None#

Tear down the active chunk iterator, shut down the encode thread pool, and drop iterator state. Does not close the underlying reader. Idempotent.

property num_samples: int#

Number of samples in the encoded stream.

property num_variants: int#

Number of variants in the encoded stream, after any selection.

property prefix_size: int#

Byte length of the format prefix/header before the variant blocks.

read(off: int, size: int) bytes#

Return up to size bytes from the virtual stream at off.

POSIX-read semantics:

  • b"" if off >= total_size or size == 0

  • size clamped to the end of the stream

  • off < 0 or size < 0 raises ValueError

Reads whose start falls in the loaded chunk or the immediately- next plan chunk are served by slicing chunk-resident bytes, advancing the running iterator one chunk at a time as needed. Reads whose start is further away rebuild the iterator at the chunk containing off.

property total_size: int#

Total length of the encoded byte stream, in bytes.

try_cached_read(off: int, size: int) bytes | None#

Return self[off : off + size] iff it can be served from in-memory state (prefix bytes and/or the currently-loaded chunk) without advancing the variant iterator. Return None otherwise.

Safe to call without external synchronisation: the return is either the correct bytes or None. The method takes one atomic snapshot of the published chunk state, so a concurrent read() advancing the iterator on another thread either completes before the snapshot (reader sees the new chunk) or after (reader sees the old chunk); the reader never sees a half-updated state mixing one chunk’s bytes with another chunk’s start offset.

POSIX-read semantics matching read() for arg validation and EOF: returns b"" if off >= total_size or size == 0; size is clamped to the end of the stream; negative off or size raises ValueError; closed encoder raises RuntimeError.

write_to(out, off: int | None = None, size: int | None = None) int#

Stream self to out (a writable file-like with a write(bytes) method) and return the number of bytes written.

Defaults to writing the entire encoded stream. Pass off and/or size to limit to a sub-range:

  • off=None → starts at byte 0.

  • size=None → writes through total_size.

Validation and EOF semantics match read(): off and size must be non-negative; size is clamped to the end of the stream; reads past EOF write nothing. The stream is read and written in fixed-size blocks.

class vcztools.BedEncoder(reader: VczReader, *, encode_threads: int | None = None, encode_block_bytes: int | None = None)#

Bases: FormatEncoder

PLINK 1 .bed byte-stream encoder over a VCZ store.

Thin FormatEncoder subclass: the base class supplies the chunk-resident state machine, POSIX-style read(), iterator restart/advance arbitration, thread-pool lifecycle, and prefix (magic) serving. BedEncoder plugs in PLINK 1’s 3-byte magic prefix and the C-kernel encode of one variant chunk.

Scope is the .bed stream only. For the companion .bim and .fam files, use write_bim() and write_fam() directly.

Honours set_samples() and set_variants() configured on the reader before construction. With set_variants, the encoded .bed covers exactly the selected variants in chunk-plan order; bed_size and num_variants reflect the selection.

Biallelic checking is performed lazily as chunks are decoded; multi-allelic variants raise ValueError during read(), not at construction.

set_variant_filter() is not yet supported and raises NotImplementedError at construction; apply predicate filters externally before passing the reader in.

Per-chunk PLINK encoding parallelises across variant-axis sub-blocks via a concurrent.futures.ThreadPoolExecutor owned by the encoder. encode_threads (default 4) sets the pool size; encode_block_bytes (default 1 MiB) is the input-bytes target per sub-block. Chunks at or below the threshold encode synchronously on the calling thread.

The 1 MiB block default targets typical L2 cache size; PLINK encoding is a tight memory-walk loop and benefits from each thread’s working set fitting in L2. Bump for very wide cohorts if profiling shows scheduling overhead dominates encode time.

property bed_size: int#

Total .bed size in bytes (alias of total_size).

class vcztools.BgenEncoder(reader: VczReader, *, total_string_length: int | None = None, pad_byte: bytes | None = None, variant_id_field: str | None = None, embed_header_samples: bool = True, encode_threads: int | None = None, encode_block_bytes: int | None = None, unphased: bool = False)#

Bases: FormatEncoder

Random-access, fixed-size BGEN byte-stream encoder over a VCZ store.

Thin FormatEncoder subclass: the base class supplies the chunk-resident state machine, POSIX-style read(), iterator restart/advance arbitration, thread-pool lifecycle, and prefix (BGEN header) serving. BgenEncoder plugs in the layout-2 header bytes and the per-chunk fixed-size variant encoding.

The byte stream is a valid BGEN layout-2 file with the compression flag set to ZLIB. Every variant block uses zlib level 0 (stored, no DEFLATE) so the compressed payload size is a deterministic function of the uncompressed genotype block size — the variant block is therefore exactly bytes_per_variant bytes wide and byte offset variant index is O(1):

bytes_per_variant

= 28 + total_string_length + zlib_stored_size(geno_size)

geno_size = 10 + (uniform_ploidy + 1) * num_samples

where the constant 28 = 3 * 2 (string length prefixes, uint16) + 4 (position) + 2 (K) + 2 * 4 (allele length prefixes, uint32) + 2 * 4 (C and D length prefixes, uint32). uniform_ploidy is derived from reader.call_genotype.shape[2] and is either 1 (haploid; geno_size = 10 + 2 * num_samples) or 2 (diploid; geno_size = 10 + 3 * num_samples).

The five BGEN string fields (varid, rsid, chrom, allele1, allele2) share a single total_string_length budget per variant. Four of them — chrom, allele1, allele2, and whichever of varid/rsid is selected by variant_id_field — are emitted at their actual UTF-8 byte lengths. The fifth slot is the padding field, holding b"." + pad_byte * (slack - 1) where slack is whatever’s left of total_string_length after the other four. If a variant’s actual content sums past total_string_length - 1 (i.e. the padding field can’t even fit its leading "."), encoding raises ValueError. Defaults are tuned for biobank biallelic SNP arrays: total_string_length=64, pad_byte=b".", variant_id_field="rsid".

Variant scope: biallelic and uniform ploidy. call_genotype must have shape[2] == 1 (haploid) or shape[2] == 2 (diploid). Even within the diploid path, every sample must remain diploid — a chunk that contains the -2 haploid-padding sentinel raises NotImplementedError on read; use write_bgen() for mixed-ploidy stores. Multi-allelic input raises ValueError lazily as chunks are decoded.

The encoder serves only the .bgen byte stream. The matching bgenix .bgi SQLite sidecar can be produced by passing variant_offsets (computed from the encoder’s fixed-size layout header_size + i * bytes_per_variant) to the module-level write_bgi(). The .sample sidecar is produced by write_sample().

set_variant_filter() is not supported and raises NotImplementedError at construction; materialise the filter or use set_variants first. Unlike write_bgen(), the encoder is I/O-free in __init__.

unphased=True forces every variant’s phased flag to 0, ignoring call_genotype_phased if present — see write_bgen() for the use case.

property bgen_size: int#

Total .bgen size in bytes (alias of total_size).

property header_size: int#

BGEN header byte length (alias of prefix_size).

property pad_byte: bytes#

Single byte used to fill the padding slot beyond its leading b".". Default b".", never the NUL byte.

property total_string_length: int#

Combined byte budget for the five BGEN string slots — write_bgi() needs this to reproduce the per-variant padding the encoder wrote into the unused id slot.

property variant_id_field: str#

Which BGEN id slot ("rsid" or "varid") the encoder routes variant_id into; the other slot is the padding field.

property variant_offsets: ndarray#

Byte boundaries of every variant block in the encoded BGEN stream, of shape (num_variants + 1,): variant i occupies [variant_offsets[i], variant_offsets[i+1]). Suitable for write_bgi().

vcztools.write_bim(reader, output)#

Write the PLINK .bim variant sidecar for reader to output.

output is a filesystem path (str / pathlib.Path) or a writable text file-like object. Tab-separated, one row per variant: chromosome, variant ID (. when absent), genetic position (always 0), base-pair position, A1 (= ALT), A2 (= REF). Chromosome names are normalised to plink 2’s --make-bed output (chr11, chrMMT, non-standard contigs unchanged); monomorphic rows emit . in the A1 slot. Multi-allelic variants raise ValueError.

vcztools.write_fam(reader, output)#

Write the PLINK .fam sample sidecar for reader to output.

output is a filesystem path (str / pathlib.Path) or a writable text file-like object. The on-disk format is tab-separated, one row per sample, with FamilyID = IndividualID = the sample ID (the BOLT-LMM / qctool convention; accepted by PLINK 2, REGENIE, and BOLT-LMM without family-aware flags). The remaining four columns (FatherID, MotherID, Sex, Phenotype) are 0/0/0/-9. Whitespace in any sample ID is rejected — the FAM format is whitespace-separated.

vcztools.write_bgi(reader, output, variant_offsets, *, variant_id_field: str = 'rsid', total_string_length: int | None = None, pad_byte: bytes = b'.')#

Write the bgenix .bgen.bgi SQLite sidecar for reader.

output is a filesystem path (str / pathlib.Path); the SQLite database is created at that location (file-like objects are not supported — sqlite3.connect needs a real path). If the file already exists, it is unlinked first so the schema can be recreated without primary-key conflicts.

variant_offsets is an integer array of length num_variants + 1 giving the byte boundaries of each variant block: variant i occupies [variant_offsets[i], variant_offsets[i+1]). Use BgenEncoder.variant_offsets for the fixed-size encoder path, or the cumulative sum of per-variant block sizes (plus the BGEN prefix length) for the variable-size write_bgen() path.

variant_id_field mirrors write_bgen() / BgenEncoder: when "rsid" (default), variant_id populates the .bgi rsid column directly; when "varid", the BGEN rsid slot was the padding field at encode time, so the .bgi rsid column carries the same padding bytes the BGEN file holds. total_string_length and pad_byte are the same parameters as on BgenEncoder and reconstruct the per-variant padding strings: None matches write_bgen()’s single-byte "." padding, an integer matches the encoder’s "." + pad_byte * (slack - 1) pattern.

Variant-metadata columns (chromosome, position, rsid, alleles) are read via the reader’s standard variant_chunks API and assembled as numpy arrays; per-row tuples are streamed into SQLite via a generator so peak memory stays at the column arrays rather than an N-row tuple list. Variant scope is biallelic only; multi-allelic raises ValueError lazily on the chunk containing the offending row.

vcztools.write_sample(reader, output)#

Write the Oxford .sample text for reader to output.

output is a filesystem path (str / pathlib.Path) or a writable text file-like object. The minimal sample file: two header rows (ID_1 ID_2 missing and 0 0 0), then one row per sample with ID_1 and ID_2 both set to the sample name (matches the --double-id style) and the missing column set to 0. Whitespace in sample IDs is rejected (the file format is whitespace-separated).