- BEDMatrix
- Introduction to the BEDMatrix package
- Limitations of the BEDMatrix package
- ALTREP
- Introduction to the ALTREP framework
- Example ALTREPs
- ALTREP Reimplementation of BEDMatrix
- Step-by-Step
- Demo
- Conclusions
Feb 19, 2019
.bed files contain genotype calls at biallelic variants
library(BEDMatrix)
path <- system.file("extdata", "example.bed", package = "BEDMatrix")
X <- BEDMatrix(path)
dim(X) # matrix-like object
## [1] 50 1000
X[1:5, 1:5]
## snp0_A snp1_C snp2_G snp3_G snp4_G ## per0_per0 0 1 1 1 0 ## per1_per1 1 1 1 1 NA ## per2_per2 1 0 0 2 0 ## per3_per3 2 0 0 0 1 ## per4_per4 0 1 0 0 0
Supplementary files:
Access is read-only
as.vector(class(X))
## [1] "BEDMatrix"
slotNames(X)
## [1] "xptr" "dims" "dnames" "path"
methods(class = "BEDMatrix") # lots of methods need to be reimplemented
## [1] as.matrix [ dim dimnames<- dimnames initialize ## [7] is.matrix length show str ## see '?methods' for accessing help and source code
Some common issues:
Internal and primitive functions without methods dispatch will likely not work (hence BGData)
tcrossprod(X)
## Error in tcrossprod(x, y): requires numeric/complex matrix/vector arguments
Saving and reloading an object does not work
save(X, file = "X.RData")
load("X.RData")
X[1]
## Error in extract_vector(x, i, ...): external pointer is not valid
1:n, seq_along, and the like now use compact internal representations via the ALTREP framework.ALTREP framework objects.ALTREP). The framework is still experimental and may undergo changes in future R releases as more experience is gained. For now, documentation is provided in https://svn.r-project.org/R/branches/ALTREP/ALTREP.html.Source: https://cran.r-project.org/doc/manuals/r-release/NEWS.html
SEXP and VECSEXP, which are pointers to a structure with typedef SEXPREC or VECTOR_SEXPRECExample: x <- 1:10
VECTOR_SEXPREC of type INTSXPBasic representation of x <- 1:10 in memory on a 64-bit machine:
writeBin(1:10, con = "sequence.bin") # write example data (imagine something bigger)
mmap <- function(filename, type = c("integer", "double"),
ptrOK = TRUE, wrtOK = FALSE, serOK = TRUE) {
type <- match.arg(type)
.Internal(mmap_file(filename, type, ptrOK, wrtOK, serOK))
}
x <- mmap("sequence.bin", type = "integer")
x
## [1] 1 2 3 4 5 6 7 8 9 10
typeof(x) # regular integer vector
## [1] "integer"
class(x) # no method magic
## [1] "integer"
x <- 1:1e12 # huge sequence format(object.size(x), units = "GB") # seemingly too large to allocate
## [1] "7450.6 Gb"
x[7] # but can read from it
## [1] 7
typeof(x) # regular numeric vector
## [1] "double"
class(x) # no method magic
## [1] "numeric"
mmap_file()?mmap_file internal function is currently limited to files that store data as a sequence of 32-bit integers or doublesdevtools::create_package("ALTBED")) with some infrastructure to make use the R's C API
R_init_ALTBED()useDynLib("ALTBED", .registration = TRUE) to NAMESPACER_init_ALTBED()R_init_ALTBED().Call()ALTINTEGER, ALTREAL, and ALTSTRING can be subclassed by package developers at the moment
ALTINTEGER is an alternate representation of an integer vector (INTSXP)ALTREAL is an alternate representation of a double vector (REALSXP)ALTSTRING is an alternate representation of a character vector (STRSXP)ALTINTEGER is the best fit for representing genotype calls (0/1/2/missing)#include <R.h>
#include <Rinternals.h>
#include <R_ext/Altrep.h>
static R_altrep_class_t altrep_class_bedmatrix;
void R_init_ALTBED(DllInfo *dll) {
// ...
R_altrep_class_t class = R_make_altinteger_class("BEDMatrix", "ALTBED", dll);
altrep_class_bedmatrix = class;
// ...
}
alt bit set in sxpinfoalt bit (in a way, similar to S4 but at a much deeper level)ALTREP
Length: get number of data elementsDuplicate and DuplicateEX: duplicate SEXPCoerce, Serialized_state, Unserialize, UnserializeEX, InspectALTVEC
Dataptr: get pointer to data elementsDataptr_or_null: get pointer to data elements or NULL otherwiseExtract_subset: get data elements that match indexALTINTEGER
Elt: get ith elementGet_region: get n elements starting from iIs_sorted, Min, Max, Sum, No_NADataptr, Dataptr_or_null, Extract_subset, and Elt all extract data in different ways in different contexts:
Dataptr is used with macros such as INTEGER() and returns a pointer to 32-bit ints in memory
mmap_file())
Dataptr_or_null returns a data pointer if possible, or NULL otherwise (used to test if a data pointer can be returned)
NULL to fall back to a different mechanismExtract_subset is used with subsetting operators such as [
Elt is used for extracting a single data element at position k
Extract_subset)Get_region is used for extracting a sequence of elements (in case it is more efficient to extract one by one)
Elt so we do not need to implement thisDuplicate necessary to maintain the "call by value" illusion
Serialized_state is called on save() (currently, version = 3 needs to be specified with save() for ALTREP support)
Unserialize is called on load()
NULL from a method typically indicates that there is no special behavior and that the same behavior as for the basic representation should be usedstatic R_altrep_class_t altrep_class_bedmatrix;
void R_init_ALTBED(DllInfo *dll) {
// ...
// create class
R_altrep_class_t class = R_make_altinteger_class("BEDMatrix", "ALTBED", dll);
altrep_class_bedmatrix = class;
// override ALTREP methods
R_set_altrep_Length_method(class, bedmatrix_Length);
R_set_altrep_Duplicate_method(class, bedmatrix_Duplicate);
// override ALTVEC methods
R_set_altvec_Dataptr_method(class, bedmatrix_Dataptr);
R_set_altvec_Dataptr_or_null_method(class, bedmatrix_Dataptr_or_null);
R_set_altvec_Extract_subset_method(class, bedmatrix_Extract_subset);
// override ALTINTEGER methods
R_set_altinteger_Elt_method(class, bedmatrix_Elt);
// ...
}
static SEXP make_bedmatrix(SEXP path, SEXP nrows, SEXP ncols) {
// ...
// mmap .bed file
// ...
// Create ALTREP object
SEXP state = PROTECT(make_bedmatrix_state(...));
SEXP eptr = PROTECT(make_bedmatrix_eptr(...));
SEXP altrep = PROTECT(R_new_altrep(altrep_class_bedmatrix, eptr, state));
// Turn it into a matrix
SEXP dim = PROTECT(Rf_allocVector(INTSXP, 2));
INTEGER(dim)[0] = Rf_asInteger(nrows);
INTEGER(dim)[1] = Rf_asInteger(ncols);
Rf_setAttrib(altrep, R_DimSymbol, dim);
UNPROTECT(4); // state, eptr, altrep, dim
return altrep;
}
SEXP C_map(SEXP path, SEXP nrows, SEXP ncols) {
return make_bedmatrix(path, nrows, ncols);
}
static const R_CallMethodDef CallEntries[] = {
{"C_map", (DL_FUNC) &C_map, 3},
{NULL, NULL, 0}
};
void R_init_ALTBED(DllInfo *dll) {
// ...
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
// ...
}
map <- function(path) {
# Determine n from .fam file and p from .bim file ...
.Call(C_map, path, n, p)
}
X <- map("inst/extdata/example.bed")
tcrossprod work, but load data into memory (chunked processing is still needed)
do_summary uses Get_region)