[loomr.git] / R / package.R
1 #' @importFrom methods setOldClass
4 #' An R interface for loom files
5 #'
6 #' loomR provides an interface for working with loom files in a loom-specific way.
7 #' We provide routines for validating loom files,
8 #' iterating with chunks through data within the loom file,
9 #' and provide a platform for other packages to build support for loom files.
10 #' Unlike other HDF5 pacakges, loomR actively protectes a loom file's structure, enabling the
11 #' user to focus on their analysis and not worry about the integrity of their data.
12 #'
13 #' @section Semantics:
14 #' Throughout all loomR-related documentation and writing, the following styles for distinguising between loom files,
15 #' \code{loom} objects, and loomR will and be used. When talking about loom files, or the actual HDF5 file on disk,
16 #' the word 'loom' will be written in normal text. Capitalization will be done based on a language's rules for
17 #' capitalization in sentences. For English, that means if the word 'loom' appears at the beginning of a sentence
18 #' and is being used to refer to a loom file, it will be capilatized. Otherwise, it will be lowercase.
19 #' For \code{loom} objects, or the object within R, the word 'loom' will always be lowercase and written in monospaced text.
20 #' When referring to the pacakge loomR, it will always be written in normal text with the 'l', 'o's, and 'm' lowercased and
21 #' the 'R' uppercased. This style will be used throughout documentation for loomR as well as any vignettes and tutorials
22 #' produced by the authors.
23 #'
24 #' @section Loom Files:
25 #' Loom files are an HDF5-based format for storing and interacting with large single-cell RNAseq datasets.
26 #' Each loom file has at least six parts to it:
27 #' the raw expression data (\code{matrix}),
28 #' groups for gene- and cell-metadata (\code{row_attrs} and \code{col_attrs}, respectively),
29 #' groups for gene-based and cell-based cluster graphs (\code{row_graphs} and \code{col_graphs}, respectively),
30 #' and \code{layers}, a group containing alternative representations of the data in \code{matrix}.
31 #' Each dataset within the loom file has rules as to what size it may be, creating a structure for the entire loom file and all the data within.
32 #' This structure is enforced to ensure that data remains intact and retriveable when spread across the various datasets in the loom file.
33 #'
34 #' \describe{
35 #'   \item{\code{matrix}}{
36 #'     The dataset that sets the dimensions for most other datasets within a loom file. This dataset has 'n' genes and 'm' cells.
37 #'     Due to the way that loomR presents data, this will appear as 'm' rows and 'n' columns. However, other HDF5 libraries will
38 #'     generally present the data as 'n' rows and 'm' columns
39 #'   }
40 #'   \item{\code{row_attrs} and \code{col_attrs}}{
41 #'     These are one- or two-dimensional datasets where a specific dimension is of length 'n', for row attributes, or 'm', for column attributes.
42 #'     Within loomR, this must be the second dimension of two-dimensional datasets, or the length of one-dimensional datasets Most other
43 #'     HDF5 libraries will show this specific dimension as the first dimension for two-dimensional datasets, or the length of one-dimensional
44 #'     datasets.
45 #'   }
46 #'   \item{\code{row_graphs} and \code{col_graphs}}{
47 #'     Unlike other datasets within a loom file, these are not controlled by \code{matrix}. Instead, within these groups are groups for
48 #'     specific graphs. Each graph group will have three datasets that represent the graph in
49 #'     \href{https://en.wikipedia.org/wiki/Sparse_matrix#Coordinate_list_(COO)}{coordinate format}: \code{a} for row indices, \code{b} for
50 #'     column indices, and \code{w} for values. Each dataset within a graph must be one-dimensional and all datasets within a graph must be
51 #'     the same length. Not all graphs must be the same length as each other.
52 #'   }
53 #'   \item{\code{layers}}{Each dataset within \code{layers} must have the exact same dimensions as \code{matrix}}
54 #' }
55 #'
56 #' @section Chunk-based iteration:
57 #' As loom files can theoretically hold million-cell datasets, performing analysis on these datasets can be impossible due to the memory
58 #' requirements for holding such a dataset in memory. To combat this problem, \code{loom} objects offer native chunk-based iteration through
59 #' the \code{batch.scan}, \code{batch.next}, \code{map}, and \code{apply} methods. This section will cover the former two methods; the latter
60 #' two are covered in the \href{http://satijalab.org/loomR/loomR_tutorial.html}{loomR tutorial}.
61 #'
62 #' \code{batch.scan} and \code{batch.next} are the heart of all chunk-based iteration in the \code{loom} object. These two methods make
63 #' use of \code{\link{itertools::ichunk}} object to chunk through the data in a loom file. Due to the way that R works, \code{batch.scan}
64 #' initializes the iterator and \code{batch.next} moves through the iterator.
65 #'
66 #' The \code{batch.scan} method will break a dataset in the loom file into chunks, based on a chunk size given to it. \code{batch.scan} will
67 #' work on any dataset, except for two-dimensional attributes and any graph dataset. When iterating over \code{matrix} and the layers, the \code{MARGIN}
68 #' argument tells the \code{loom} object which way to chunk the data. A \code{MARGIN} of 1 will chunk over genes while a \code{MARGIN} of 2 will chunk
69 #' over cells. For one-dimmensional attributes, \code{MARGIN} is ignored. \code{batch.scan} returns an integer whose length is the number of iterations
70 #' it takes to iterate over the dataset selected.
71 #'
72 #' Pulling data in chunks is done by \code{batch.next}. This method simply returns the next chunk of data. If \code{return.data = FALSE} is passed,
73 #' \code{batch.next} will instead return the indices of the next chunk. When using these methods, we recommend storing the results of \code{batch.scan}
74 #' and iterating through this vector to keep track of where the \code{loom} object is in the iteration.
75 #' \preformatted{
76 #'   # Set up the iterator on the `loom` object lfile
77 #'   batch <- lfile$batch.scan(dataset.use = 'matrix', MARGIN = 2)
78 #'   # Iterate through the dataset, pulling data
79 #'   # If `return.data = FALSE` is passed, the indices
80 #'   # of the next chunk will be returned instead
81 #'   for (i in batch) {
82 #'     data.use <- lfile$batch.next()
83 #'   }
84 #' }
85 #'
86 #' @section Extending loomR:
87 #' The \code{loom} class is the heart of loomR. This class is written in the
88 #' \href{https://cran.r-project.org/web/packages/R6/vignettes/Introduction.html}{R6} object style and can be extended in three ways.
89 #' For each of the following, one be discretionary when \code{return} is used instead of \code{\link{invisible}}. As \code{loom} object are merely
90 #' handles to loom files, any function or method that modifies the file should not need to return anything. However, we recommend always returning
91 #' the \code{loom} object invisibly, using \code{\link{invisible}}. While not necessary for functionality, it means that objects in a user's environment
92 #' won't get overwritten if they try to reassign their \code{loom} object to the output of a function. For functions and methods that don't modify the
93 #' loom file, and instead return data, then the \code{return} function should be used.
94 #'
95 #' The first way to extend \code{loom} objects is by subclassing the object and making a new R6 class. This allows new classes to
96 #' declare custom R6 methods and gain access to all of the \code{loom} object's methods, including S3- and S4-style methods.
97 #' New classes can also overwrite any methods for \code{loom} objects, allowing the extender to change the core behaviour of \code{loom} objects.
98 #' While this option allows the greatest control and access to the \code{loom} object, it involves the greatest amount of work
99 #' as one would need to write a new R6 class and all the associated boilerplate code. As such, we recommend subclassing \code{loom} objects
100 #' when a new class is needed, but would advise developers to use the other methods of extending \code{loom} objects for simpler tasks.
101 #'
102 #' The second way is by using S4-style methods can be written for \code{loom} objects. loomR exports the \code{loom} class as an S4 class, allowing
103 #' one to write highly-specialized methods that enforce class-specificity and can change behaviour based on the classes of other objects provided to
104 #' a function. S4 methods look like normal functions to the end user, but can do different things based on the class, or classes, of objects passed to it.
105 #' This allows for highly-customized routines without cluttering a package's namespace, as only the generic function is exported. S4 methods can also be
106 #' written for generics exported by other packages, assuming the said generic has been imported before writing new methods. Furthermore, generics
107 #' and methods can be kept internally, and R will dispatch the appropriate method as if the generic was exported. However, S4 methods have the drawback
108 #' of not autocompleting arguments in the terminal or RStudio. This means that the user may need to keep documentation open while using these methods,
109 #' which detracts from the user-friendliness of these methods. Finally, while there is less boilerplate in declaring S4 generics and methods than
110 #' declaring R6 classes and methods, there is still more to write than our last method. As such, we recommend S4 methods for anyone who needs method
111 #' dispatch for internal functions only.
112 #' \preformatted{
113 #'   #' @export SomeFunction
114 #'   methods::setGeneric(
115 #'     name = 'SomeFunction',
116 #'     def = function(object, ...) {
117 #'       return(standardGeneric(f = 'SomeFunction))
118 #'     }
119 #'   )
120 #'
121 #'   # Note, no extra Roxygen notes needed
122 #'   methods::setMethod(
123 #'     f = 'SomeFunction',
124 #'     signature = c('object' = 'loom'),
125 #'     definition = function(object, loom.param, ...) {
126 #'       # do something
127 #'     }
128 #'   )
129 #' }
130 #'
131 #' As R6 objects are based on S3 objects, the final way to extend \code{loom} objects is by writing S3-style methods. These methods involve the
132 #' least amount of boilerplate to set up. S3 generics are written just like normal functions, albiet with a few differences. Firstly, they have
133 #' two arguments: the argument that determines the class for dispatching and \code{...} to pass other arguments to methods. Finally, the only
134 #' thing an S3 generic needs to do is call \code{UseMethod} to allow R to dispatch based on the class of whatever the object is. Unlike S4 methods,
135 #' S3 methods provide tab-autocompletion for method-specific arguments, providing help messages along the way. This means that S3 methods are more
136 #' user-friendly than S4 methods. Like S4 methods, S3 methods can use S3 generics declared by other packages, with the same assumptions about
137 #' imports applying here as well. However, S3 methods cannot be kept internally, and must be exported for R to properly dispatch the method. This means
138 #' that a package's namespace will have n + 1 functions declared for every S3 generic, where n is the number of classes a method is declared for and the
139 #' one extra is for the generic. Furthermore, as the methods themselves are exported, anyone can simply use the method directly rather than go through
140 #' the generic and have R dispatch a method based on object class. Despite these drawbacks, S3 methods are how we recommend one extends loomR unless
141 #' one needs the specific features of R6 classes or S4-style methods.
142 #' \preformatted{
143 #'   #' @export somefunction
144 #'   somefunction <- function(object, ...) {
145 #'     UseMethod('somefunction', object)
146 #'   }
147 #'
148 #'   #' @export somefunction.loom
149 #'   #' @method somefunction loom
150 #'   somefunction.loom <- function(object, loom.param, ...) {
151 #'     # do something
152 #'   }
153 #' }
154 #'
155 #' @docType package
156 #' @name loomR-package
157 #'
158 NULL
161 # Hooks to set loom as an S4 class upon
162 # loadNamespace or library/require
163 .onLoad <- function(libname, pkgname) {
164   setOldClass(Classes = 'loom')
165 }