| Title: | Vendored 'PLINK 2' pgenlib with a C-Callable Genotype Reader API |
|---|---|
| Description: | Vendors the read subset of 'PLINK 2's pgenlib library (<https://github.com/chrchang/plink-ng>), the same subset the CRAN package 'pgenlibr' vendors and builds, as a static library and exposes it through 'R_RegisterCCallable' C-callable entry points. Also vendors plink2's own import closure ('VcfToPgen', 'BcfToPgen', 'OxBgenToPgen', 'OxGenToPgen', 'OxHapslegendToPgen', 'Plink1DosageToPgen') so a VCF, BCF, BGEN, Oxford .gen, Oxford .haps/.legend, or legacy PLINK 1 --import-dosage file can be converted to a '.pgen' and read back through this package's own reader, without a separate 'htslib'-based parser. This is a carrier package: it has no high-level R modeling API of its own beyond a milestone smoke test that opens a '.pgen' file and reports its sample and variant counts. Other R packages link to it ('LinkingTo') to read 'PLINK 2' genotype files natively from their own C or C++ code, which 'pgenlibr' does not expose, since it only offers an R-level interface. |
| Authors: | Sounkou Mahamane Toure [aut, cre], Christopher Chang [cph] (Author of the vendored pgenlib library and plink2 program), Shaun Purcell [cph] (Co-author of the vendored plink2 program (VcfToPgen import closure)), Eric Biggers [cph] (Author of included libdeflate library), Yann Collet [cph] (Author of included Zstd library), Meta Platforms, Inc. [cph] (Zstd library), Evan Nemerson [cph] (Author of included SIMDe library), Mutsuo Saito [cph] (Co-author of included SFMT library), Makoto Matsumoto [cph] (Co-author of included SFMT library) |
| Maintainer: | Sounkou Mahamane Toure <[email protected]> |
| License: | GPL-3 |
| Version: | 0.1.0 |
| Built: | 2026-07-10 10:09:44 UTC |
| Source: | https://github.com/sounkou-bioinfo/Rfmalloc |
Reads hardcalls and packs them into fmalloc-backed, 2-bit .bed storage
with Rfmalloc::fmalloc_bed(). path selects the reader: a path ending in
.bed is read with rpgen_read_bed_hardcalls() (PLINK 1, counts from the
companion .bim/.fam); anything else is read with
rpgen_read_hardcalls() (PLINK 2 .pgen). The genotype matrix itself is
materialized once, in R, as the integer matrix pgenlib decodes to; the
fmalloc tensor it packs into is what downstream matrix products (a
genotype PCA, a GRM) stream out-of-core, so the genotypes make one trip
through an ordinary R matrix on the way into fmalloc storage, never a
second one back out as doubles.
rpgen_bed(path, pvar = NULL, bim = NULL, fam = NULL, runtime = NULL)rpgen_bed(path, pvar = NULL, bim = NULL, fam = NULL, runtime = NULL)
path |
Path to a |
pvar |
Path to the companion |
bim, fam
|
Paths to the companion |
runtime |
Runtime handle from |
An fmalloc_tensor of dtype "bed", n_sample x n_variant.
rpgen_dosage(), rpgen_read_hardcalls(),
rpgen_read_bed_hardcalls()
pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") rt <- Rfmalloc::open_fmalloc(tempfile(), size_gb = 0.5) tn <- rpgen_bed(pgen, runtime = rt) dim(tn) Rfmalloc::cleanup_fmalloc(rt)pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") rt <- Rfmalloc::open_fmalloc(tempfile(), size_gb = 0.5) tn <- rpgen_bed(pgen, runtime = rt) dim(tn) Rfmalloc::cleanup_fmalloc(rt)
A PLINK 1 .bed file carries no header of its own (unlike a .pgen's -
see rpgen_info()): its sample and variant counts come from the companion
.fam (one line per sample) and .bim (one line per variant) instead.
This reads exactly those two line counts, with no pgenlib involvement.
rpgen_bed_info(bed)rpgen_bed_info(bed)
bed |
Path to a |
A list with n_sample and n_variant, both integers.
rpgen_info(), rpgen_read_bed_hardcalls()
Reads dosages from a PLINK 2 .pgen file with rpgen_read_dosages() and
packs them into fmalloc-backed, 1-byte fixed-point storage with
Rfmalloc::fmalloc_dosage(). As with rpgen_bed(), the genotype matrix is
materialized once, in R, as the numeric matrix pgenlib decodes to; the
fmalloc tensor it packs into is what a downstream standardized product
(via Rfmalloc::fmalloc_dosage_standardize()) streams out-of-core.
rpgen_dosage(path, pvar = NULL, runtime = NULL)rpgen_dosage(path, pvar = NULL, runtime = NULL)
path |
Path to a |
pvar |
Path to the companion |
runtime |
Runtime handle from |
An fmalloc_tensor of dtype "dosage", n_sample x n_variant.
rpgen_bed(), rpgen_read_dosages(),
Rfmalloc::fmalloc_dosage_standardize()
pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") rt <- Rfmalloc::open_fmalloc(tempfile(), size_gb = 0.5) tn <- rpgen_dosage(pgen, runtime = rt) dim(tn) Rfmalloc::cleanup_fmalloc(rt)pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") rt <- Rfmalloc::open_fmalloc(tempfile(), size_gb = 0.5) tn <- rpgen_dosage(pgen, runtime = rt) dim(tn) Rfmalloc::cleanup_fmalloc(rt)
Converts bcf to a .pgen by calling plink2's own BcfToPgen() importer
BCF's binary-sibling counterpart to rpgen_import_vcf()'s VcfToPgen(),
living in the same vendored closure - with the defaults a plain
plink2 --bcf <bcf> --make-pgen (no other flags) would use.
rpgen_import_bcf(bcf, out = tempfile(fileext = ".pgen"))rpgen_import_bcf(bcf, out = tempfile(fileext = ".pgen"))
bcf |
Path to a BCF file. |
out |
Path to the |
The produced .pgen can be read back with any of Rpgen's existing .pgen
readers, exactly as rpgen_import_vcf()'s can.
out, invisibly on success; an R error is raised on failure.
rpgen_import_vcf(), rpgen_read_hardcalls()
vcf <- system.file("extdata", "tiny.vcf", package = "Rpgen") if (nzchar(vcf) && nzchar(Sys.which("bcftools"))) { bcf <- tempfile(fileext = ".bcf") system2("bcftools", c("view", shQuote(vcf), "-Ob", "-o", shQuote(bcf))) pgen <- rpgen_import_bcf(bcf) rpgen_info(pgen)$n_sample unlink(c(bcf, pgen, sub("\\.pgen$", ".pvar", pgen), sub("\\.pgen$", ".psam", pgen))) }vcf <- system.file("extdata", "tiny.vcf", package = "Rpgen") if (nzchar(vcf) && nzchar(Sys.which("bcftools"))) { bcf <- tempfile(fileext = ".bcf") system2("bcftools", c("view", shQuote(vcf), "-Ob", "-o", shQuote(bcf))) pgen <- rpgen_import_bcf(bcf) rpgen_info(pgen)$n_sample unlink(c(bcf, pgen, sub("\\.pgen$", ".pvar", pgen), sub("\\.pgen$", ".psam", pgen))) }
Convenience wrapper chaining rpgen_import_vcf() and rpgen_bed(): the
VCF is imported to a (by default temporary) .pgen via plink2's own
importer, immediately read back through Rpgen's existing .pgen reader,
and packed into fmalloc-backed 2-bit .bed storage. The intermediate
.pgen/.pvar/.psam files are removed afterward unless keep = TRUE.
rpgen_import_bed( vcf, out = tempfile(fileext = ".pgen"), keep = FALSE, runtime = NULL )rpgen_import_bed( vcf, out = tempfile(fileext = ".pgen"), keep = FALSE, runtime = NULL )
vcf |
Path to a VCF file; see |
out |
Path to the intermediate |
keep |
Keep the intermediate |
runtime |
Runtime handle from |
An fmalloc_tensor of dtype "bed", n_sample x n_variant; see
rpgen_bed().
rpgen_import_vcf(), rpgen_bed()
vcf <- system.file("extdata", "tiny.vcf", package = "Rpgen") if (nzchar(vcf)) { rt <- Rfmalloc::open_fmalloc(tempfile(), size_gb = 0.5) tn <- rpgen_import_bed(vcf, runtime = rt) dim(tn) Rfmalloc::cleanup_fmalloc(rt) }vcf <- system.file("extdata", "tiny.vcf", package = "Rpgen") if (nzchar(vcf)) { rt <- Rfmalloc::open_fmalloc(tempfile(), size_gb = 0.5) tn <- rpgen_import_bed(vcf, runtime = rt) dim(tn) Rfmalloc::cleanup_fmalloc(rt) }
Converts bgen (any of v1.1/v1.2/v1.3) to a .pgen by calling plink2's
own OxBgenToPgen() importer, with the defaults a plain
plink2 --bgen <bgen> --sample <sample> --make-pgen (or, if sample is
NULL, plink2 --bgen <bgen> --make-pgen alone) would use. Unlike
rpgen_import_gen()'s .gen, a BGEN v1.2/v1.3 file may carry its own
sample identifier block, so sample may be omitted when the file has one
plink2's own importer raises a clear error if it does not.
rpgen_import_bgen(bgen, sample = NULL, out = tempfile(fileext = ".pgen"))rpgen_import_bgen(bgen, sample = NULL, out = tempfile(fileext = ".pgen"))
bgen |
Path to a BGEN file (v1.1, v1.2, or v1.3). |
sample |
Path to a companion |
out |
Path to the |
The produced .pgen can be read back with any of Rpgen's existing .pgen
readers, exactly as rpgen_import_vcf()'s can.
out, invisibly on success; an R error is raised on failure.
rpgen_import_gen(), rpgen_import_haps()
bgen <- system.file("extdata", "tiny.bgen", package = "Rpgen") samp <- system.file("extdata", "tiny.sample", package = "Rpgen") if (nzchar(bgen) && nzchar(samp)) { pgen <- rpgen_import_bgen(bgen, sample = samp) rpgen_info(pgen)$n_sample unlink(c(pgen, sub("\\.pgen$", ".pvar", pgen), sub("\\.pgen$", ".psam", pgen))) }bgen <- system.file("extdata", "tiny.bgen", package = "Rpgen") samp <- system.file("extdata", "tiny.sample", package = "Rpgen") if (nzchar(bgen) && nzchar(samp)) { pgen <- rpgen_import_bgen(bgen, sample = samp) rpgen_info(pgen)$n_sample unlink(c(pgen, sub("\\.pgen$", ".pvar", pgen), sub("\\.pgen$", ".psam", pgen))) }
Converts the Oxford-format gen/sample pair to a .pgen by calling
plink2's own OxGenToPgen() importer, with the defaults a plain
plink2 --gen <gen> --sample <sample> --make-pgen would use. Unlike
rpgen_import_vcf()'s VCF/BCF, a .gen file carries no sample IDs or
pedigree of its own, so sample is required, not optional.
rpgen_import_gen(gen, sample, out = tempfile(fileext = ".pgen"))rpgen_import_gen(gen, sample, out = tempfile(fileext = ".pgen"))
gen |
Path to an Oxford-format |
sample |
Path to the companion |
out |
Path to the |
gen's rows must carry an explicit leading chromosome column (plink2's
original 5-column .gen layout: chr id pos a1 a2 <probabilities>...);
this function does not expose an --oxford-single-chr equivalent.
The produced .pgen can be read back with any of Rpgen's existing .pgen
readers, exactly as rpgen_import_vcf()'s can.
out, invisibly on success; an R error is raised on failure.
rpgen_import_bgen(), rpgen_import_haps()
Converts the Oxford-format haps/legend/sample triple to a .pgen by
calling plink2's own OxHapslegendToPgen() importer, with the defaults a
plain plink2 --haps <haps> --legend <legend> <chr> --sample <sample> --make-pgen would use.
rpgen_import_haps(haps, legend, sample, chr, out = tempfile(fileext = ".pgen"))rpgen_import_haps(haps, legend, sample, chr, out = tempfile(fileext = ".pgen"))
haps |
Path to a |
legend |
Path to the companion |
sample |
Path to the companion |
chr |
Chromosome code for every variant in |
out |
Path to the |
A .haps/.legend pair encodes phased haplotypes, and the produced
.pgen faithfully carries that phase - but Rpgen's current readers
(rpgen_read_hardcalls(), rpgen_read_dosages()) only read the
collapsed, unphased hardcall/dosage view; reading phase back is a future
milestone. The genotypes themselves (0/1/2 copies of the alternate
allele) still round-trip correctly through the existing readers - only
which haplotype carries which allele is not yet recoverable.
chr is required: the classic IMPUTE2 .legend format (id position a0 a1, no chromosome column) does not carry its own chromosome, so plink2
itself requires one whenever --legend is used (confirmed via
plink2 --help legend).
out, invisibly on success; an R error is raised on failure.
rpgen_import_gen(), rpgen_import_bgen(), rpgen_read_hardcalls()
Converts the legacy PLINK 1.x --import-dosage text format (dosage,
plus companion fam/map files) to a .pgen by calling plink2's own
Plink1DosageToPgen() importer, with the defaults a plain
plink2 --import-dosage <dosage> --fam <fam> --map <map> --make-pgen (no
--import-dosage modifiers) would use - in particular, the per-sample
column format (a single dosage value, or a double/triple-probability
layout) is auto-inferred from dosage's own column count, the same as
the plain command would do.
rpgen_import_plink1_dosage(dosage, fam, map, out = tempfile(fileext = ".pgen"))rpgen_import_plink1_dosage(dosage, fam, map, out = tempfile(fileext = ".pgen"))
dosage |
Path to a PLINK 1 |
fam |
Path to the companion |
map |
Path to the companion |
out |
Path to the |
The produced .pgen can be read back with any of Rpgen's existing .pgen
readers, exactly as rpgen_import_vcf()'s can.
out, invisibly on success; an R error is raised on failure.
rpgen_import_vcf(), rpgen_read_dosages()
Converts vcf to a .pgen by calling plink2's own VcfToPgen() importer
(vendored by tools/vendor-plink2-import/, see its PROVENANCE.md) with
the defaults a plain plink2 --vcf <vcf> --make-pgen (no other flags)
would use - no argv parsing happens; every default matches what plink2.cc
itself computes for that combination (see src/rpgen_import.cpp's
comments for exactly which). The design choice this embodies: reuse
plink2's own import code rather than a from-scratch htslib-based VCF
reader, because the identical vendored closure also covers BCF/BGEN/
Oxford for a later milestone.
rpgen_import_vcf(vcf, out = tempfile(fileext = ".pgen"))rpgen_import_vcf(vcf, out = tempfile(fileext = ".pgen"))
vcf |
Path to a VCF file (may be gzip/bgzip-compressed; plink2's own importer detects that from the file's magic bytes, not its extension). |
out |
Path to the |
The produced .pgen (plus its companion .pvar/.psam, written next to
it with the same base name) can be read back with any of Rpgen's existing
.pgen readers - rpgen_info(), rpgen_read_hardcalls(),
rpgen_read_dosages(), or rpgen_bed()/rpgen_dosage() for the
Rfmalloc tensor surface - since it is an ordinary .pgen, not a special
format of its own.
out, invisibly on success (matching the input, since plink2's
importer writes exactly there); an R error is raised on failure, with
plink2's own diagnostic already relayed to the R console (see
src/rpgen_import.cpp's top comment for why the error path has two
halves).
rpgen_bed(), rpgen_read_hardcalls()
vcf <- system.file("extdata", "tiny.vcf", package = "Rpgen") if (nzchar(vcf)) { pgen <- rpgen_import_vcf(vcf) info <- rpgen_info(pgen) info$n_sample unlink(c(pgen, sub("\\.pgen$", ".pvar", pgen), sub("\\.pgen$", ".psam", pgen))) }vcf <- system.file("extdata", "tiny.vcf", package = "Rpgen") if (nzchar(vcf)) { pgen <- rpgen_import_vcf(vcf) info <- rpgen_info(pgen) info$n_sample unlink(c(pgen, sub("\\.pgen$", ".pvar", pgen), sub("\\.pgen$", ".psam", pgen))) }
Opens a PLINK 2 .pgen file through Rpgen's vendored pgenlib
(PgfiInitPhase1() / PgfiInitPhase2() / PgrInit()), reads its header
counts, and closes it again. This is a thin wrapper around the
RC_rpgen_info .Call entry point, itself a thin wrapper around the
Rpgen_open_info C-callable registered for other packages to link
against (see inst/include/Rpgen.h).
rpgen_info(path)rpgen_info(path)
path |
Path to a |
A list with n_sample and n_variant, both integers.
pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") rpgen_info(pgen)pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") rpgen_info(pgen)
Reads every sample and every variant from a PLINK 1 .bed/.bim/.fam
fileset through Rpgen's vendored pgenlib. PgfiInitPhase1() opens a .bed
transparently, in the exact same code path a .pgen takes (its vrtypes
simply come back NULL) - the one real difference is that a .bed has no
header to read its sample/variant counts back from, so they are counted
from the companion .fam/.bim first (see rpgen_bed_info()) and passed
in explicitly. Genotypes are then read via the same plink2::PgrGet()
rpgen_read_hardcalls() uses; a .bed is biallelic hardcalls only, so
there is no dosage counterpart to this function. This is a thin wrapper
around the RC_rpgen_read_bed_hardcalls .Call entry point, itself a
thin wrapper around the Rpgen_read_bed_hardcalls C-callable (see
inst/include/Rpgen.h) - the lower-level counterpart to rpgen_bed() for
callers who want the plain R matrix rather than an Rfmalloc tensor.
rpgen_read_bed_hardcalls(bed, bim = NULL, fam = NULL)rpgen_read_bed_hardcalls(bed, bim = NULL, fam = NULL)
bed |
Path to a |
bim |
Path to the companion |
fam |
Path to the companion |
An integer matrix of 0, 1, 2, or NA hardcall dosages,
n_sample x n_variant, samples in rows and variants in columns.
rpgen_bed(), rpgen_bed_info(), rpgen_read_hardcalls()
Reads every sample and every variant from a PLINK 2 .pgen file through
Rpgen's vendored pgenlib, via plink2::PgrGet() (rpgen_read_hardcalls())
or plink2::PgrGetD() (rpgen_read_dosages()). Both return a dense
n_sample x n_variant matrix, samples in rows and variants in columns,
matching PLINK's own .bed orientation. These are thin wrappers around the
RC_rpgen_read_hardcalls/RC_rpgen_read_dosages .Call entry points,
themselves thin wrappers around the Rpgen_read_hardcalls/
Rpgen_read_dosages C-callables (see inst/include/Rpgen.h) - the
lower-level counterpart to rpgen_bed()/rpgen_dosage(), for callers who
want the plain R matrix rather than an Rfmalloc tensor.
rpgen_read_hardcalls(path, pvar = NULL) rpgen_read_dosages(path, pvar = NULL)rpgen_read_hardcalls(path, pvar = NULL) rpgen_read_dosages(path, pvar = NULL)
path |
Path to a |
pvar |
Path to the companion |
For a multiallelic variant, plink2::PgrGet()/PgrGetD() collapse every
ALT allele into a single non-reference count - the same encoding
pgenlibr::ReadIntList()/ReadList() return by calling the identical
pgenlib entry points. Allele identity (which ALT allele is present) is not
needed to produce that collapsed encoding, only for an allele-specific
read (plink2::PgrGet1()/PgrGet1D(), not exposed here), which is the
only thing pgenlibr's own .pvar requirement (NewPgen(..., pvar = )) at
this fixture's multiallelic-plus-dosage combination actually guards
against - so unlike pgenlibr::NewPgen(), rpgen_read_hardcalls()/
rpgen_read_dosages() do not require a .pvar for this file at all.
rpgen_read_hardcalls() returns an integer matrix of 0, 1,
2, or NA hardcall dosages. rpgen_read_dosages() returns a numeric
matrix of dosages in [0, 2], or NA for missing.
pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") hc <- rpgen_read_hardcalls(pgen) dim(hc) ds <- rpgen_read_dosages(pgen) dim(ds)pgen <- system.file("extdata", "chr21_phase3_start.pgen", package = "Rpgen") hc <- rpgen_read_hardcalls(pgen) dim(hc) ds <- rpgen_read_dosages(pgen) dim(ds)