makeCompressedMatrix <- function(x, dims, byrow=TRUE) # Coerces a NULL, scalar, vector or matrix to a compressed matrix, # Determines whether the rows or columns are intended to be # repeated, and stores this in the attributes. # # written by Aaron Lun # created 24 September 2016 # last modified 9 July 2017 { repeat.row <- repeat.col <- FALSE if (is.matrix(x)) { if (inherits(x, "CompressedMatrix")) { return(x) } dims <- dim(x) } else if (length(x)==1L) { repeat.row <- repeat.col <- TRUE x <- matrix(x) } else { if (!byrow) { if (dims[1]!=length(x)) { stop("'dims[1]' should equal length of 'x'") } x <- cbind(x) repeat.col <- TRUE } else { x <- rbind(x) if (dims[2]!=length(x)) { stop("'dims[2]' should equal length of 'x'") } repeat.row <- TRUE } } dimnames(x) <- NULL class(x) <- "CompressedMatrix" attr(x, "Dims") <- as.integer(dims) attr(x, "repeat.row") <- repeat.row attr(x, "repeat.col") <- repeat.col return(x) } .strip_to_matrix <- function(x) { dims <- attr(x, "dim") attributes(x) <- NULL attr(x, "dim") <- dims return(x) } dim.CompressedMatrix <- function(x) # Getting dimensions. # # written by Aaron Lun # created 21 June 2017 { attr(x, "Dims") } length.CompressedMatrix <- function(x) # Getting length. # # written by Aaron Lun # created 25 January 2018 # last modified 1 March 2018 { prod(attr(x,"Dims")) } `[.CompressedMatrix` <- function(x, i, j, drop=TRUE) # Subsetting for CompressedMatrix objects. # # written by Aaron Lun # created 24 September 2016 # last modified 21 June 2017 { Nargs <- nargs() - !(missing(drop)) if (Nargs<3L) { return(as.matrix(x)[i]) } raw.mat <- .strip_to_matrix(x) row.status <- attr(x, "repeat.row") col.status <- attr(x, "repeat.col") if (!row.status && !missing(i)) { raw.mat <- raw.mat[i,,drop=FALSE] } if (!col.status && !missing(j)) { raw.mat <- raw.mat[,j,drop=FALSE] } nr <- nrow(x) if (!missing(i)) { ref <- seq_len(nr) nr <- length(ref[i]) } nc <- ncol(x) if (!missing(j)) { ref <- seq_len(nc) nc <- length(ref[j]) } class(raw.mat) <- class(x) attr(raw.mat, "Dims") <- c(nr, nc) attr(raw.mat, "repeat.row") <- row.status attr(raw.mat, "repeat.col") <- col.status if (drop && ((!missing(i) && length(i)==1L) || (!missing(j) && length(j)==1L))) { raw.mat <- as.vector(as.matrix(raw.mat)) } return(raw.mat) } `[<-.CompressedMatrix` <- function(x, i, j, value) # Subset assignment for CompressedMatrix objects. # # written by Aaron Lun # created 25 January 2018 { ref <- as.matrix(x) if (is(value, "CompressedMatrix")) { value <- as.matrix(value) } if (nargs() < 4L) { ref[i] <- value } else { ref[i,j] <- value } makeCompressedMatrix(ref, attr(x, "Dims"), TRUE) } as.matrix.CompressedMatrix <- function(x, ...) # Expanding it to a full matrix. # # written by Aaron Lun # created 26 September 2016 # last modified 21 June 2017 { raw.mat <- .strip_to_matrix(x) row.status <- attr(x, "repeat.row") col.status <- attr(x, "repeat.col") if (row.status) { raw.mat <- matrix(raw.mat, nrow(x), ncol(x), byrow=TRUE) } else if (col.status) { raw.mat <- matrix(raw.mat, nrow(x), ncol(x)) } else { raw.mat <- as.matrix(raw.mat) } return(raw.mat) } rbind.CompressedMatrix <- function(...) # Rbinding things together. # # written by Aaron Lun # created 21 June 2017 { everything <- list(...) nobjects <- length(everything) if (nobjects==1) { return(everything[[1]]) } all.nr <- sum(unlist(lapply(everything, nrow))) col.rep <- logical(nobjects) row.rep <- logical(nobjects) for (i in seq_along(everything)) { x <- everything[[i]] col.rep[i] <- attr(x, "repeat.col") row.rep[i] <- attr(x, "repeat.row") } # If everything is column repeats, we can do a naive concatenation. if (all(col.rep)) { collected.vals <- vector("list", nobjects) all.nc <- ncol(everything[[1]]) for (i in seq_along(everything)) { current <- everything[[i]] if (!identical(all.nc, ncol(current))) { stop("cannot combine CompressedMatrix objects with different number of columns") } collected.vals[[i]] <- rep(.strip_to_matrix(current), length.out=nrow(current)) } return(makeCompressedMatrix(unlist(collected.vals), dims=c(all.nr, all.nc), byrow=FALSE)) } # If everything is row repeats AND values are all equal, we can just modify the nr. if (all(row.rep)) { okay <- TRUE ref <- .strip_to_matrix(everything[[1]]) for (i in 2:length(everything)) { current <- .strip_to_matrix(everything[[i]]) if (!isTRUE(all.equal(everything[[i]], ref))) { okay <- FALSE break } } if (okay) { current <- everything[[1]] attr(current, "Dims")[1] <- all.nr return(current) } } # Otherwise, expanding each element and rbinding them. for (i in seq_along(everything)) { everything[[i]] <- as.matrix(everything[[i]]) } return(makeCompressedMatrix(do.call(rbind, everything))) } cbind.CompressedMatrix <- function(...) # Cbinding things together. # # written by Aaron Lun # created 21 June 2017 { everything <- list(...) nobjects <- length(everything) if (nobjects==1) { return(everything[[1]]) } all.nc <- sum(unlist(lapply(everything, ncol))) col.rep <- logical(nobjects) row.rep <- logical(nobjects) for (i in seq_along(everything)) { x <- everything[[i]] col.rep[i] <- attr(x, "repeat.col") row.rep[i] <- attr(x, "repeat.row") } # If everything is row repeats, we can do a naive concatenation. if (all(row.rep)) { collected.vals <- vector("list", nobjects) all.nr <- nrow(everything[[1]]) for (i in seq_along(everything)) { current <- everything[[i]] if (!identical(all.nr, nrow(current))) { stop("cannot combine CompressedMatrix objects with different number of rows") } collected.vals[[i]] <- rep(.strip_to_matrix(current), length.out=ncol(current)) } return(makeCompressedMatrix(unlist(collected.vals), dims=c(all.nr, all.nc), byrow=TRUE)) } # If everything is column repeats AND values are all equal, we can just modify the nc. if (all(col.rep)) { okay <- TRUE ref <- .strip_to_matrix(everything[[1]]) for (i in 2:length(everything)) { current <- .strip_to_matrix(everything[[i]]) if (!isTRUE(all.equal(everything[[i]], ref))) { okay <- FALSE break } } if (okay) { current <- everything[[1]] attr(current, "Dims")[2] <- all.nc return(current) } } # Otherwise, expanding each element and rbinding them. for (i in seq_along(everything)) { everything[[i]] <- as.matrix(everything[[i]]) } return(makeCompressedMatrix(do.call(cbind, everything))) } Ops.CompressedMatrix <- function(e1, e2) # A function that performs some binary operation on two CompressedMatrix objects, # in a manner that best preserves memory usage. # # written by Aaron Lun # created 26 September 2016 # last modified 30 June 2017 { if (!inherits(e1, "CompressedMatrix")) { e1 <- makeCompressedMatrix(e1, dim(e2), byrow=FALSE) # Promoted to column-major CompressedMatrix } if (!inherits(e2, "CompressedMatrix")) { e2 <- makeCompressedMatrix(e2, dim(e1), byrow=FALSE) } if (!identical(dim(e1), dim(e2))) { stop("CompressedMatrix dimensions should be equal for binary operations") } row.rep <- attr(e1, "repeat.row") && attr(e2, "repeat.row") col.rep <- attr(e1, "repeat.col") && attr(e2, "repeat.col") if (row.rep || col.rep) { new.dim <- dim(e1) e1 <- as.vector(.strip_to_matrix(e1)) e2 <- as.vector(.strip_to_matrix(e2)) outcome <- NextMethod(.Generic) outcome <- makeCompressedMatrix(outcome, new.dim, byrow=row.rep) } else { e1 <- as.matrix(e1) e2 <- as.matrix(e2) outcome <- NextMethod(.Generic) outcome <- makeCompressedMatrix(outcome) } return(outcome) } .compressOffsets <- function(y, offset, lib.size=NULL) # A convenience function to avoid repeatedly having to write the code below. # If provided, offsets take precedence over the library size. # If neither are provided, library sizes are automatically computed # as the sum of counts in the count matrix 'y'. # A prefunctory check for finite values is performed in the C++ code. # If 'offset' is already of the CompressedMatrix class, then # we assume it's already gone through this once so we don't do it again. { if (inherits(offset, "CompressedMatrix")) { return(offset) } if (is.null(offset)) { if (is.null(lib.size)) lib.size <- colSums(y) offset <- log(lib.size) } if (!is.double(offset)) storage.mode(offset) <- "double" offset <- makeCompressedMatrix(offset, dim(y), byrow=TRUE) check.range <- suppressWarnings(range(offset)) if (any(!is.finite(check.range))) { stop("offsets must be finite values") } return(offset) } .compressWeights <- function(y, weights=NULL) # A convenience function to avoid repeatedly having to write the code below. # All weights default to 1 if not specified. # A prefunctory check for finite, positive values is performed in the C++ code. # If 'weights' is already a CompressedMatrix, then we assume it's # already gone through this and don't do it again. { if (inherits(weights, "CompressedMatrix")) { return(weights) } if (is.null(weights)) weights <- 1 if (!is.double(weights)) storage.mode(weights) <- "double" weights <- makeCompressedMatrix(weights, dim(y), byrow=TRUE) check.range <- suppressWarnings(range(weights)) if (any(is.na(check.range)) || check.range[1] <= 0) { stop("weights must be finite positive values") } return(weights) } .compressPrior <- function(y, prior.count) # Again for the prior counts, checking for non-negative finite values. # Skipping the check if it's already a CompressedMatrix object. { if (inherits(prior.count, "CompressedMatrix")) { return(prior.count) } if(!is.double(prior.count)) storage.mode(prior.count) <- "double" prior.count <- makeCompressedMatrix(prior.count, dim(y), byrow=FALSE) check.range <- suppressWarnings(range(prior.count)) if (any(is.na(check.range)) || check.range[1] < 0) { stop("prior counts must be finite non-negative values") } return(prior.count) } .compressDispersions <- function(y, dispersion) # Again for the dispersions, checking for non-negative finite values. # Skipping the check if it's already a CompressedMatrix object. { if (inherits(dispersion, "CompressedMatrix")) { return(dispersion) } if(!is.double(dispersion)) storage.mode(dispersion) <- "double" dispersion <- makeCompressedMatrix(dispersion, dim(y), byrow=FALSE) check.range <- suppressWarnings(range(dispersion)) if (any(is.na(check.range)) || check.range[1] < 0) { stop("dispersions must be finite non-negative values") } return(dispersion) }