├── .gitignore ├── .Rbuildignore ├── tests ├── utils │ └── test.r ├── zz-check.r ├── annmatrix.R └── annmatrix.Rout.save ├── .github └── FUNDING.yml ├── R ├── autocomplete.r ├── transpose.r ├── matrixgenerics.r ├── scale.r ├── stack.r ├── groupgenerics.r ├── convert.r ├── subset.r ├── pca.r ├── print.r ├── annmatrix.r └── bind.r ├── man ├── autocomplete.Rd ├── groupgenerics.Rd ├── stack.Rd ├── transpose.Rd ├── matrixgenerics.Rd ├── scale.Rd ├── print.Rd ├── convert.Rd ├── subset.Rd ├── pca.Rd ├── bind.Rd └── annmatrix.Rd ├── NAMESPACE ├── DESCRIPTION ├── NEWS └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | README.Rmd 5 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | meta 2 | .github 3 | ^\.travis\.yml$ 4 | README.md 5 | 6 | -------------------------------------------------------------------------------- /tests/utils/test.r: -------------------------------------------------------------------------------- 1 | # prints errors without stopping and returns the contents and type of annmatrix as a list 2 | test <- function(expr) { 3 | res <- try(expr) 4 | if (class(res)[1] == "annmatrix") { 5 | print(list(type = typeof(res), matrix = as.matrix(res), rann = attr(res, ".annmatrix.rann"), cann = attr(res, ".annmatrix.cann"))) 6 | } else if (class(res)[1] != "try-error") { 7 | print(res) 8 | } 9 | message(strrep("-", 80)) 10 | message("") 11 | } 12 | 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [karoliskoncevicius] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /R/autocomplete.r: -------------------------------------------------------------------------------- 1 | #' Auto Complete Functions for annmatrix Class 2 | #' 3 | #' Function used to select autocomplete options for dollar `$` and at `@` operators. 4 | #' 5 | #' @param x annmatrix object. 6 | #' @param pattern a regular expression used to select possible auto-completion names. 7 | #' 8 | #' @return A set of possible auto-completion names for row (\code{@}) or column (\code{$}) annotation fields. 9 | #' 10 | #' @author Karolis Koncevičius 11 | #' @name autocomplete 12 | #' @export 13 | .DollarNames.annmatrix <- function(x, pattern = "") { 14 | utils::findMatches(pattern, names(attr(x, ".annmatrix.cann"))) 15 | } 16 | 17 | #' @rdname autocomplete 18 | #' @export 19 | .AtNames.annmatrix <- function(x, pattern = "") { 20 | utils::findMatches(pattern, names(attr(x, ".annmatrix.rann"))) 21 | } 22 | -------------------------------------------------------------------------------- /man/autocomplete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/autocomplete.r 3 | \name{autocomplete} 4 | \alias{autocomplete} 5 | \alias{.DollarNames.annmatrix} 6 | \alias{.AtNames.annmatrix} 7 | \title{Auto Complete Functions for annmatrix Class} 8 | \usage{ 9 | \method{.DollarNames}{annmatrix}(x, pattern = "") 10 | 11 | \method{.AtNames}{annmatrix}(x, pattern = "") 12 | } 13 | \arguments{ 14 | \item{x}{annmatrix object.} 15 | 16 | \item{pattern}{a regular expression used to select possible auto-completion names.} 17 | } 18 | \value{ 19 | A set of possible auto-completion names for row (\code{@}) or column (\code{$}) annotation fields. 20 | } 21 | \description{ 22 | Function used to select autocomplete options for dollar `$` and at `@` operators. 23 | } 24 | \author{ 25 | Karolis Koncevičius 26 | } 27 | -------------------------------------------------------------------------------- /man/groupgenerics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/groupgenerics.r 3 | \name{groupgenerics} 4 | \alias{groupgenerics} 5 | \alias{Ops.annmatrix} 6 | \alias{chooseOpsMethod.annmatrix} 7 | \title{Group Generic Functions for annmatrix Class} 8 | \usage{ 9 | \method{Ops}{annmatrix}(e1, e2) 10 | 11 | \method{chooseOpsMethod}{annmatrix}(x, y, mx, my, cl, reverse) 12 | } 13 | \arguments{ 14 | \item{e1, e2}{annmatrix objects.} 15 | 16 | \item{x, y}{The objects being dispatched on by the group generic.} 17 | 18 | \item{mx, my}{The methods found for objects 'x' and 'y'.} 19 | 20 | \item{cl}{The call to the group generic.} 21 | 22 | \item{reverse}{A logical value indicating whether 'x' and 'y' are reversed from the way they were supplied to the generic.} 23 | } 24 | \value{ 25 | An object of class 'annmatrix'. 26 | } 27 | \description{ 28 | The functions listed here work under the hood and are almost never called by the user. 29 | } 30 | \author{ 31 | Karolis Koncevičius 32 | } 33 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | S3method(as.annmatrix,default) 2 | S3method(as.annmatrix,matrix) 3 | S3method(as.matrix,annmatrix) 4 | S3method(print,annmatrix) 5 | S3method("[",annmatrix) 6 | S3method("$",annmatrix) 7 | S3method("$<-",annmatrix) 8 | S3method("@",annmatrix) 9 | S3method("@<-",annmatrix) 10 | S3method(.AtNames,annmatrix) 11 | S3method(.DollarNames,annmatrix) 12 | S3method(rbind,annmatrix) 13 | S3method(cbind,annmatrix) 14 | S3method(scale,annmatrix) 15 | S3method(t,annmatrix) 16 | S3method("%*%",annmatrix) 17 | S3method(prcomp,annmatrix) 18 | S3method(stack,annmatrix) 19 | S3method(Ops,annmatrix) 20 | S3method(chooseOpsMethod,annmatrix) 21 | export(annmatrix) 22 | export(as.annmatrix) 23 | export(is.annmatrix) 24 | export(rowanns) 25 | export("rowanns<-") 26 | export(colanns) 27 | export("colanns<-") 28 | importFrom(utils, stack) 29 | importFrom(utils, .AtNames) 30 | importFrom(utils, .DollarNames) 31 | importFrom(utils, findMatches) 32 | importFrom(utils, .S3methods) 33 | importFrom(methods, callGeneric) 34 | importFrom(stats, prcomp) 35 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: annmatrix 2 | Title: Annotated Matrix: Matrices with Persistent Row and Column Annotations 3 | Version: 0.1.2 4 | Authors@R: person("Karolis", "Koncevičius", email = "karolis.koncevicius@gmail.com", role = c("aut", "cre")) 5 | Maintainer: Karolis Koncevičius 6 | Description: Implements persistent row and column annotations for R matrices. The annotations associated with rows and columns are preserved after subsetting, transposition, and various other matrix-specific operations. Intended use case is for storing and manipulating genomic datasets which typically consist of a matrix of measurements (like gene expression values) as well as annotations about rows (i.e. genomic locations) and annotations about columns (i.e. meta-data about collected samples). But 'annmatrix' objects are also expected to be useful in various other contexts. 7 | Depends: R (>= 4.3.0) 8 | Imports: utils, methods, stats 9 | License: GPL-2 10 | Encoding: UTF-8 11 | LazyData: true 12 | URL: https://github.com/karoliskoncevicius/annmatrix 13 | BugReports: https://github.com/karoliskoncevicius/annmatrix/issues 14 | RoxygenNote: 7.2.3 15 | -------------------------------------------------------------------------------- /man/stack.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/stack.r 3 | \name{stack} 4 | \alias{stack} 5 | \alias{stack.annmatrix} 6 | \title{Stack an annmatrix object} 7 | \usage{ 8 | \method{stack}{annmatrix}(x, ...) 9 | } 10 | \arguments{ 11 | \item{x}{annmatrix object.} 12 | 13 | \item{...}{further arguments passed to or from methods.} 14 | } 15 | \value{ 16 | transposed annmatrix object 17 | } 18 | \description{ 19 | Turns annmatrix into a data frame by transforming the matrix along with row and column annotations into separate data frame columns. 20 | } 21 | \examples{ 22 | # construct annmatrix object 23 | x <- matrix(rnorm(20*10), 20, 10) 24 | 25 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 26 | gender = sample(c("M", "F"), 10, replace = TRUE)) 27 | 28 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 29 | pos = runif(20, 0, 1000000)) 30 | 31 | X <- annmatrix(x, rowdata, coldata) 32 | 33 | # stack all information into a long-format data.frame 34 | Y <- stack(X) 35 | 36 | } 37 | \author{ 38 | Karolis Koncevičius 39 | } 40 | -------------------------------------------------------------------------------- /man/transpose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/transpose.r 3 | \name{transpose} 4 | \alias{transpose} 5 | \alias{t.annmatrix} 6 | \title{Transposing annmatrix Objects} 7 | \usage{ 8 | \method{t}{annmatrix}(x) 9 | } 10 | \arguments{ 11 | \item{x}{annmatrix object.} 12 | } 13 | \value{ 14 | transposed annmatrix object with appropriately adjusted row and column annotations. 15 | } 16 | \description{ 17 | Transpose annmatrix along with the associated row and column annotations. 18 | } 19 | \examples{ 20 | # construct annmatrix object 21 | x <- matrix(rnorm(20*10), 20, 10) 22 | 23 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 24 | gender = sample(c("M", "F"), 10, replace = TRUE)) 25 | 26 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 27 | pos = runif(20, 0, 1000000)) 28 | 29 | X <- annmatrix(x, rowdata, coldata) 30 | 31 | # transposes the main matrix along with row and column annotations 32 | Xt <- t(X) 33 | 34 | print(X) 35 | print(Xt) 36 | 37 | X@chr 38 | Xt$chr 39 | 40 | } 41 | \author{ 42 | Karolis Koncevičius 43 | } 44 | -------------------------------------------------------------------------------- /R/transpose.r: -------------------------------------------------------------------------------- 1 | #' Transposing annmatrix Objects 2 | #' 3 | #' Transpose annmatrix along with the associated row and column annotations. 4 | #' 5 | #' @param x annmatrix object. 6 | #' 7 | #' @return transposed annmatrix object with appropriately adjusted row and column annotations. 8 | #' 9 | #' @examples 10 | #' # construct annmatrix object 11 | #' x <- matrix(rnorm(20*10), 20, 10) 12 | #' 13 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 14 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 15 | #' 16 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 17 | #' pos = runif(20, 0, 1000000)) 18 | #' 19 | #' X <- annmatrix(x, rowdata, coldata) 20 | #' 21 | #' # transposes the main matrix along with row and column annotations 22 | #' Xt <- t(X) 23 | #' 24 | #' print(X) 25 | #' print(Xt) 26 | #' 27 | #' X@chr 28 | #' Xt$chr 29 | #' 30 | #' @author Karolis Koncevičius 31 | #' @name transpose 32 | #' @export 33 | t.annmatrix <- function(x) { 34 | x <- t.default(x) 35 | attnames <- names(attributes(x)) 36 | attnames[match(c(".annmatrix.rann", ".annmatrix.cann"), attnames)] <- c(".annmatrix.cann", ".annmatrix.rann") 37 | names(attributes(x)) <- attnames 38 | x 39 | } 40 | 41 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Package: annmatrix 2 | ================== 3 | 4 | Version 0.1.2 [2023-05-14] 5 | 6 | BUG FIXES: 7 | 8 | * The class of 'annmatrix' objects now also include "matrix" and "array" fixing a bug where matrix-generic methods like unique() treated annmatrix like a vector. 9 | * 'stack(X)' now works without errors when X contains duplicated row names and/or duplicated column names. 10 | 11 | NEW FEATURES: 12 | 13 | * 'scale()' now preserves the annmatrix class. 14 | * 'rbind()' and 'cbind()' now preserve the annmatrix class and combine annotations. 15 | 16 | CHANGES: 17 | 18 | * annmatrix() constructor no longer tries to set celver annotation names when 'rann' and 'cann' arguments are passed as vectors. 19 | 20 | DOCUMENTATION: 21 | 22 | * Added information about return values for all .Rd files (as requested for submission to CRAN). 23 | 24 | INTERNAL: 25 | 26 | * Added unit tests for annmatrix constructor 27 | 28 | 29 | Version: 0.1.1 [2023-05-01] 30 | 31 | NEW FEATURES: 32 | 33 | * 'as.annmatrix()' converts various objects to 'annmatrix' (for now only works on matrices). 34 | * 'stack()' turns annmatrix object into a long format data frame. 35 | 36 | DOCUMENTATION: 37 | 38 | * Simplified the documentation of 'help(annmatrix)' by moving conversion and subsetting methods to another place. 39 | 40 | 41 | Version: 0.1.0 [2023-04-29] 42 | 43 | NEW FEATURES: 44 | 45 | * Created 46 | 47 | -------------------------------------------------------------------------------- /man/matrixgenerics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/matrixgenerics.r 3 | \name{matrixgenerics} 4 | \alias{matrixgenerics} 5 | \alias{\%*\%.annmatrix} 6 | \title{Matrix Generic Functions for annmatrix Class} 7 | \usage{ 8 | \method{\%*\%}{annmatrix}(x, y) 9 | } 10 | \arguments{ 11 | \item{x, y}{numeric or complex matrices or vectors.} 12 | } 13 | \value{ 14 | an object of class 'annmatrix'. 15 | } 16 | \description{ 17 | Matrix cross-product operator implemented for annmatrix class 18 | } 19 | \details{ 20 | The resulting matrix will be the same as a product between two regular matrices. 21 | If present annmatrix row annotations will be carried over from the first matrix \code{x} 22 | while the annotations for rows will be carried over from the second matrix \code{y}. 23 | } 24 | \examples{ 25 | # construct annmatrix object 26 | x <- matrix(rnorm(20*10), 20, 10) 27 | 28 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 29 | gender = sample(c("M", "F"), 10, replace = TRUE)) 30 | 31 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 32 | pos = runif(20, 0, 1000000)) 33 | 34 | X <- annmatrix(x, rowdata, coldata) 35 | 36 | res <- 1:20 \%*\% X 37 | res$group 38 | 39 | res <- X \%*\% 1:10 40 | res@chr 41 | 42 | res <- t(X) \%*\% X 43 | res@group 44 | res$group 45 | 46 | } 47 | \author{ 48 | Karolis Koncevičius 49 | } 50 | -------------------------------------------------------------------------------- /R/matrixgenerics.r: -------------------------------------------------------------------------------- 1 | #' Matrix Generic Functions for annmatrix Class 2 | #' 3 | #' Matrix cross-product operator implemented for annmatrix class 4 | #' 5 | #' The resulting matrix will be the same as a product between two regular matrices. 6 | #' If present annmatrix row annotations will be carried over from the first matrix \code{x} 7 | #' while the annotations for rows will be carried over from the second matrix \code{y}. 8 | #' 9 | #' @param x,y numeric or complex matrices or vectors. 10 | #' 11 | #' @return an object of class 'annmatrix'. 12 | #' 13 | #' @examples 14 | #' # construct annmatrix object 15 | #' x <- matrix(rnorm(20*10), 20, 10) 16 | #' 17 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 18 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 19 | #' 20 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 21 | #' pos = runif(20, 0, 1000000)) 22 | #' 23 | #' X <- annmatrix(x, rowdata, coldata) 24 | #' 25 | #' res <- 1:20 %*% X 26 | #' res$group 27 | #' 28 | #' res <- X %*% 1:10 29 | #' res@chr 30 | #' 31 | #' res <- t(X) %*% X 32 | #' res@group 33 | #' res$group 34 | #' 35 | #' @author Karolis Koncevičius 36 | #' @name matrixgenerics 37 | #' @export 38 | `%*%.annmatrix` <- function(x, y) { 39 | 40 | x <- unclass(x) 41 | y <- unclass(y) 42 | result <- x %*% y 43 | 44 | rann <- attr(x, ".annmatrix.rann") 45 | cann <- attr(y, ".annmatrix.cann") 46 | 47 | annmatrix(result, rann, cann) 48 | } 49 | -------------------------------------------------------------------------------- /tests/zz-check.r: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Brodie Gaslam 2 | 3 | # This file is part of "aammrtf - An Almost Most Minimal R Test Framework" 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 or 3 of the License. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # Go to for a copy of the license. 15 | 16 | flist <- function(x, y) paste0(x, paste0("'", basename(y), "'", collapse=", ")) 17 | report <- function(x) {writeLines(character(13)); stop(x, call.=FALSE)} 18 | 19 | outs <- list.files(pattern="\\.Rout$") 20 | noascii <- which(lengths(lapply(outs, tools::showNonASCIIfile)) > 0) 21 | if(length(noascii)) warning(flist("Outputs with non-ASCII:\n", outs[noascii])) 22 | 23 | tar <- list.files(pattern='\\.Rout\\.save$', full.names=TRUE) 24 | cur <- file.path(dirname(tar), sub('\\.save$', '', basename(tar))) 25 | awol <- !file.exists(cur) 26 | 27 | if(any(awol)) report(flist(".Rout files missing (failed?):\n", cur[awol])) 28 | 29 | diffs <- Map(tools::Rdiff, tar[!awol], cur[!awol], useDiff=TRUE, Log=TRUE) 30 | diffs <- vapply(diffs, '[[', 1, 'status') 31 | if(any(!!diffs)) report(flist("Test output differences:\n", cur[!!diffs])) 32 | -------------------------------------------------------------------------------- /R/scale.r: -------------------------------------------------------------------------------- 1 | #' Scaling and Centering of annmatrix Objects 2 | #' 3 | #' Centers and scales the columns of an annmatrix object. 4 | #' 5 | #' Behaves exactly as \code{scale} on a regular matrix with the preservation of 'annmatrix' class being the only difference. 6 | #' 7 | #' @param x annmatrix object. 8 | #' @param center either a logical value or a numeric vector of length equal to the number of columns of 'x' (default is TRUE). 9 | #' @param scale either a logical value or a numeric vector of length equal to the number of columns of 'x' (default is TRUE). 10 | #' 11 | #' @return The centered and/or scaled annmatrix object with additional attributes "scaled:center" and "scaled:scale" holding the numbers used for centering and scaling of each column. 12 | #' 13 | #' @seealso \code{scale.default} 14 | #' 15 | #' @examples 16 | #' # construct annmatrix object 17 | #' x <- matrix(rnorm(20*10), 20, 10) 18 | #' 19 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 20 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 21 | #' 22 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 23 | #' pos = runif(20, 0, 1000000)) 24 | #' 25 | #' X <- annmatrix(x, rowdata, coldata) 26 | #' 27 | #' scale(X) 28 | #' scale(X, center = colMeans(X)) 29 | #' 30 | #' @author Karolis Koncevičius 31 | #' @name scale 32 | #' @export 33 | scale.annmatrix <- function(x, center = TRUE, scale = TRUE) { 34 | annmatrix(NextMethod(), attr(x, ".annmatrix.rann"), attr(x, ".annmatrix.cann")) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /man/scale.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/scale.r 3 | \name{scale} 4 | \alias{scale} 5 | \alias{scale.annmatrix} 6 | \title{Scaling and Centering of annmatrix Objects} 7 | \usage{ 8 | \method{scale}{annmatrix}(x, center = TRUE, scale = TRUE) 9 | } 10 | \arguments{ 11 | \item{x}{annmatrix object.} 12 | 13 | \item{center}{either a logical value or a numeric vector of length equal to the number of columns of 'x' (default is TRUE).} 14 | 15 | \item{scale}{either a logical value or a numeric vector of length equal to the number of columns of 'x' (default is TRUE).} 16 | } 17 | \value{ 18 | The centered and/or scaled annmatrix object with additional attributes "scaled:center" and "scaled:scale" holding the numbers used for centering and scaling of each column. 19 | } 20 | \description{ 21 | Centers and scales the columns of an annmatrix object. 22 | } 23 | \details{ 24 | Behaves exactly as \code{scale} on a regular matrix with the preservation of 'annmatrix' class being the only difference. 25 | } 26 | \examples{ 27 | # construct annmatrix object 28 | x <- matrix(rnorm(20*10), 20, 10) 29 | 30 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 31 | gender = sample(c("M", "F"), 10, replace = TRUE)) 32 | 33 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 34 | pos = runif(20, 0, 1000000)) 35 | 36 | X <- annmatrix(x, rowdata, coldata) 37 | 38 | scale(X) 39 | scale(X, center = colMeans(X)) 40 | 41 | } 42 | \seealso{ 43 | \code{scale.default} 44 | } 45 | \author{ 46 | Karolis Koncevičius 47 | } 48 | -------------------------------------------------------------------------------- /man/print.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/print.r 3 | \name{print} 4 | \alias{print} 5 | \alias{print.annmatrix} 6 | \title{Print annmatrix Object} 7 | \usage{ 8 | \method{print}{annmatrix}(x, nrow = 5, ncol = 5, digits = getOption("digits"), ...) 9 | } 10 | \arguments{ 11 | \item{x}{a matrix.} 12 | 13 | \item{nrow}{number of rows to display (default is 5).} 14 | 15 | \item{ncol}{number of columns to display (default is 5).} 16 | 17 | \item{digits}{number of digits to display (default set to getOptions("digits")).} 18 | 19 | \item{...}{further arguments passed to or from methods.} 20 | } 21 | \value{ 22 | invisibly returns annmatrix object. 23 | } 24 | \description{ 25 | Functions that print an annmatrix object 26 | } 27 | \details{ 28 | annmatrix objects are printed in a shortened form (5 rows and 5 columns by default). 29 | In addition the function displays information about available meta-data for rows and columns. 30 | } 31 | \examples{ 32 | # construct annmatrix object 33 | x <- matrix(rnorm(20*10), 20, 10) 34 | 35 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 36 | gender = sample(c("M", "F"), 10, replace = TRUE)) 37 | 38 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 39 | pos = runif(20, 0, 1000000)) 40 | 41 | X <- annmatrix(x, rowdata, coldata) 42 | 43 | print(X) 44 | print(X, 10, 5) 45 | print(X, 2, 2) 46 | 47 | # also works with a list-based matrix 48 | x <- matrix(list(mtcars, iris3, USAccDeaths, rivers), ncol=2) 49 | print(x) 50 | X <- annmatrix(x) 51 | print(X) 52 | 53 | } 54 | \author{ 55 | Karolis Koncevičius 56 | } 57 | -------------------------------------------------------------------------------- /R/stack.r: -------------------------------------------------------------------------------- 1 | #' Stack an annmatrix object 2 | #' 3 | #' Turns annmatrix into a data frame by transforming the matrix along with row and column annotations into separate data frame columns. 4 | #' 5 | #' @param x annmatrix object. 6 | #' @param ... further arguments passed to or from methods. 7 | #' 8 | #' @return transposed annmatrix object 9 | #' 10 | #' @examples 11 | #' # construct annmatrix object 12 | #' x <- matrix(rnorm(20*10), 20, 10) 13 | #' 14 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 15 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 16 | #' 17 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 18 | #' pos = runif(20, 0, 1000000)) 19 | #' 20 | #' X <- annmatrix(x, rowdata, coldata) 21 | #' 22 | #' # stack all information into a long-format data.frame 23 | #' Y <- stack(X) 24 | #' 25 | #' @author Karolis Koncevičius 26 | #' @name stack 27 | #' @export 28 | stack.annmatrix <- function(x, ...) { 29 | rann <- attr(x, ".annmatrix.rann") 30 | cann <- attr(x, ".annmatrix.cann") 31 | 32 | rnx <- rownames(x) 33 | if (is.null(rnx)) { 34 | rnx <- 1:nrow(x) 35 | } else if (is.character(rnx)) { 36 | rnx <- make.unique(rnx) 37 | } 38 | 39 | cnx <- colnames(x) 40 | if (is.null(cnx)) { 41 | cnx <- 1:ncol(x) 42 | } else if (is.character(cnx)) { 43 | cnx <- make.unique(cnx) 44 | } 45 | 46 | data.frame(value = as.numeric(x), 47 | rann[rep(seq_len(nrow(rann)), nrow(cann)), , drop = FALSE], 48 | cann[rep(seq_len(nrow(cann)), each = nrow(rann)), , drop = FALSE], 49 | row.names = paste(rep(rnx, nrow(cann)), rep(cnx, each = nrow(rann)), sep=":"), 50 | ... 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /man/convert.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/convert.r 3 | \name{convert} 4 | \alias{convert} 5 | \alias{as.annmatrix} 6 | \alias{as.annmatrix.default} 7 | \alias{as.annmatrix.matrix} 8 | \alias{as.matrix.annmatrix} 9 | \alias{is.annmatrix} 10 | \title{Convert annmatrix Objects to and from Other Types} 11 | \usage{ 12 | as.annmatrix(x) 13 | 14 | \method{as.annmatrix}{default}(x) 15 | 16 | \method{as.annmatrix}{matrix}(x) 17 | 18 | \method{as.matrix}{annmatrix}(x, ...) 19 | 20 | is.annmatrix(x) 21 | } 22 | \arguments{ 23 | \item{x}{an R object.} 24 | 25 | \item{...}{additional arguments to be passed to or from methods.} 26 | } 27 | \value{ 28 | \code{is.annmatrix} returns TRUE if object is of class 'annmatrix' and FALSE otherwise. 29 | \code{as.annmatrix} methods return an object of class 'annmatrix'. 30 | \code{as.matrix} returns a regular matrix. 31 | } 32 | \description{ 33 | Methods for turning R objects to class annmatrix and vice versa. 34 | } 35 | \details{ 36 | \code{as.annmatrix} will attempt to convert an object to annmatrix. 37 | 38 | \code{as.matrix} will turn an \code{annmatrix} object into a regular matrix. 39 | 40 | \code{is.annmatrix} checks if the object is an instance of \code{annmatrix}. 41 | } 42 | \examples{ 43 | # construct annmatrix object 44 | x <- matrix(rnorm(20*10), 20, 10) 45 | X <- as.annmatrix(x) 46 | 47 | X$group <- rep(c("case", "control"), each = 5) 48 | X$gender <- sample(c("M", "F"), 10, replace = TRUE) 49 | X@chr <- sample(c("chr1", "chr2"), 20, replace = TRUE) 50 | X@pos <- runif(20, 0, 1000000) 51 | 52 | is.matrix(x) 53 | is.matrix(X) 54 | 55 | is.annmatrix(x) 56 | is.annmatrix(X) 57 | 58 | as.matrix(X) 59 | 60 | } 61 | \author{ 62 | Karolis Koncevičius 63 | } 64 | -------------------------------------------------------------------------------- /R/groupgenerics.r: -------------------------------------------------------------------------------- 1 | #' Group Generic Functions for annmatrix Class 2 | #' 3 | #' The functions listed here work under the hood and are almost never called by the user. 4 | #' 5 | #' @param e1,e2 annmatrix objects. 6 | #' @param x,y The objects being dispatched on by the group generic. 7 | #' @param mx,my The methods found for objects 'x' and 'y'. 8 | #' @param cl The call to the group generic. 9 | #' @param reverse A logical value indicating whether 'x' and 'y' are reversed from the way they were supplied to the generic. 10 | #' 11 | #' @return An object of class 'annmatrix'. 12 | #' 13 | #' @author Karolis Koncevičius 14 | #' @name groupgenerics 15 | #' @export 16 | Ops.annmatrix <- function(e1, e2) { 17 | 18 | if (is.annmatrix(e1)) { 19 | myclass <- setdiff(class(e1), "annmatrix") 20 | pairclass <- oldClass(e2) 21 | rann <- attr(e1, ".annmatrix.rann") 22 | cann <- attr(e1, ".annmatrix.cann") 23 | e1 <- as.matrix(e1) 24 | } else if (is.annmatrix(e2)) { 25 | myclass <- setdiff(class(e2), "annmatrix") 26 | pairclass <- oldClass(e1) 27 | rann <- attr(e2, ".annmatrix.rann") 28 | cann <- attr(e2, ".annmatrix.cann") 29 | e2 <- as.matrix(e2) 30 | } 31 | 32 | result <- callGeneric(e1, e2) 33 | 34 | # Only return annmatrix if there is no specific method defined for this operations from the pair class 35 | # With help from Mikael Jagan on Stack Overflow: https://stackoverflow.com/a/75953638/1953718 36 | if (is.null(pairclass) || 37 | (all(is.na(match(paste0("Ops.", pairclass), .S3methods("Ops")))) && 38 | all(is.na(match(paste0(.Generic, ".", pairclass), .S3methods(.Generic)))))) { 39 | result <- structure(result, class = c("annmatrix", myclass), .annmatrix.rann = rann, .annmatrix.cann = cann) 40 | } 41 | 42 | result 43 | } 44 | 45 | #' @rdname groupgenerics 46 | #' @export 47 | chooseOpsMethod.annmatrix <- function(x, y, mx, my, cl, reverse) { 48 | TRUE 49 | } 50 | -------------------------------------------------------------------------------- /man/subset.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/subset.r 3 | \name{subset} 4 | \alias{subset} 5 | \alias{[.annmatrix} 6 | \title{Subset annmatrix Objects} 7 | \usage{ 8 | \method{[}{annmatrix}(x, i, j, ..., drop = TRUE) 9 | } 10 | \arguments{ 11 | \item{x}{an R object.} 12 | 13 | \item{i}{subset for rows.} 14 | 15 | \item{j}{subset for columns.} 16 | 17 | \item{...}{further arguments passed to or from methods.} 18 | 19 | \item{drop}{if TRUE (default) subsetting a single row or column will returned a vector.} 20 | } 21 | \value{ 22 | A selected subset of the provided 'annmatrix' object. 23 | } 24 | \description{ 25 | Methods for selecting a set of rows or columns from annmatrix while keeping 26 | the associated annotations intact. 27 | } 28 | \details{ 29 | \code{X[i,j]} returns a selected subset of annmatrix object. Row and column 30 | annotations are preserved and subsetted where needed. In the special case 31 | when only one column or row is selected in order to be consistent with the 32 | \code{matrix} behavior the dimensions of matrix are dropped and a vector is 33 | returned. Just like in the case of matrices the additional argument 34 | \code{drop=FALSE} can be provided in order to return a proper matrix 35 | instead. 36 | } 37 | \examples{ 38 | # construct annmatrix object 39 | x <- matrix(rnorm(20*10), 20, 10) 40 | 41 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 42 | gender = sample(c("M", "F"), 10, replace = TRUE)) 43 | 44 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 45 | pos = runif(20, 0, 1000000)) 46 | 47 | X <- annmatrix(x, rowdata, coldata) 48 | 49 | # annotations are preserved after subsetting 50 | Y <- X[X@chr == "chr1", X$group == "case"] 51 | Y@chr 52 | Y$'' 53 | 54 | Y[, 1] 55 | Y[, 1, drop = FALSE] 56 | 57 | } 58 | \seealso{ 59 | \code{as.annmatrix} 60 | } 61 | \author{ 62 | Karolis Koncevičius 63 | } 64 | -------------------------------------------------------------------------------- /R/convert.r: -------------------------------------------------------------------------------- 1 | #' Convert annmatrix Objects to and from Other Types 2 | #' 3 | #' Methods for turning R objects to class annmatrix and vice versa. 4 | #' 5 | #' \code{as.annmatrix} will attempt to convert an object to annmatrix. 6 | #' 7 | #' \code{as.matrix} will turn an \code{annmatrix} object into a regular matrix. 8 | #' 9 | #' \code{is.annmatrix} checks if the object is an instance of \code{annmatrix}. 10 | #' 11 | #' @param x an R object. 12 | #' @param ... additional arguments to be passed to or from methods. 13 | #' 14 | #' @return \code{is.annmatrix} returns TRUE if object is of class 'annmatrix' and FALSE otherwise. 15 | #' \code{as.annmatrix} methods return an object of class 'annmatrix'. 16 | #' \code{as.matrix} returns a regular matrix. 17 | #' 18 | #' @examples 19 | #' # construct annmatrix object 20 | #' x <- matrix(rnorm(20*10), 20, 10) 21 | #' X <- as.annmatrix(x) 22 | #' 23 | #' X$group <- rep(c("case", "control"), each = 5) 24 | #' X$gender <- sample(c("M", "F"), 10, replace = TRUE) 25 | #' X@chr <- sample(c("chr1", "chr2"), 20, replace = TRUE) 26 | #' X@pos <- runif(20, 0, 1000000) 27 | #' 28 | #' is.matrix(x) 29 | #' is.matrix(X) 30 | #' 31 | #' is.annmatrix(x) 32 | #' is.annmatrix(X) 33 | #' 34 | #' as.matrix(X) 35 | #' 36 | #' @author Karolis Koncevičius 37 | #' @name convert 38 | #' @export 39 | as.annmatrix <- function(x) { 40 | UseMethod("as.annmatrix") 41 | } 42 | 43 | #' @rdname convert 44 | #' @export 45 | as.annmatrix.default <- function(x) { 46 | if(is.null(x)) { 47 | annmatrix() 48 | } else { 49 | stop("no applicable method for converting an object of class '", class(x)[1], "' to annmatrix") 50 | } 51 | } 52 | 53 | #' @rdname convert 54 | #' @export 55 | as.annmatrix.matrix <- function(x) { 56 | annmatrix(x) 57 | } 58 | 59 | #' @rdname convert 60 | #' @export 61 | as.matrix.annmatrix <- function(x, ...) { 62 | attr(x, ".annmatrix.rann") <- NULL 63 | attr(x, ".annmatrix.cann") <- NULL 64 | unclass(x) 65 | } 66 | 67 | #' @rdname convert 68 | #' @export 69 | is.annmatrix <- function(x) { 70 | inherits(x, "annmatrix") 71 | } 72 | 73 | -------------------------------------------------------------------------------- /man/pca.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pca.r 3 | \name{pca} 4 | \alias{pca} 5 | \alias{prcomp.annmatrix} 6 | \title{Principal Component Analysis for annmatrix Objects} 7 | \usage{ 8 | \method{prcomp}{annmatrix}( 9 | x, 10 | retx = TRUE, 11 | center = TRUE, 12 | scale. = FALSE, 13 | tol = NULL, 14 | rank. = NULL, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{x}{annmatrix object.} 20 | 21 | \item{retx}{logical indicator whether the rotated variables should be returned (defaults to TRUE).} 22 | 23 | \item{center}{logical value indicating whether variables should be centered (defaults to TRUE).} 24 | 25 | \item{scale.}{logical value indicating whether variables should be scaled (defaults to FALSE).} 26 | 27 | \item{tol}{tolerance value indicating magnitude below which components will be omitted.} 28 | 29 | \item{rank.}{number specifying the maximal rank (max number of principal components to be used).} 30 | 31 | \item{...}{arguments passed to or from other methods.} 32 | } 33 | \value{ 34 | prcom object with rotation and x matrices turned into annmatrix 35 | } 36 | \description{ 37 | Performs principal component analysis on annmatrix objects and preserves row and column annotations in scores and loadings. 38 | } 39 | \details{ 40 | The resulting loadings ('rotation') and scores ('x') are turned into annmatrix objects with additional row and column annotations. 41 | Row annotations are copied from the original input matrix while column annotations contain computed information about each principal component. 42 | The added information contains: 1) principal component number, 2) standard deviation, 3) variance, 4) fraction of variance explained. 43 | } 44 | \examples{ 45 | # construct annmatrix object 46 | x <- matrix(rnorm(20*10), 20, 10) 47 | 48 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 49 | gender = sample(c("M", "F"), 10, replace = TRUE)) 50 | 51 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 52 | pos = runif(20, 0, 1000000)) 53 | 54 | X <- annmatrix(x, rowdata, coldata) 55 | 56 | pca <- prcomp(t(X)) 57 | pca$rotation 58 | pca$rotation$'' 59 | 60 | scores <- t(pca$rotation) \%*\% X 61 | scores@var_explained 62 | scores$gender 63 | 64 | } 65 | \author{ 66 | Karolis Koncevičius 67 | } 68 | -------------------------------------------------------------------------------- /R/subset.r: -------------------------------------------------------------------------------- 1 | #' Subset annmatrix Objects 2 | #' 3 | #' Methods for selecting a set of rows or columns from annmatrix while keeping 4 | #' the associated annotations intact. 5 | #' 6 | #' \code{X[i,j]} returns a selected subset of annmatrix object. Row and column 7 | #' annotations are preserved and subsetted where needed. In the special case 8 | #' when only one column or row is selected in order to be consistent with the 9 | #' \code{matrix} behavior the dimensions of matrix are dropped and a vector is 10 | #' returned. Just like in the case of matrices the additional argument 11 | #' \code{drop=FALSE} can be provided in order to return a proper matrix 12 | #' instead. 13 | #' 14 | #' @param x an R object. 15 | #' @param i subset for rows. 16 | #' @param j subset for columns. 17 | #' @param drop if TRUE (default) subsetting a single row or column will returned a vector. 18 | #' @param ... further arguments passed to or from methods. 19 | #' 20 | #' @return A selected subset of the provided 'annmatrix' object. 21 | #' 22 | #' @seealso \code{as.annmatrix} 23 | #' 24 | #' @examples 25 | #' # construct annmatrix object 26 | #' x <- matrix(rnorm(20*10), 20, 10) 27 | #' 28 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 29 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 30 | #' 31 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 32 | #' pos = runif(20, 0, 1000000)) 33 | #' 34 | #' X <- annmatrix(x, rowdata, coldata) 35 | #' 36 | #' # annotations are preserved after subsetting 37 | #' Y <- X[X@chr == "chr1", X$group == "case"] 38 | #' Y@chr 39 | #' Y$'' 40 | #' 41 | #' Y[, 1] 42 | #' Y[, 1, drop = FALSE] 43 | #' 44 | #' @author Karolis Koncevičius 45 | #' @name subset 46 | #' @export 47 | `[.annmatrix` <- function(x, i, j, ..., drop = TRUE) { 48 | mat <- NextMethod("[") 49 | 50 | if (is.matrix(mat)) { 51 | 52 | if (missing(i)) { 53 | attr(mat, ".annmatrix.rann") <- attr(x, ".annmatrix.rann") 54 | } else { 55 | if (is.character(i)) i <- match(i, rownames(x)) 56 | attr(mat, ".annmatrix.rann") <- attr(x, ".annmatrix.rann")[i,,drop = FALSE] 57 | } 58 | 59 | if (missing(j)) { 60 | attr(mat, ".annmatrix.cann") <- attr(x, ".annmatrix.cann") 61 | } else { 62 | if (is.character(j)) j <- match(j, colnames(x)) 63 | attr(mat, ".annmatrix.cann") <- attr(x, ".annmatrix.cann")[j,,drop = FALSE] 64 | } 65 | 66 | class(mat) <- append("annmatrix", class(mat)) 67 | } 68 | 69 | mat 70 | } 71 | -------------------------------------------------------------------------------- /R/pca.r: -------------------------------------------------------------------------------- 1 | #' Principal Component Analysis for annmatrix Objects 2 | #' 3 | #' Performs principal component analysis on annmatrix objects and preserves row and column annotations in scores and loadings. 4 | #' 5 | #' The resulting loadings ('rotation') and scores ('x') are turned into annmatrix objects with additional row and column annotations. 6 | #' Row annotations are copied from the original input matrix while column annotations contain computed information about each principal component. 7 | #' The added information contains: 1) principal component number, 2) standard deviation, 3) variance, 4) fraction of variance explained. 8 | #' 9 | #' @param x annmatrix object. 10 | #' @param retx logical indicator whether the rotated variables should be returned (defaults to TRUE). 11 | #' @param center logical value indicating whether variables should be centered (defaults to TRUE). 12 | #' @param scale. logical value indicating whether variables should be scaled (defaults to FALSE). 13 | #' @param tol tolerance value indicating magnitude below which components will be omitted. 14 | #' @param rank. number specifying the maximal rank (max number of principal components to be used). 15 | #' @param ... arguments passed to or from other methods. 16 | #' 17 | #' @return prcom object with rotation and x matrices turned into annmatrix 18 | #' 19 | #' @examples 20 | #' # construct annmatrix object 21 | #' x <- matrix(rnorm(20*10), 20, 10) 22 | #' 23 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 24 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 25 | #' 26 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 27 | #' pos = runif(20, 0, 1000000)) 28 | #' 29 | #' X <- annmatrix(x, rowdata, coldata) 30 | #' 31 | #' pca <- prcomp(t(X)) 32 | #' pca$rotation 33 | #' pca$rotation$'' 34 | #' 35 | #' scores <- t(pca$rotation) %*% X 36 | #' scores@var_explained 37 | #' scores$gender 38 | #' 39 | #' @author Karolis Koncevičius 40 | #' @name pca 41 | #' @export 42 | prcomp.annmatrix <- function(x, retx = TRUE, center = TRUE, scale. = FALSE, tol = NULL, rank. = NULL, ...) { 43 | res <- NextMethod(as.matrix(x), retx = retx, center = center, scale. = scale., tol = tol, rank. = rank., ...) 44 | info <- data.frame(pc = colnames(res$rotation), 45 | sd = res$sdev, 46 | var = res$sdev^2, 47 | var_explained = res$sdev^2 / sum(res$sdev^2), 48 | row.names = colnames(res$rotation) 49 | ) 50 | res$rotation <- annmatrix(res$rotation, rann = attr(x, ".annmatrix.cann"), cann = info) 51 | if(retx) { 52 | res$x <- annmatrix(res$x, rann = attr(x, ".annmatrix.rann"), cann = info) 53 | } 54 | res 55 | } 56 | -------------------------------------------------------------------------------- /man/bind.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/bind.r 3 | \name{bind} 4 | \alias{bind} 5 | \alias{rbind.annmatrix} 6 | \alias{cbind.annmatrix} 7 | \title{Bind several annmatrix Objects Together} 8 | \usage{ 9 | \method{rbind}{annmatrix}(...) 10 | 11 | \method{cbind}{annmatrix}(...) 12 | } 13 | \arguments{ 14 | \item{...}{(generalized) vector or matrix objects} 15 | } 16 | \value{ 17 | a single annmatrix object resulting from binding all the inputs together 18 | } 19 | \description{ 20 | Implementation of \code{rbind} and \code{cbind} methods for annmatrix objects. 21 | } 22 | \details{ 23 | All the inputs are bound together in exact same way as if all the annmatrix objects were regular matrices. 24 | Then, the row and column annotations of the supplied annmatrix objects are merged together. 25 | Missing annotations are filled in using 'NA' values. 26 | 27 | For demonstration purposes only \code{rbind} will be described with \code{cbind} behaving accordingly. 28 | 29 | 1) Matrix. 30 | The obtained matrix will be exactly the same as calling \code{rbind} with all annmatrix objects replaced by regular matrices. 31 | 32 | 2) Column annotations. 33 | When \code{rbind} is called the matrices are all assumed to have the same set of columns. 34 | Hence, the column annotations are assumed to be shared between all provided annmatrix objects. 35 | Thus, in order to retain all possible column annotations, they are merged together. 36 | This way any column annotation field present in at least one of the provided annmatrix objects will be present in the final result. 37 | In case of conflicts, when the same annotation field is present in multiple annmatrix objects but contains different values, the first occuring instance is selected and an appropriate warning is displayed. 38 | Non-annmatrix objects are assumed to share the column annotations present in supplied annmatrix objects. 39 | 40 | 3) Row annotations. 41 | When \code{rbind} is called the matrices are assumed to have a separate unique set of rows. 42 | Hence no conflicts between annotation values are possible for row annotations. 43 | In order to retain all possible row annotations, row annotations are merged together. 44 | Thus, the resulting row annotation data frame will have as many fields as there were unique row annotation fields among all the provided annmatrix objects. 45 | Unlike with column annotations, if a particular annmatrix only had a subset of the final collection of annotation fields, then the missing fields are added and the annotation is filled with NA values. 46 | All the rows associated with non-annmatrix objects will have missing (NA) values for all the annotation fields. 47 | } 48 | \examples{ 49 | # construct annmatrix object 50 | x <- matrix(rnorm(20*10), 20, 10) 51 | 52 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 53 | gender = sample(c("M", "F"), 10, replace = TRUE)) 54 | 55 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 56 | pos = runif(20, 0, 1000000)) 57 | 58 | X <- annmatrix(x, rowdata, coldata) 59 | 60 | X1 <- X[1:10,] 61 | X2 <- X[11:20,] 62 | all.equal(X, rbind(X1, X2)) 63 | 64 | X1 <- X[,1:5] 65 | X2 <- X[,6:10] 66 | all.equal(X, cbind(X1, X2)) 67 | 68 | X11 <- X[1:10, 1:5] 69 | X21 <- X[11:20, 1:5] 70 | X12 <- X[1:10, 6:10] 71 | X22 <- X[11:20, 6:10] 72 | all.equal(X, cbind(rbind(X11, X21), rbind(X12, X22))) 73 | 74 | } 75 | \author{ 76 | Karolis Koncevičius 77 | } 78 | -------------------------------------------------------------------------------- /R/print.r: -------------------------------------------------------------------------------- 1 | #' Print annmatrix Object 2 | #' 3 | #' Functions that print an annmatrix object 4 | #' 5 | #' annmatrix objects are printed in a shortened form (5 rows and 5 columns by default). 6 | #' In addition the function displays information about available meta-data for rows and columns. 7 | #' 8 | #' @param x a matrix. 9 | #' @param nrow number of rows to display (default is 5). 10 | #' @param ncol number of columns to display (default is 5). 11 | #' @param digits number of digits to display (default set to getOptions("digits")). 12 | #' @param ... further arguments passed to or from methods. 13 | #' 14 | #' @return invisibly returns annmatrix object. 15 | #' 16 | #' @examples 17 | #' # construct annmatrix object 18 | #' x <- matrix(rnorm(20*10), 20, 10) 19 | #' 20 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 21 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 22 | #' 23 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 24 | #' pos = runif(20, 0, 1000000)) 25 | #' 26 | #' X <- annmatrix(x, rowdata, coldata) 27 | #' 28 | #' print(X) 29 | #' print(X, 10, 5) 30 | #' print(X, 2, 2) 31 | #' 32 | #' # also works with a list-based matrix 33 | #' x <- matrix(list(mtcars, iris3, USAccDeaths, rivers), ncol=2) 34 | #' print(x) 35 | #' X <- annmatrix(x) 36 | #' print(X) 37 | #' 38 | #' @author Karolis Koncevičius 39 | #' @name print 40 | #' @export 41 | print.annmatrix <- function(x, nrow = 5, ncol = 5, digits = getOption("digits"), ...) { 42 | rinds <- seq_len(min(nrow, nrow(x))) 43 | cinds <- seq_len(min(ncol, ncol(x))) 44 | 45 | if (nrow(x) > nrow) { 46 | rinds <- c(rinds[-length(rinds)], NA, nrow(x)) 47 | } 48 | if (ncol(x) > ncol) { 49 | cinds <- c(cinds[-length(cinds)], NA, ncol(x)) 50 | } 51 | 52 | mat <- as.matrix(x[rinds, cinds, drop = FALSE]) 53 | 54 | if (is.null(rownames(mat))) { 55 | rownames(mat) <- ifelse(is.na(rinds), NA, paste0("[", rinds, ",]")) 56 | } 57 | if (is.null(colnames(mat))) { 58 | colnames(mat) <- ifelse(is.na(cinds), NA, paste0("[,", cinds, "]")) 59 | } 60 | 61 | if (is.list(mat)) { 62 | dims <- lapply(mat, function(x) ifelse(is.null(dim(x)), length(x), dim(x))) 63 | classes <- sapply(mat, function(x) class(x)[1]) 64 | classes <- paste0(classes, "<", sapply(dims, paste, collapse = ","), ">") 65 | mat <- matrix(classes, nrow = nrow(mat), ncol = ncol(mat), dimnames = dimnames(mat)) 66 | } else if (is.numeric(mat)) { 67 | mat[!is.na(mat)] <- format(mat[!is.na(mat)], digits = digits) 68 | } else if (is.character(mat)) { 69 | mat[] <- ifelse(is.na(mat), NA, paste0('"', mat, '"')) 70 | } 71 | 72 | width <- max(0, nchar(mat), nchar(colnames(mat)), na.rm = TRUE) 73 | mwidth <- max(0, nchar(mat), na.rm = TRUE) 74 | justify <- ifelse(is.numeric(x) | is.logical(x), "right", "left") 75 | 76 | if(!is.character(x)) { 77 | mat <- format(mat, width = width, justify = justify) 78 | fill <- format(strrep(".", mwidth), width = width, justify = justify) 79 | } else { 80 | fill <- strrep(".", mwidth) 81 | } 82 | 83 | mat[is.na(rinds),] <- fill 84 | mat[,is.na(cinds)] <- fill 85 | 86 | rownames(mat) <- ifelse(is.na(rinds), "", format(rownames(mat), justify = "right")) 87 | colnames(mat) <- ifelse(is.na(cinds), "", format(colnames(mat), width = width, justify = "right")) 88 | 89 | 90 | print.default(mat, quote = FALSE) 91 | cat("\n") 92 | 93 | cat("rann: ") 94 | cat(names(attr(x, ".annmatrix.rann")), sep = ", ") 95 | cat("\n") 96 | 97 | cat("cann: ") 98 | cat(names(attr(x, ".annmatrix.cann")), sep = ", ") 99 | cat("\n") 100 | 101 | invisible(x) 102 | } 103 | 104 | -------------------------------------------------------------------------------- /tests/annmatrix.R: -------------------------------------------------------------------------------- 1 | library(annmatrix) 2 | source("utils/test.r") 3 | 4 | #=================================== ERRORS ==================================== 5 | 6 | test(annmatrix(mean)) # closure cannot be transformed to a matrix 7 | test(annmatrix(environment())) # environment cannot be transformed to a matrix 8 | 9 | test(annmatrix(iris, rann = data.frame())) # incorrect number of row annotations (zero) 10 | test(annmatrix(iris, rann = data.frame(a = 1, b = 2))) # incorrect number of row annotations (one) 11 | test(annmatrix(iris, rann = data.frame(1:3))) # incorrect number of row annotations (too low) 12 | test(annmatrix(iris, rann = data.frame(1:200))) # incorrect number of row annotations (too high) 13 | 14 | test(annmatrix(iris, cann = data.frame())) # incorrect number of column annotations (zero) 15 | test(annmatrix(iris, cann = data.frame(a = 1, b = 2))) # incorrect number of column annotations (one) 16 | test(annmatrix(iris, cann = data.frame(1:3))) # incorrect number of column annotations (too low) 17 | test(annmatrix(iris, cann = data.frame(1:200))) # incorrect number of column annotations (too high) 18 | 19 | test(annmatrix(iris, data.frame(), data.frame())) # when both incorrect - error about row annotations 20 | 21 | 22 | #============================= EMPTY CONSTRUCTORS ============================== 23 | 24 | test(annmatrix()) # empty call results in an empty matrix 25 | test(annmatrix(data.frame(), data.frame())) # allows specifying empty data.frames for annotations 26 | 27 | test(annmatrix(logical())) # empty logical results in zero-row one-column logical 28 | test(annmatrix(integer())) # empty integer results in zero-row one-column integer 29 | test(annmatrix(numeric())) # empty numeric results in zero-row one-column numeric 30 | test(annmatrix(character())) # empty character results in zero-row one-column character 31 | test(annmatrix(complex())) # empty complex results in zero-row one-column complex 32 | test(annmatrix(list())) # empty list results in zero-row one-column list 33 | 34 | x <- matrix(integer(), nrow = 0, ncol = 3) # zero number of rows 35 | test(annmatrix(x)) # sets the correct number of rows and columns 36 | test(annmatrix(x, rann = data.frame())) # allows specifying empty row annotations 37 | test(annmatrix(x, cann = data.frame(a = letters[1:3]))) # allows specifying annotations for columns 38 | 39 | x <- matrix(integer(), nrow = 3, ncol = 0) # zero number of columns 40 | test(annmatrix(x)) # sets the correct number of rows and columns 41 | test(annmatrix(x, cann = data.frame())) # allows specifying empty column annotations 42 | test(annmatrix(x, rann = data.frame(a = letters[1:3]))) # allows specifying annotations for rows 43 | 44 | 45 | #=============================== VARIOUS TYPES ================================= 46 | 47 | test(annmatrix(c(TRUE, FALSE))) # works with logical 48 | test(annmatrix(1:2)) # works with integer 49 | test(annmatrix(1:2/2)) # works with real 50 | test(annmatrix(letters[1:2])) # works with character 51 | test(annmatrix(as.complex(1:2))) # works with complex 52 | test(annmatrix(matrix(1:6, nrow=2))) # works with matrix 53 | test(annmatrix(list(1, 1:2, letters[1:3]))) # works with list 54 | 55 | 56 | #=============================== REGULAR CASES ================================= 57 | 58 | # check for preserving correct result, annotations, and row/column names 59 | x <- matrix(1:6, nrow=2, ncol=3, dimnames=list(letters[1:2], LETTERS[1:3])) 60 | rann <- data.frame(id = 1:2, chr = c("c1", "c2"), pos = c(10, 20)) 61 | cann <- data.frame(name = c("A", "B", "C"), age = c(20, 40, 60)) 62 | 63 | test(annmatrix(x)) # without annotations 64 | test(annmatrix(x, rann = rann)) # only row annotations 65 | test(annmatrix(x, cann = cann)) # only column annotations 66 | test(annmatrix(x, rann = rann, cann = cann)) # both annotations 67 | 68 | # TODO: check for list matrices and list entries in annotations 69 | -------------------------------------------------------------------------------- /man/annmatrix.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/annmatrix.r 3 | \name{annmatrix} 4 | \alias{annmatrix} 5 | \alias{rowanns} 6 | \alias{rowanns<-} 7 | \alias{colanns} 8 | \alias{colanns<-} 9 | \alias{@.annmatrix} 10 | \alias{@<-.annmatrix} 11 | \alias{$.annmatrix} 12 | \alias{$<-.annmatrix} 13 | \title{annmatrix Objects and Basic Functionality} 14 | \usage{ 15 | annmatrix(x, rann, cann) 16 | 17 | rowanns(x, names) 18 | 19 | rowanns(x, names) <- value 20 | 21 | colanns(x, names) 22 | 23 | colanns(x, names) <- value 24 | 25 | `@.annmatrix`(object, name) 26 | 27 | \method{@}{annmatrix}(object, name) <- value 28 | 29 | \method{$}{annmatrix}(x, name) 30 | 31 | \method{$}{annmatrix}(x, name) <- value 32 | } 33 | \arguments{ 34 | \item{x, object}{an R object.} 35 | 36 | \item{rann}{annotation \code{data.frame} for rows of the \code{annmatrix} object.} 37 | 38 | \item{cann}{annotation \code{data.frame} for columns of the \code{annmatrix} object.} 39 | 40 | \item{names}{a character vector of existing row/column annotation names.} 41 | 42 | \item{value}{a value that will be assigned to row/column annotation field.} 43 | 44 | \item{name}{a name of an existing row/column annotation.} 45 | } 46 | \value{ 47 | \code{annmatrix} returns an R object of class 'annmatrix'. 48 | } 49 | \description{ 50 | Annotated matrix is a regular matrix with additional functionality for 51 | attaching persistent information about row and column entries. Annotations 52 | associated with rows and columns are preserved after subsetting, 53 | transposition, and various other matrix-specific operations. 54 | 55 | Intended 'annmatrix' use case is for storing and manipulating genomic 56 | datasets that typically consist of a matrix of measurements (like gene 57 | expression values) as well as annotations about rows (i.e. genomic 58 | locations) and annotations about columns (i.e. meta-data about collected 59 | samples). But \code{annmatrix} objects are also expected be useful in 60 | various other contexts. 61 | } 62 | \details{ 63 | \code{annmatrix()} constructs an annmatrix. The function expects \code{x} to 64 | be a \code{matrix} and \code{rowanns} and \code{colanns} to be of class 65 | \code{data.frame}. If the passed objects are of a different classes they 66 | will be converted via the use of \code{as.matrix} and \code{as.data.frame}. 67 | 68 | \code{rowanns} and \code{colanns} returns the selected field from column and 69 | row annotations respectively. When the selected field is not specified the 70 | whole annotation \code{data.frame} is returned. 71 | 72 | \code{@} and \code{$} are convenience shortcuts for selecting annotations. 73 | \code{X@value} selects an existing column from row annotations while 74 | \code{X$value} selects a column from column annotations. An empty selection 75 | of \code{X@''} and \code{X$''} will return the whole annotation data.frame 76 | for rows and columns respectively. 77 | 78 | \code{rowanns<-} and \code{colanns<-} functions can be used to replace the 79 | column and row annotations respectively. When the selected field is not 80 | specified the whole annotation \code{data.frame} is replaced. 81 | 82 | \code{@<-} and \code{$<-} are convenience shortcuts for the above (see 83 | Examples). A replacement of an empty value - \code{X@'' <- df} and 84 | \code{X$'' <- df} will replace the whole annotation data.frame. 85 | } 86 | \examples{ 87 | # construct annmatrix object 88 | x <- matrix(rnorm(20*10), 20, 10) 89 | 90 | coldata <- data.frame(group = rep(c("case", "control"), each = 5), 91 | gender = sample(c("M", "F"), 10, replace = TRUE)) 92 | 93 | rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 94 | pos = runif(20, 0, 1000000)) 95 | 96 | X <- annmatrix(x, rowdata, coldata) 97 | 98 | is.matrix(x) 99 | is.matrix(X) 100 | 101 | is.annmatrix(x) 102 | is.annmatrix(X) 103 | 104 | # manipulating annotations without using shortcuts 105 | rowanns(X) 106 | colanns(X) 107 | 108 | rowanns(X, "chr") 109 | rowanns(X, "gene") <- letters[1:20] 110 | rowanns(X, c("chr", "gene")) 111 | rowanns(X, "gene") <- NULL 112 | rowanns(X) 113 | 114 | colanns(X, "group") 115 | colanns(X, "age") <- 1:10*10 116 | colanns(X, "age") 117 | colanns(X, "age") <- NULL 118 | colanns(X, "age") 119 | 120 | # more convenient 121 | X@'' 122 | X@chr 123 | X@gene <- letters[1:20] 124 | X@gene 125 | X@gene <- NULL 126 | X@gene 127 | 128 | X$'' 129 | X$group 130 | X$age <- 1:10*10 131 | X$age 132 | X$age <- NULL 133 | X$age 134 | X$'' <- data.frame(id = 1:10, name = LETTERS[1:10]) 135 | X$name 136 | 137 | # annotations are preserved after subsetting 138 | Y <- X[X@chr == "chr1", X$name \%in\% c("A", "B", "C")] 139 | Y@chr 140 | Y$'' 141 | 142 | Y[, 1] 143 | Y[, 1, drop = FALSE] 144 | 145 | } 146 | \seealso{ 147 | \code{as.annmatrix} 148 | } 149 | \author{ 150 | Karolis Koncevičius 151 | } 152 | -------------------------------------------------------------------------------- /R/annmatrix.r: -------------------------------------------------------------------------------- 1 | #' annmatrix Objects and Basic Functionality 2 | #' 3 | #' @description 4 | #' Annotated matrix is a regular matrix with additional functionality for 5 | #' attaching persistent information about row and column entries. Annotations 6 | #' associated with rows and columns are preserved after subsetting, 7 | #' transposition, and various other matrix-specific operations. 8 | #' 9 | #' Intended 'annmatrix' use case is for storing and manipulating genomic 10 | #' datasets that typically consist of a matrix of measurements (like gene 11 | #' expression values) as well as annotations about rows (i.e. genomic 12 | #' locations) and annotations about columns (i.e. meta-data about collected 13 | #' samples). But \code{annmatrix} objects are also expected be useful in 14 | #' various other contexts. 15 | #' 16 | #' @details 17 | #' \code{annmatrix()} constructs an annmatrix. The function expects \code{x} to 18 | #' be a \code{matrix} and \code{rowanns} and \code{colanns} to be of class 19 | #' \code{data.frame}. If the passed objects are of a different classes they 20 | #' will be converted via the use of \code{as.matrix} and \code{as.data.frame}. 21 | #' 22 | #' \code{rowanns} and \code{colanns} returns the selected field from column and 23 | #' row annotations respectively. When the selected field is not specified the 24 | #' whole annotation \code{data.frame} is returned. 25 | #' 26 | #' \code{@} and \code{$} are convenience shortcuts for selecting annotations. 27 | #' \code{X@value} selects an existing column from row annotations while 28 | #' \code{X$value} selects a column from column annotations. An empty selection 29 | #' of \code{X@''} and \code{X$''} will return the whole annotation data.frame 30 | #' for rows and columns respectively. 31 | #' 32 | #' \code{rowanns<-} and \code{colanns<-} functions can be used to replace the 33 | #' column and row annotations respectively. When the selected field is not 34 | #' specified the whole annotation \code{data.frame} is replaced. 35 | #' 36 | #' \code{@<-} and \code{$<-} are convenience shortcuts for the above (see 37 | #' Examples). A replacement of an empty value - \code{X@'' <- df} and 38 | #' \code{X$'' <- df} will replace the whole annotation data.frame. 39 | #' 40 | #' @param x,object an R object. 41 | #' @param rann annotation \code{data.frame} for rows of the \code{annmatrix} object. 42 | #' @param cann annotation \code{data.frame} for columns of the \code{annmatrix} object. 43 | #' @param name a name of an existing row/column annotation. 44 | #' @param names a character vector of existing row/column annotation names. 45 | #' @param value a value that will be assigned to row/column annotation field. 46 | #' 47 | #' @return \code{annmatrix} returns an R object of class 'annmatrix'. 48 | # \code{@} and \code{$} return selected row and column annotations, respectively. 49 | #' 50 | #' @seealso \code{as.annmatrix} 51 | #' 52 | #' @examples 53 | #' # construct annmatrix object 54 | #' x <- matrix(rnorm(20*10), 20, 10) 55 | #' 56 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 57 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 58 | #' 59 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 60 | #' pos = runif(20, 0, 1000000)) 61 | #' 62 | #' X <- annmatrix(x, rowdata, coldata) 63 | #' 64 | #' is.matrix(x) 65 | #' is.matrix(X) 66 | #' 67 | #' is.annmatrix(x) 68 | #' is.annmatrix(X) 69 | #' 70 | #' # manipulating annotations without using shortcuts 71 | #' rowanns(X) 72 | #' colanns(X) 73 | #' 74 | #' rowanns(X, "chr") 75 | #' rowanns(X, "gene") <- letters[1:20] 76 | #' rowanns(X, c("chr", "gene")) 77 | #' rowanns(X, "gene") <- NULL 78 | #' rowanns(X) 79 | #' 80 | #' colanns(X, "group") 81 | #' colanns(X, "age") <- 1:10*10 82 | #' colanns(X, "age") 83 | #' colanns(X, "age") <- NULL 84 | #' colanns(X, "age") 85 | #' 86 | #' # more convenient 87 | #' X@'' 88 | #' X@chr 89 | #' X@gene <- letters[1:20] 90 | #' X@gene 91 | #' X@gene <- NULL 92 | #' X@gene 93 | #' 94 | #' X$'' 95 | #' X$group 96 | #' X$age <- 1:10*10 97 | #' X$age 98 | #' X$age <- NULL 99 | #' X$age 100 | #' X$'' <- data.frame(id = 1:10, name = LETTERS[1:10]) 101 | #' X$name 102 | #' 103 | #' # annotations are preserved after subsetting 104 | #' Y <- X[X@chr == "chr1", X$name %in% c("A", "B", "C")] 105 | #' Y@chr 106 | #' Y$'' 107 | #' 108 | #' Y[, 1] 109 | #' Y[, 1, drop = FALSE] 110 | #' 111 | #' @author Karolis Koncevičius 112 | #' @name annmatrix 113 | #' @export 114 | annmatrix <- function(x, rann, cann) { 115 | 116 | if (missing(x)) { 117 | x <- matrix(nrow = 0, ncol = 0) 118 | } else { 119 | x <- as.matrix(x) 120 | } 121 | 122 | if (missing(rann) || is.null(rann)) { 123 | rann <- data.frame(row.names = seq_len(nrow(x))) 124 | } else { 125 | rann <- as.data.frame(rann) 126 | } 127 | 128 | if (missing(cann) || is.null(cann)) { 129 | cann <- data.frame(row.names = seq_len(ncol(x))) 130 | } else { 131 | cann <- as.data.frame(cann) 132 | } 133 | 134 | if (nrow(x) != nrow(rann)) { 135 | stop("Number of 'rann' rows must match the number of rows in 'x'") 136 | } 137 | if (ncol(x) != nrow(cann)) { 138 | stop("Number of 'cann' rows must match the number of columns in 'x'") 139 | } 140 | 141 | structure(x, class = c("annmatrix", class(x)), .annmatrix.rann = rann, .annmatrix.cann = cann) 142 | } 143 | 144 | #' @rdname annmatrix 145 | #' @export 146 | rowanns <- function(x, names) { 147 | if (missing(names)) { 148 | attr(x, ".annmatrix.rann") 149 | } else if (length(names) == 1) { 150 | attr(x, ".annmatrix.rann")[[names]] 151 | } else { 152 | attr(x, ".annmatrix.rann")[,names] 153 | } 154 | } 155 | 156 | #' @rdname annmatrix 157 | #' @export 158 | `rowanns<-` <- function(x, names, value) { 159 | rann <- attr(x, ".annmatrix.rann") 160 | 161 | if (missing(names)) { 162 | if (is.null(value)) { 163 | rann <- data.frame(row.names = 1:nrow(x)) 164 | } else if (!is.data.frame(value)) { 165 | stop("row annotations should be in a data.frame") 166 | } else if (nrow(value) != nrow(x)) { 167 | stop("new row annotation data should have the same number of rows as there are rows in the matrix") 168 | } else { 169 | rann <- value 170 | } 171 | } else { 172 | rann[,names] <- value 173 | } 174 | 175 | attr(x, ".annmatrix.rann") <- rann 176 | x 177 | } 178 | 179 | #' @rdname annmatrix 180 | #' @export 181 | colanns <- function(x, names) { 182 | if (missing(names)) { 183 | attr(x, ".annmatrix.cann") 184 | } else if (length(names) == 1) { 185 | attr(x, ".annmatrix.cann")[[names]] 186 | } else { 187 | attr(x, ".annmatrix.cann")[,names] 188 | } 189 | } 190 | 191 | 192 | #' @rdname annmatrix 193 | #' @export 194 | `colanns<-` <- function(x, names, value) { 195 | cann <- attr(x, ".annmatrix.cann") 196 | 197 | if (missing(names)) { 198 | if (is.null(value)) { 199 | cann <- data.frame(row.names = 1:ncol(x)) 200 | } else if (!is.data.frame(value)) { 201 | stop("column annotations should be in a data.frame") 202 | } else if (nrow(value) != ncol(x)) { 203 | stop("new column annotation data should have the same number of rows as there are columns in the matrix") 204 | } else { 205 | cann <- value 206 | } 207 | } else { 208 | cann[,names] <- value 209 | } 210 | 211 | attr(x, ".annmatrix.cann") <- cann 212 | x 213 | } 214 | #' @rdname annmatrix 215 | #' @export 216 | `@.annmatrix` <- function(object, name) { 217 | if (nchar(name) == 0) { 218 | rowanns(object) 219 | } else { 220 | rowanns(object, name) 221 | } 222 | } 223 | 224 | #' @rdname annmatrix 225 | #' @export 226 | `@<-.annmatrix` <- function(object, name, value) { 227 | if (nchar(name) == 0) { 228 | rowanns(object) <- value 229 | } else { 230 | rowanns(object, name) <- value 231 | } 232 | object 233 | } 234 | 235 | #' @rdname annmatrix 236 | #' @export 237 | `$.annmatrix` <- function(x, name) { 238 | if (nchar(name) == 0) { 239 | colanns(x) 240 | } else { 241 | colanns(x, name) 242 | } 243 | } 244 | 245 | #' @rdname annmatrix 246 | #' @export 247 | `$<-.annmatrix` <- function(x, name, value) { 248 | if (nchar(name) == 0) { 249 | colanns(x) <- value 250 | } else { 251 | colanns(x, name) <- value 252 | } 253 | x 254 | } 255 | 256 | -------------------------------------------------------------------------------- /R/bind.r: -------------------------------------------------------------------------------- 1 | #' Bind several annmatrix Objects Together 2 | #' 3 | #' Implementation of \code{rbind} and \code{cbind} methods for annmatrix objects. 4 | #' 5 | #' All the inputs are bound together in exact same way as if all the annmatrix objects were regular matrices. 6 | #' Then, the row and column annotations of the supplied annmatrix objects are merged together. 7 | #' Missing annotations are filled in using 'NA' values. 8 | #' 9 | #' For demonstration purposes only \code{rbind} will be described with \code{cbind} behaving accordingly. 10 | #' 11 | #' 1) Matrix. 12 | #' The obtained matrix will be exactly the same as calling \code{rbind} with all annmatrix objects replaced by regular matrices. 13 | #' 14 | #' 2) Column annotations. 15 | #' When \code{rbind} is called the matrices are all assumed to have the same set of columns. 16 | #' Hence, the column annotations are assumed to be shared between all provided annmatrix objects. 17 | #' Thus, in order to retain all possible column annotations, they are merged together. 18 | #' This way any column annotation field present in at least one of the provided annmatrix objects will be present in the final result. 19 | #' In case of conflicts, when the same annotation field is present in multiple annmatrix objects but contains different values, the first occuring instance is selected and an appropriate warning is displayed. 20 | #' Non-annmatrix objects are assumed to share the column annotations present in supplied annmatrix objects. 21 | #' 22 | #' 3) Row annotations. 23 | #' When \code{rbind} is called the matrices are assumed to have a separate unique set of rows. 24 | #' Hence no conflicts between annotation values are possible for row annotations. 25 | #' In order to retain all possible row annotations, row annotations are merged together. 26 | #' Thus, the resulting row annotation data frame will have as many fields as there were unique row annotation fields among all the provided annmatrix objects. 27 | #' Unlike with column annotations, if a particular annmatrix only had a subset of the final collection of annotation fields, then the missing fields are added and the annotation is filled with NA values. 28 | #' All the rows associated with non-annmatrix objects will have missing (NA) values for all the annotation fields. 29 | #' 30 | #' @param ... (generalized) vector or matrix objects 31 | #' 32 | #' @return a single annmatrix object resulting from binding all the inputs together 33 | #' 34 | #' @examples 35 | #' # construct annmatrix object 36 | #' x <- matrix(rnorm(20*10), 20, 10) 37 | #' 38 | #' coldata <- data.frame(group = rep(c("case", "control"), each = 5), 39 | #' gender = sample(c("M", "F"), 10, replace = TRUE)) 40 | #' 41 | #' rowdata <- data.frame(chr = sample(c("chr1", "chr2"), 20, replace = TRUE), 42 | #' pos = runif(20, 0, 1000000)) 43 | #' 44 | #' X <- annmatrix(x, rowdata, coldata) 45 | #' 46 | #' X1 <- X[1:10,] 47 | #' X2 <- X[11:20,] 48 | #' all.equal(X, rbind(X1, X2)) 49 | #' 50 | #' X1 <- X[,1:5] 51 | #' X2 <- X[,6:10] 52 | #' all.equal(X, cbind(X1, X2)) 53 | #' 54 | #' X11 <- X[1:10, 1:5] 55 | #' X21 <- X[11:20, 1:5] 56 | #' X12 <- X[1:10, 6:10] 57 | #' X22 <- X[11:20, 6:10] 58 | #' all.equal(X, cbind(rbind(X11, X21), rbind(X12, X22))) 59 | #' 60 | #' @author Karolis Koncevičius 61 | #' @name bind 62 | #' @export 63 | rbind.annmatrix <- function(...) { 64 | args <- list(...) 65 | 66 | # collect all row and column annotations from annmatrix objects 67 | # then turn all annmatrix objects to regular matrices 68 | ranns <- vector(length(args), mode = "list") 69 | canns <- vector(length(args), mode = "list") 70 | for (i in 1:length(args)) { 71 | if (is.annmatrix(args[[i]])) { 72 | ranns[[i]] <- rowanns(args[[i]]) 73 | canns[[i]] <- colanns(args[[i]]) 74 | attr(args[[i]], ".annmatrix.rann") <- NULL 75 | attr(args[[i]], ".annmatrix.cann") <- NULL 76 | class(args[[i]]) <- setdiff(class(args[[i]]), "annmatrix") 77 | } 78 | } 79 | 80 | # call regular rbind on the arguments without any annmatrix objects 81 | res <- do.call(rbind, args) 82 | 83 | # we can only safely continue if the resulting object is a regular matrix 84 | if (all(class(res) == c("matrix", "array"))) { 85 | 86 | # 1. deal with column annotations 87 | # column annotations will just gain more columns collected from all annmatrix objects 88 | # so gather all of them as separate entries in a list 89 | canns <- unlist(canns, recursive = FALSE) 90 | 91 | # when two annmatrices have same column annotations we can safely remove duplicates 92 | canns <- canns[!duplicated(canns)] 93 | 94 | # but need check for entries with same names but different values 95 | # first construct a data.frame using only unique fields 96 | uniques <- !duplicated(names(canns)) 97 | dups <- canns[!uniques] 98 | canns <- data.frame(canns[uniques]) 99 | 100 | # in case conflicting entries are detected 101 | if (length(dups) > 0) { 102 | 103 | # first show a warning 104 | warning("conflicting annmatrix column annotations - using the ones that occurred first but re-writing missing values") 105 | 106 | # then check which entries in the original have NA values 107 | # we will only be re-writing those 108 | missnames <- names(which(colSums(is.na(canns)) > 0)) 109 | dups <- dups[names(dups) %in% missnames] 110 | 111 | # if there is still something to do 112 | if (length(dups) > 0) { 113 | 114 | # go through each duplicated entry and rewrite missing values 115 | for (i in seq_along(dups)) { 116 | ann <- names(dups)[i] 117 | na <- is.na(canns[[ann]]) 118 | canns[[ann]][na] <- dups[[i]][na] 119 | } 120 | } 121 | 122 | } 123 | 124 | # 2. deal with row annotations 125 | # for rows we don't care about duplicates 126 | # this is because in rbind each row has only one entry of specified annotation 127 | # first get all the column names from non-empty annotations 128 | rnames <- unique(unlist(sapply(ranns, names))) 129 | 130 | # then go through the row annotations and adjust them 131 | for (i in 1:length(ranns)) { 132 | 133 | # if annotation is non-empty then expand it by filling NAs in new columns 134 | if (!is.null(ranns[[i]])) { 135 | ranns[[i]][,setdiff(rnames, names(ranns[[i]]))] <- NA 136 | 137 | # if annotation is empty then we need to create a data.frame filled with NAs 138 | # there will be 3 possibilities: 139 | # 1) argument was empty (length zero) - create data.frame with 0 rows 140 | # 2) argument has no dimensions, meaning it was a vector - create data.frame with 1 rows 141 | # 3) argument had 2 dimensions - create data.frame with as many rows as there were in argument 142 | # 4) argument dimension size was above 2 - create data.frame with 1 rows 143 | } else { 144 | nr <- 1 145 | if (length(args[[i]]) == 0) { 146 | nr <- 0 147 | } else if (length(dim(args[[i]])) == 2) { 148 | nr <- dim(args[[i]])[1] 149 | } 150 | ranns[[i]] <- data.frame(matrix(NA, nrow = nr, ncol = length(rnames), dimnames = list(NULL, rnames))) 151 | } 152 | 153 | } 154 | 155 | # finally bind all the row annotations into a single data.frame 156 | ranns <- do.call(rbind, ranns) 157 | 158 | res <- annmatrix(res, rann = ranns, cann = canns) 159 | 160 | } 161 | 162 | res 163 | } 164 | 165 | #' @rdname bind 166 | #' @export 167 | cbind.annmatrix <- function(...) { 168 | args <- list(...) 169 | 170 | # follows the same logic as rbind, look there for comments 171 | ranns <- vector(length(args), mode = "list") 172 | canns <- vector(length(args), mode = "list") 173 | for (i in 1:length(args)) { 174 | if (is.annmatrix(args[[i]])) { 175 | ranns[[i]] <- rowanns(args[[i]]) 176 | canns[[i]] <- colanns(args[[i]]) 177 | attr(args[[i]], ".annmatrix.rann") <- NULL 178 | attr(args[[i]], ".annmatrix.cann") <- NULL 179 | class(args[[i]]) <- setdiff(class(args[[i]]), "annmatrix") 180 | } 181 | } 182 | 183 | res <- do.call(cbind, args) 184 | 185 | if (all(class(res) == c("matrix", "array"))) { 186 | 187 | ranns <- unlist(ranns, recursive = FALSE) 188 | ranns <- ranns[!duplicated(ranns)] 189 | 190 | dups <- duplicated(names(ranns)) 191 | if (any(dups)) { 192 | warning("conflicting annmatrix row annotations - using the ones that occurred first") 193 | ranns <- ranns[!dups] 194 | } 195 | 196 | ranns <- data.frame(ranns) 197 | 198 | cnames <- unique(unlist(sapply(canns, names))) 199 | 200 | for (i in 1:length(canns)) { 201 | if (!is.null(canns[[i]])) { 202 | canns[[i]][,setdiff(cnames, names(canns[[i]]))] <- NA 203 | } else { 204 | nc <- 1 205 | if (length(args[[i]]) == 0) { 206 | nc <- 0 207 | } else if (length(dim(args[[i]])) == 2) { 208 | nc <- dim(args[[i]])[1] 209 | } 210 | canns[[i]] <- data.frame(matrix(NA, nrow = nc, ncol = length(cnames), dimnames = list(NULL, cnames))) 211 | } 212 | } 213 | 214 | canns <- do.call(rbind, canns) 215 | res <- annmatrix(res, rann = ranns, cann = canns) 216 | 217 | } 218 | 219 | res 220 | } 221 | -------------------------------------------------------------------------------- /tests/annmatrix.Rout.save: -------------------------------------------------------------------------------- 1 | 2 | R version 4.3.0 (2023-04-21) -- "Already Tomorrow" 3 | Copyright (C) 2023 The R Foundation for Statistical Computing 4 | Platform: x86_64-apple-darwin22.4.0 (64-bit) 5 | 6 | R is free software and comes with ABSOLUTELY NO WARRANTY. 7 | You are welcome to redistribute it under certain conditions. 8 | Type 'license()' or 'licence()' for distribution details. 9 | 10 | Natural language support but running in an English locale 11 | 12 | R is a collaborative project with many contributors. 13 | Type 'contributors()' for more information and 14 | 'citation()' on how to cite R or R packages in publications. 15 | 16 | Type 'demo()' for some demos, 'help()' for on-line help, or 17 | 'help.start()' for an HTML browser interface to help. 18 | Type 'q()' to quit R. 19 | 20 | > library(annmatrix) 21 | > source("utils/test.r") 22 | > 23 | > #=================================== ERRORS ==================================== 24 | > 25 | > test(annmatrix(mean)) # closure cannot be transformed to a matrix 26 | Error in as.vector(x, mode) : 27 | cannot coerce type 'closure' to vector of type 'any' 28 | -------------------------------------------------------------------------------- 29 | 30 | > test(annmatrix(environment())) # environment cannot be transformed to a matrix 31 | Error in as.vector(x, mode) : 32 | cannot coerce type 'environment' to vector of type 'any' 33 | -------------------------------------------------------------------------------- 34 | 35 | > 36 | > test(annmatrix(iris, rann = data.frame())) # incorrect number of row annotations (zero) 37 | Error in annmatrix(iris, rann = data.frame()) : 38 | Number of 'rann' rows must match the number of rows in 'x' 39 | -------------------------------------------------------------------------------- 40 | 41 | > test(annmatrix(iris, rann = data.frame(a = 1, b = 2))) # incorrect number of row annotations (one) 42 | Error in annmatrix(iris, rann = data.frame(a = 1, b = 2)) : 43 | Number of 'rann' rows must match the number of rows in 'x' 44 | -------------------------------------------------------------------------------- 45 | 46 | > test(annmatrix(iris, rann = data.frame(1:3))) # incorrect number of row annotations (too low) 47 | Error in annmatrix(iris, rann = data.frame(1:3)) : 48 | Number of 'rann' rows must match the number of rows in 'x' 49 | -------------------------------------------------------------------------------- 50 | 51 | > test(annmatrix(iris, rann = data.frame(1:200))) # incorrect number of row annotations (too high) 52 | Error in annmatrix(iris, rann = data.frame(1:200)) : 53 | Number of 'rann' rows must match the number of rows in 'x' 54 | -------------------------------------------------------------------------------- 55 | 56 | > 57 | > test(annmatrix(iris, cann = data.frame())) # incorrect number of column annotations (zero) 58 | Error in annmatrix(iris, cann = data.frame()) : 59 | Number of 'cann' rows must match the number of columns in 'x' 60 | -------------------------------------------------------------------------------- 61 | 62 | > test(annmatrix(iris, cann = data.frame(a = 1, b = 2))) # incorrect number of column annotations (one) 63 | Error in annmatrix(iris, cann = data.frame(a = 1, b = 2)) : 64 | Number of 'cann' rows must match the number of columns in 'x' 65 | -------------------------------------------------------------------------------- 66 | 67 | > test(annmatrix(iris, cann = data.frame(1:3))) # incorrect number of column annotations (too low) 68 | Error in annmatrix(iris, cann = data.frame(1:3)) : 69 | Number of 'cann' rows must match the number of columns in 'x' 70 | -------------------------------------------------------------------------------- 71 | 72 | > test(annmatrix(iris, cann = data.frame(1:200))) # incorrect number of column annotations (too high) 73 | Error in annmatrix(iris, cann = data.frame(1:200)) : 74 | Number of 'cann' rows must match the number of columns in 'x' 75 | -------------------------------------------------------------------------------- 76 | 77 | > 78 | > test(annmatrix(iris, data.frame(), data.frame())) # when both incorrect - error about row annotations 79 | Error in annmatrix(iris, data.frame(), data.frame()) : 80 | Number of 'rann' rows must match the number of rows in 'x' 81 | -------------------------------------------------------------------------------- 82 | 83 | > 84 | > 85 | > #============================= EMPTY CONSTRUCTORS ============================== 86 | > 87 | > test(annmatrix()) # empty call results in an empty matrix 88 | $type 89 | [1] "logical" 90 | 91 | $matrix 92 | <0 x 0 matrix> 93 | 94 | $rann 95 | data frame with 0 columns and 0 rows 96 | 97 | $cann 98 | data frame with 0 columns and 0 rows 99 | 100 | -------------------------------------------------------------------------------- 101 | 102 | > test(annmatrix(data.frame(), data.frame())) # allows specifying empty data.frames for annotations 103 | $type 104 | [1] "logical" 105 | 106 | $matrix 107 | <0 x 0 matrix> 108 | 109 | $rann 110 | data frame with 0 columns and 0 rows 111 | 112 | $cann 113 | data frame with 0 columns and 0 rows 114 | 115 | -------------------------------------------------------------------------------- 116 | 117 | > 118 | > test(annmatrix(logical())) # empty logical results in zero-row one-column logical 119 | $type 120 | [1] "logical" 121 | 122 | $matrix 123 | [,1] 124 | 125 | $rann 126 | data frame with 0 columns and 0 rows 127 | 128 | $cann 129 | data frame with 0 columns and 1 row 130 | 131 | -------------------------------------------------------------------------------- 132 | 133 | > test(annmatrix(integer())) # empty integer results in zero-row one-column integer 134 | $type 135 | [1] "integer" 136 | 137 | $matrix 138 | [,1] 139 | 140 | $rann 141 | data frame with 0 columns and 0 rows 142 | 143 | $cann 144 | data frame with 0 columns and 1 row 145 | 146 | -------------------------------------------------------------------------------- 147 | 148 | > test(annmatrix(numeric())) # empty numeric results in zero-row one-column numeric 149 | $type 150 | [1] "double" 151 | 152 | $matrix 153 | [,1] 154 | 155 | $rann 156 | data frame with 0 columns and 0 rows 157 | 158 | $cann 159 | data frame with 0 columns and 1 row 160 | 161 | -------------------------------------------------------------------------------- 162 | 163 | > test(annmatrix(character())) # empty character results in zero-row one-column character 164 | $type 165 | [1] "character" 166 | 167 | $matrix 168 | [,1] 169 | 170 | $rann 171 | data frame with 0 columns and 0 rows 172 | 173 | $cann 174 | data frame with 0 columns and 1 row 175 | 176 | -------------------------------------------------------------------------------- 177 | 178 | > test(annmatrix(complex())) # empty complex results in zero-row one-column complex 179 | $type 180 | [1] "complex" 181 | 182 | $matrix 183 | [,1] 184 | 185 | $rann 186 | data frame with 0 columns and 0 rows 187 | 188 | $cann 189 | data frame with 0 columns and 1 row 190 | 191 | -------------------------------------------------------------------------------- 192 | 193 | > test(annmatrix(list())) # empty list results in zero-row one-column list 194 | $type 195 | [1] "list" 196 | 197 | $matrix 198 | [,1] 199 | 200 | $rann 201 | data frame with 0 columns and 0 rows 202 | 203 | $cann 204 | data frame with 0 columns and 1 row 205 | 206 | -------------------------------------------------------------------------------- 207 | 208 | > 209 | > x <- matrix(integer(), nrow = 0, ncol = 3) # zero number of rows 210 | > test(annmatrix(x)) # sets the correct number of rows and columns 211 | $type 212 | [1] "integer" 213 | 214 | $matrix 215 | [,1] [,2] [,3] 216 | 217 | $rann 218 | data frame with 0 columns and 0 rows 219 | 220 | $cann 221 | data frame with 0 columns and 3 rows 222 | 223 | -------------------------------------------------------------------------------- 224 | 225 | > test(annmatrix(x, rann = data.frame())) # allows specifying empty row annotations 226 | $type 227 | [1] "integer" 228 | 229 | $matrix 230 | [,1] [,2] [,3] 231 | 232 | $rann 233 | data frame with 0 columns and 0 rows 234 | 235 | $cann 236 | data frame with 0 columns and 3 rows 237 | 238 | -------------------------------------------------------------------------------- 239 | 240 | > test(annmatrix(x, cann = data.frame(a = letters[1:3]))) # allows specifying annotations for columns 241 | $type 242 | [1] "integer" 243 | 244 | $matrix 245 | [,1] [,2] [,3] 246 | 247 | $rann 248 | data frame with 0 columns and 0 rows 249 | 250 | $cann 251 | a 252 | 1 a 253 | 2 b 254 | 3 c 255 | 256 | -------------------------------------------------------------------------------- 257 | 258 | > 259 | > x <- matrix(integer(), nrow = 3, ncol = 0) # zero number of columns 260 | > test(annmatrix(x)) # sets the correct number of rows and columns 261 | $type 262 | [1] "integer" 263 | 264 | $matrix 265 | 266 | [1,] 267 | [2,] 268 | [3,] 269 | 270 | $rann 271 | data frame with 0 columns and 3 rows 272 | 273 | $cann 274 | data frame with 0 columns and 0 rows 275 | 276 | -------------------------------------------------------------------------------- 277 | 278 | > test(annmatrix(x, cann = data.frame())) # allows specifying empty column annotations 279 | $type 280 | [1] "integer" 281 | 282 | $matrix 283 | 284 | [1,] 285 | [2,] 286 | [3,] 287 | 288 | $rann 289 | data frame with 0 columns and 3 rows 290 | 291 | $cann 292 | data frame with 0 columns and 0 rows 293 | 294 | -------------------------------------------------------------------------------- 295 | 296 | > test(annmatrix(x, rann = data.frame(a = letters[1:3]))) # allows specifying annotations for rows 297 | $type 298 | [1] "integer" 299 | 300 | $matrix 301 | 302 | [1,] 303 | [2,] 304 | [3,] 305 | 306 | $rann 307 | a 308 | 1 a 309 | 2 b 310 | 3 c 311 | 312 | $cann 313 | data frame with 0 columns and 0 rows 314 | 315 | -------------------------------------------------------------------------------- 316 | 317 | > 318 | > 319 | > #=============================== VARIOUS TYPES ================================= 320 | > 321 | > test(annmatrix(c(TRUE, FALSE))) # works with logical 322 | $type 323 | [1] "logical" 324 | 325 | $matrix 326 | [,1] 327 | [1,] TRUE 328 | [2,] FALSE 329 | 330 | $rann 331 | data frame with 0 columns and 2 rows 332 | 333 | $cann 334 | data frame with 0 columns and 1 row 335 | 336 | -------------------------------------------------------------------------------- 337 | 338 | > test(annmatrix(1:2)) # works with integer 339 | $type 340 | [1] "integer" 341 | 342 | $matrix 343 | [,1] 344 | [1,] 1 345 | [2,] 2 346 | 347 | $rann 348 | data frame with 0 columns and 2 rows 349 | 350 | $cann 351 | data frame with 0 columns and 1 row 352 | 353 | -------------------------------------------------------------------------------- 354 | 355 | > test(annmatrix(1:2/2)) # works with real 356 | $type 357 | [1] "double" 358 | 359 | $matrix 360 | [,1] 361 | [1,] 0.5 362 | [2,] 1.0 363 | 364 | $rann 365 | data frame with 0 columns and 2 rows 366 | 367 | $cann 368 | data frame with 0 columns and 1 row 369 | 370 | -------------------------------------------------------------------------------- 371 | 372 | > test(annmatrix(letters[1:2])) # works with character 373 | $type 374 | [1] "character" 375 | 376 | $matrix 377 | [,1] 378 | [1,] "a" 379 | [2,] "b" 380 | 381 | $rann 382 | data frame with 0 columns and 2 rows 383 | 384 | $cann 385 | data frame with 0 columns and 1 row 386 | 387 | -------------------------------------------------------------------------------- 388 | 389 | > test(annmatrix(as.complex(1:2))) # works with complex 390 | $type 391 | [1] "complex" 392 | 393 | $matrix 394 | [,1] 395 | [1,] 1+0i 396 | [2,] 2+0i 397 | 398 | $rann 399 | data frame with 0 columns and 2 rows 400 | 401 | $cann 402 | data frame with 0 columns and 1 row 403 | 404 | -------------------------------------------------------------------------------- 405 | 406 | > test(annmatrix(matrix(1:6, nrow=2))) # works with matrix 407 | $type 408 | [1] "integer" 409 | 410 | $matrix 411 | [,1] [,2] [,3] 412 | [1,] 1 3 5 413 | [2,] 2 4 6 414 | 415 | $rann 416 | data frame with 0 columns and 2 rows 417 | 418 | $cann 419 | data frame with 0 columns and 3 rows 420 | 421 | -------------------------------------------------------------------------------- 422 | 423 | > test(annmatrix(list(1, 1:2, letters[1:3]))) # works with list 424 | $type 425 | [1] "list" 426 | 427 | $matrix 428 | [,1] 429 | [1,] 1 430 | [2,] integer,2 431 | [3,] character,3 432 | 433 | $rann 434 | data frame with 0 columns and 3 rows 435 | 436 | $cann 437 | data frame with 0 columns and 1 row 438 | 439 | -------------------------------------------------------------------------------- 440 | 441 | > 442 | > 443 | > #=============================== REGULAR CASES ================================= 444 | > 445 | > # check for preserving correct result, annotations, and row/column names 446 | > x <- matrix(1:6, nrow=2, ncol=3, dimnames=list(letters[1:2], LETTERS[1:3])) 447 | > rann <- data.frame(id = 1:2, chr = c("c1", "c2"), pos = c(10, 20)) 448 | > cann <- data.frame(name = c("A", "B", "C"), age = c(20, 40, 60)) 449 | > 450 | > test(annmatrix(x)) # without annotations 451 | $type 452 | [1] "integer" 453 | 454 | $matrix 455 | A B C 456 | a 1 3 5 457 | b 2 4 6 458 | 459 | $rann 460 | data frame with 0 columns and 2 rows 461 | 462 | $cann 463 | data frame with 0 columns and 3 rows 464 | 465 | -------------------------------------------------------------------------------- 466 | 467 | > test(annmatrix(x, rann = rann)) # only row annotations 468 | $type 469 | [1] "integer" 470 | 471 | $matrix 472 | A B C 473 | a 1 3 5 474 | b 2 4 6 475 | 476 | $rann 477 | id chr pos 478 | 1 1 c1 10 479 | 2 2 c2 20 480 | 481 | $cann 482 | data frame with 0 columns and 3 rows 483 | 484 | -------------------------------------------------------------------------------- 485 | 486 | > test(annmatrix(x, cann = cann)) # only column annotations 487 | $type 488 | [1] "integer" 489 | 490 | $matrix 491 | A B C 492 | a 1 3 5 493 | b 2 4 6 494 | 495 | $rann 496 | data frame with 0 columns and 2 rows 497 | 498 | $cann 499 | name age 500 | 1 A 20 501 | 2 B 40 502 | 3 C 60 503 | 504 | -------------------------------------------------------------------------------- 505 | 506 | > test(annmatrix(x, rann = rann, cann = cann)) # both annotations 507 | $type 508 | [1] "integer" 509 | 510 | $matrix 511 | A B C 512 | a 1 3 5 513 | b 2 4 6 514 | 515 | $rann 516 | id chr pos 517 | 1 1 c1 10 518 | 2 2 c2 20 519 | 520 | $cann 521 | name age 522 | 1 A 20 523 | 2 B 40 524 | 3 C 60 525 | 526 | -------------------------------------------------------------------------------- 527 | 528 | > 529 | > # TODO: check for list matrices and list entries in annotations 530 | > 531 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CRAN version](http://www.r-pkg.org/badges/version/annmatrix)](https://cran.r-project.org/package=annmatrix) 2 | [![dependencies](https://tinyverse.netlify.com/badge/annmatrix)](https://CRAN.R-project.org/package=annmatrix) 3 | [![Monthly Downloads](https://cranlogs.r-pkg.org/badges/annmatrix)](https://cranlogs.r-pkg.org/badges/annmatrix) 4 | 5 | # annmatrix # 6 | 7 | R Annotated Matrix Object 8 | 9 | ![illustration](http://karolis.koncevicius.lt/data/annmatrix/illustration.png) 10 | 11 | 12 | ## Description ## 13 | 14 | `annmatrix` implements persistent row and column annotations for R matrices. 15 | 16 | The use-case was born out of the need to better organize biomedical microarray and sequencing data within R. 17 | But 'annmatrix' is readily applicable in other contexts where the data can be assembled into a matrix form with rows and columns representing distinct types of information. 18 | 19 | The main advantage of 'annmatrix' over BioConductor implementations like [SummarizedExperiment](https://bioconductor.org/packages/release/bioc/html/SummarizedExperiment.html) and [AnnotatedDataFrame](https://www.rdocumentation.org/packages/Biobase/versions/2.32.0/topics/AnnotatedDataFrame) is simplicity. 20 | Since 'annmatrix' is based on a matrix, and not a list or a data frame, it behaves like a regular matrix and can be directly passed to various methods that expect a matrix for an input. 21 | 22 | 23 | ## Demonstration ## 24 | 25 | Say, we have a small gene expression dataset with 10 genes measured across 6 samples. 26 | 27 | ```r 28 | mat <- matrix(rnorm(10 * 6), nrow = 10, ncol = 6) 29 | 30 | [,1] [,2] [,3] [,4] [,5] [,6] 31 | [1,] -0.66184983 -0.3828219 -1.2668148 -1.42199245 -0.431174368 -1.86544873 32 | [2,] 1.71895416 0.2994216 -0.1985833 -0.32822829 0.382446538 1.82998433 33 | [3,] 2.12166699 0.6742398 0.1388658 0.28457007 0.041125095 -0.99111590 34 | [4,] 1.49715368 -0.2928163 -0.2793360 0.71933588 -0.059224001 -1.45043462 35 | [5,] -0.03614058 0.4880534 0.7089194 0.43241598 -1.296423984 -0.01482476 36 | [6,] 1.23194518 0.8828018 -0.7666105 -0.35192477 -2.136976377 0.53663905 37 | [7,] -0.06488077 1.8627490 1.4433629 0.29772143 -0.893614686 -0.81110377 38 | [8,] 1.06899373 1.6117253 0.8448793 -0.26143236 0.612732875 -0.31832480 39 | [9,] -0.37696531 0.1354795 -0.3993704 1.30868973 0.582971232 1.11147600 40 | [10,] 1.04318309 1.0880860 -1.4277676 0.01587026 -0.005882013 -0.14174471 41 | ``` 42 | 43 | We can turn this matrix into 'annmatrix' object right away. 44 | 45 | ```r 46 | X <- as.annmatrix(mat) 47 | ``` 48 | 49 | When printed `annmatrix` shows 4 first + the last row and 4 first + the last column from the matrix and lists all available row and column annotations (of which there are currently none). 50 | 51 | ```r 52 | X 53 | 54 | [,1] [,2] [,3] [,4] [,6] 55 | [1,] -0.66184983 -0.38282188 -1.26681476 -1.42199245 ........... -1.86544873 56 | [2,] 1.71895416 0.29942160 -0.19858329 -0.32822829 ........... 1.82998433 57 | [3,] 2.12166699 0.67423976 0.13886578 0.28457007 ........... -0.99111590 58 | [4,] 1.49715368 -0.29281632 -0.27933600 0.71933588 ........... -1.45043462 59 | ........... ........... ........... ........... ........... ........... 60 | [10,] 1.04318309 1.08808601 -1.42776759 0.01587026 ........... -0.14174471 61 | 62 | rann: 63 | cann: 64 | ``` 65 | 66 | Custom operators `@` and `$` are provided for convenient manipulation of row and column data. 67 | Let's add some additional information about our samples. 68 | 69 | ```r 70 | X$group <- c("case", "case", "case", "control", "control", "control") 71 | X$sex <- c("F", "M", "M", "M", "F", "F") 72 | 73 | X 74 | 75 | [,1] [,2] [,3] [,4] [,6] 76 | [1,] -0.66184983 -0.38282188 -1.26681476 -1.42199245 ........... -1.86544873 77 | [2,] 1.71895416 0.29942160 -0.19858329 -0.32822829 ........... 1.82998433 78 | [3,] 2.12166699 0.67423976 0.13886578 0.28457007 ........... -0.99111590 79 | [4,] 1.49715368 -0.29281632 -0.27933600 0.71933588 ........... -1.45043462 80 | ........... ........... ........... ........... ........... ........... 81 | [10,] 1.04318309 1.08808601 -1.42776759 0.01587026 ........... -0.14174471 82 | 83 | rann: 84 | cann: group, sex 85 | 86 | ``` 87 | 88 | We can add the entire annotation table by assigning to an empty value. Let's do this for rows. 89 | 90 | ```r 91 | chromosome <- c("chr1", "chr1", "chr1", "chr1", "chr2", "chr2", "chr2", "chr3", "chr3", "chr3") 92 | position <- runif(10, 0, 1000000) 93 | 94 | X@'' <- data.frame(chr = chromosome, pos = position) 95 | 96 | X 97 | 98 | [,1] [,2] [,3] [,4] [,6] 99 | [1,] -0.66184983 -0.38282188 -1.26681476 -1.42199245 ........... -1.86544873 100 | [2,] 1.71895416 0.29942160 -0.19858329 -0.32822829 ........... 1.82998433 101 | [3,] 2.12166699 0.67423976 0.13886578 0.28457007 ........... -0.99111590 102 | [4,] 1.49715368 -0.29281632 -0.27933600 0.71933588 ........... -1.45043462 103 | ........... ........... ........... ........... ........... ........... 104 | [10,] 1.04318309 1.08808601 -1.42776759 0.01587026 ........... -0.14174471 105 | 106 | rann: chr, pos 107 | cann: group, sex 108 | ``` 109 | 110 | Same shortcuts can be used for retrieving available annotations. 111 | 112 | ```r 113 | X@chr 114 | 115 | [1] "chr1" "chr1" "chr1" "chr1" "chr2" "chr2" "chr2" "chr3" "chr3" "chr3" 116 | 117 | X$group 118 | 119 | [1] "case" "case" "case" "control" "control" "control" 120 | ``` 121 | 122 | As well as for adjusting annotations. 123 | 124 | ```r 125 | X@pos 126 | 127 | [1] 418638.0 423928.7 248000.4 220840.4 482854.1 662214.0 122708.6 334171.7 219852.3 927672.1 128 | 129 | X@pos <- X@pos * 10 130 | 131 | X@pos 132 | 133 | [1] 4186380 4239287 2480004 2208404 4828541 6622140 1227086 3341717 2198523 9276721 134 | ``` 135 | 136 | Or create new ones. 137 | 138 | ```r 139 | X$age <- seq(10, 60, 10) 140 | X$age 141 | 142 | [1] 10 20 30 40 50 60 143 | ``` 144 | 145 | When an empty name is provided it will return the whole annotation `data.frame`. 146 | 147 | ```r 148 | X$'' 149 | 150 | group sex age 151 | 1 case F 10 152 | 2 case M 20 153 | 3 case M 30 154 | 4 control M 40 155 | 5 control F 50 156 | 6 control F 60 157 | ``` 158 | 159 | When subsetting the `annmatrix` object all the annotations are correctly adjusted and class is preserved. 160 | 161 | 162 | ```r 163 | X_case <- X[, X$group == "case"] 164 | X_case 165 | 166 | [,1] [,2] [,3] 167 | [1,] -0.6618498 -0.3828219 -1.2668148 168 | [2,] 1.7189542 0.2994216 -0.1985833 169 | [3,] 2.1216670 0.6742398 0.1388658 170 | [4,] 1.4971537 -0.2928163 -0.2793360 171 | .......... .......... .......... 172 | [10,] 1.0431831 1.0880860 -1.4277676 173 | 174 | rann: chr, pos 175 | cann: group, sex, age 176 | ``` 177 | 178 | ```r 179 | X_case$'' 180 | 181 | group sex age 182 | 1 case F 10 183 | 2 case M 20 184 | 3 case M 30 185 | ``` 186 | 187 | However in order to be consistent with `matrix` the class is dropped when selecting only a single row or column. 188 | 189 | ```r 190 | X[1,] 191 | 192 | [1] -0.6618498 -0.3828219 -1.2668148 -1.4219925 -0.4311744 -1.8654487 193 | ``` 194 | 195 | But just like with a matrix we can enforce it to preserve all the annotations and the class by setting `drop=FALSE`. 196 | 197 | 198 | ```r 199 | X[1,, drop=FALSE] 200 | 201 | [,1] [,2] [,3] [,4] [,6] 202 | [1,] -0.6618498 -0.3828219 -1.2668148 -1.4219925 .......... -1.8654487 203 | 204 | rann: chr, pos 205 | cann: group, sex, age 206 | ``` 207 | 208 | Operations on `annmatrix` object don't loose the class. 209 | 210 | ```r 211 | X > 0 212 | 213 | [,1] [,2] [,3] [,4] [,6] 214 | [1,] FALSE FALSE FALSE FALSE ..... FALSE 215 | [2,] TRUE TRUE FALSE FALSE ..... TRUE 216 | [3,] TRUE TRUE TRUE TRUE ..... FALSE 217 | [4,] TRUE FALSE FALSE TRUE ..... FALSE 218 | ..... ..... ..... ..... ..... ..... 219 | [10,] TRUE TRUE FALSE TRUE ..... FALSE 220 | 221 | rann: chr, pos 222 | cann: group, sex, age 223 | ``` 224 | 225 | ```r 226 | X <- X - rowMeans(X) 227 | X 228 | 229 | [,1] [,2] [,3] [,4] [,6] 230 | [1,] 0.34316717 0.62219512 -0.26179776 -0.41697545 ........... -0.86043172 231 | [2,] 1.10162165 -0.31791091 -0.81591579 -0.94556080 ........... 1.21265182 232 | [3,] 1.74344169 0.29601446 -0.23935952 -0.09365523 ........... -1.36934120 233 | [4,] 1.47470725 -0.31526276 -0.30178244 0.69688945 ........... -1.47288106 234 | ........... ........... ........... ........... ........... ........... 235 | [10,] 0.94789225 0.99279517 -1.52305843 -0.07942058 ........... -0.23703556 236 | 237 | rann: chr, pos 238 | cann: group, sex, age 239 | ``` 240 | 241 | Matrix transpose will preserve the class and correctly adjust row and column annotations. 242 | 243 | ```r 244 | t(X) 245 | 246 | [,1] [,2] [,3] [,4] [,10] 247 | [1,] 0.34316717 1.10162165 1.74344169 1.47470725 ........... 0.94789225 248 | [2,] 0.62219512 -0.31791091 0.29601446 -0.31526276 ........... 0.99279517 249 | [3,] -0.26179776 -0.81591579 -0.23935952 -0.30178244 ........... -1.52305843 250 | [4,] -0.41697545 -0.94556080 -0.09365523 0.69688945 ........... -0.07942058 251 | ........... ........... ........... ........... ........... ........... 252 | [6,] -0.86043172 1.21265182 -1.36934120 -1.47288106 ........... -0.23703556 253 | 254 | rann: group, sex, age 255 | cann: chr, pos 256 | ``` 257 | 258 | Principal component analysis with `prcomp` will transform the PCA loadings into 'annmatrix' object and add row and column annotations to it, such as proportion of variance explained. 259 | 260 | ```r 261 | pca <- prcomp(t(X)) 262 | pca$rotation 263 | 264 | PC1 PC2 PC3 PC4 PC6 265 | [1,] 0.17186228 -0.06111446 0.28479933 0.34143265 ........... 0.06141574 266 | [2,] 0.07770596 0.56511346 -0.15143694 0.26206867 ........... -0.05690171 267 | [3,] 0.48808049 0.04486714 0.31834335 -0.16657847 ........... 0.57896252 268 | [4,] 0.34355333 0.02691052 0.45134983 -0.56375589 ........... -0.52045090 269 | ........... ........... ........... ........... ........... ........... 270 | [10,] 0.32535557 0.32483324 0.07792832 0.15417554 ........... 0.21093439 271 | 272 | rann: chr, pos 273 | cann: pc, sd, var, var_explained 274 | 275 | 276 | pca$rotation$var_explained 277 | 278 | [1] 3.976151e-01 2.492482e-01 1.991826e-01 8.899235e-02 6.496171e-02 3.582514e-33 279 | ``` 280 | 281 | Furthermore, matrix cross-product will preserve all annotations that are possible to preserve after the product. 282 | 283 | ```r 284 | X_scores <- t(pca$rotation) %*% X 285 | X_scores 286 | 287 | [,1] [,2] [,3] [,4] [,6] 288 | PC1 2.642132e+00 1.845104e+00 -3.957560e-01 -5.252007e-01 ............. -1.828050e+00 289 | PC2 1.486939e+00 -8.303449e-01 -2.172757e+00 -3.843922e-01 ............. 1.723213e+00 290 | PC3 7.210412e-01 -8.182090e-01 -4.038344e-01 1.336113e-01 ............. -1.709567e+00 291 | PC4 -2.734391e-01 8.671400e-01 2.275004e-03 -1.559362e+00 ............. 2.337191e-01 292 | ............. ............. ............. ............. ............. ............. 293 | PC6 1.110223e-16 2.220446e-15 2.553513e-15 -1.020364e-14 ............. -2.567391e-16 294 | 295 | rann: pc, sd, var, var_explained 296 | cann: group, sex, age 297 | 298 | 299 | X_scores@'' 300 | 301 | pc sd var var_explained 302 | PC1 PC1 1.853695e+00 3.436186e+00 3.976151e-01 303 | PC2 PC2 1.467652e+00 2.154001e+00 2.492482e-01 304 | PC3 PC3 1.311996e+00 1.721335e+00 1.991826e-01 305 | PC4 PC4 8.769670e-01 7.690712e-01 8.899235e-02 306 | PC5 PC5 7.492653e-01 5.613986e-01 6.496171e-02 307 | PC6 PC6 1.759547e-16 3.096006e-32 3.582514e-33 308 | 309 | 310 | X_scores$'' 311 | 312 | group sex age 313 | 1 case F 10 314 | 2 case M 20 315 | 3 case M 30 316 | 4 control M 40 317 | 5 control F 50 318 | 6 control F 60 319 | ``` 320 | 321 | And, of course, we get all the goodies that come from storing our data as a matrix. 322 | 323 | ```r 324 | 325 | # medians of all genes on chromosome 1 326 | 327 | library(matrixStats) 328 | rowMedians(X[X@chr == "chr1",]) 329 | 330 | [1] 0.04068471 -0.27639844 -0.16650737 -0.19172644 331 | 332 | 333 | # Gene-wise Bartlett's test for equal variance between cases and control 334 | 335 | library(matrixTests) 336 | row_bartlett(X, X$group) 337 | 338 | obs.tot obs.groups var.pooled df statistic pvalue 339 | 1 6 2 0.3717333 1 0.363202329 0.546733101 340 | 2 6 2 1.0998615 1 0.016042378 0.899210686 341 | 3 6 2 0.7554444 1 0.268153616 0.604573040 342 | 4 6 2 1.1341347 1 0.006848371 0.934046441 343 | 5 6 2 0.4758403 1 1.043799502 0.306939452 344 | 6 6 2 1.4967384 1 0.093874925 0.759307605 345 | 7 6 2 0.7351686 1 0.275875443 0.599417430 346 | 8 6 2 0.2139256 1 0.124149137 0.724577269 347 | 9 6 2 0.1161703 1 0.073665362 0.786072915 348 | 10 6 2 1.0400756 1 6.832203465 0.008952875 349 | 350 | 351 | # Pearson's correlation test between each gene's expression values and the age of subjects 352 | 353 | library(matrixTests) 354 | row_cor_pearson(X, X$age) 355 | 356 | obs.paired cor df statistic pvalue conf.low conf.high alternative cor.null conf.level 357 | 1 6 -0.5602433 4 -1.35270865 0.24755755 -0.9430338 0.46085636 two.sided 0 0.95 358 | 2 6 0.0384372 4 0.07693125 0.94237259 -0.7980176 0.82428560 two.sided 0 0.95 359 | 3 6 -0.9092607 4 -4.36906111 0.01197687 -0.9901613 -0.37278589 two.sided 0 0.95 360 | 4 6 -0.6950676 4 -1.93357802 0.12529880 -0.9632623 0.26724253 two.sided 0 0.95 361 | 5 6 -0.4096758 4 -0.89818427 0.41986507 -0.9165164 0.60205486 two.sided 0 0.95 362 | 6 6 -0.5186591 4 -1.21326433 0.29177292 -0.9361660 0.50580928 two.sided 0 0.95 363 | 7 6 -0.6143204 4 -1.55710099 0.19443841 -0.9515021 0.39334780 two.sided 0 0.95 364 | 8 6 -0.7763522 4 -2.46344001 0.06943424 -0.9741458 0.09517432 two.sided 0 0.95 365 | 9 6 0.7663925 4 2.38613079 0.07548443 -0.1194861 0.97285981 two.sided 0 0.95 366 | 10 6 -0.4486700 4 -1.00407581 0.37215468 -0.9238398 0.57069431 two.sided 0 0.95 367 | ``` 368 | 369 | When needed 'annmatrix' can be transformed into a long-format data frame. 370 | 371 | ```r 372 | stack(X) 373 | 374 | value chr pos group sex age 375 | 1:1 0.343167174 chr1 4186380 case F 10 376 | 2:1 1.101621650 chr1 4239287 case F 10 377 | 3:1 1.743441691 chr1 2480004 case F 10 378 | 4:1 1.474707248 chr1 2208404 case F 10 379 | 5:1 -0.083140482 chr2 4828541 case F 10 380 | 6:1 1.332632768 chr2 6622140 case F 10 381 | ... ............ .... ....... .... . .. 382 | ``` 383 | 384 | Which then can be used for things like making `ggplot2` figures. 385 | 386 | ```r 387 | # case vs control boxplot by chromosome 388 | 389 | library(ggplot2) 390 | ggplot(stack(X), aes(x=group, y=value, color=group)) + 391 | facet_wrap(~chr) + 392 | geom_boxplot() + 393 | theme_bw() 394 | ``` 395 | 396 | ![ggplot](http://karolis.koncevicius.lt/data/annmatrix/ggplot.png) 397 | 398 | 399 | ## Technical Details ## 400 | 401 | `annmatrix` uses R's S3 class system to extend the base `matrix` class in order to provide it with persistent annotations that are associated with rows and columns. 402 | Technically `annmatrix` object is just a regular R `matrix` with additional `data.frame` attributes `.annmatrix.rann` and `.annmatrix.cann` that are preserved after sub-setting and other matrix-specific operations. 403 | As a result, every function that works on a `matrix` by design should work the same way with `annmatrix`. 404 | 405 | 406 | ## Installation ## 407 | 408 | Directly from CRAN: 409 | 410 | ```r 411 | install.packages("annmatrix") 412 | ``` 413 | 414 | Or from GitHub, using `remotes` library: 415 | 416 | ```r 417 | remotes::install_github("karoliskoncevicius/annmatrix") # CRAN version 418 | remotes::install_github("karoliskoncevicius/annmatrix@dev") # stable changes not yet on CRAN 419 | ``` 420 | 421 | 422 | ## See Also ## 423 | 424 | Similar ideas can be found in: 425 | 426 | 1. [Henrik Bengtsson's "wishlist for R"](https://github.com/HenrikBengtsson/Wishlist-for-R/issues/2) 427 | 2. [BioConductor's AnnotatedDataFrame object](https://www.rdocumentation.org/packages/Biobase/versions/2.32.0/topics/AnnotatedDataFrame) 428 | 429 | --------------------------------------------------------------------------------