diff --git a/DESCRIPTION b/DESCRIPTION index 03548227..d8ec696b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -39,13 +39,13 @@ VignetteBuilder: knitr Encoding: UTF-8 LinkingTo: Rcpp, RcppArmadillo, RcppEigen, RcppProgress Depends: - Matrix, methods, stats, utils, R (>= 3.4) Imports: circlize, + cli, cowplot, ComplexHeatmap, dplyr, @@ -55,6 +55,7 @@ Imports: leidenAlg (>= 1.1.1), lifecycle, magrittr, + Matrix, RANN, RColorBrewer, Rcpp, diff --git a/NAMESPACE b/NAMESPACE index 5c634ada..84bd3aaa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +S3method("[",liger) S3method("[",ligerDataset) S3method("[[",liger) S3method(.DollarNames,liger) @@ -189,7 +190,6 @@ exportClasses(ligerRNADataset) exportClasses(ligerSpatialDataset) exportMethods("$") exportMethods("$<-") -exportMethods("[") exportMethods("cellMeta<-") exportMethods("coordinate<-") exportMethods("dataset<-") @@ -234,6 +234,9 @@ exportMethods(scaleUnsharedData) exportMethods(show) exportMethods(varFeatures) exportMethods(varUnsharedFeatures) +importClassesFrom(Matrix,dgCMatrix) +importClassesFrom(Matrix,dgTMatrix) +importClassesFrom(Matrix,dgeMatrix) importClassesFrom(S4Vectors,DataFrame) importFrom(Matrix,colSums) importFrom(Matrix,rowSums) diff --git a/R/ATAC.R b/R/ATAC.R index aa609a32..b3867a3e 100644 --- a/R/ATAC.R +++ b/R/ATAC.R @@ -50,33 +50,28 @@ imputeKNN <- function( knn_k = nNeighbors ) { .deprecateArgs(list(knn_k = "nNeighbors"), defunct = "scale") - # if (!requireNamespace("FNN", quietly = TRUE)) { - # stop("Package \"foreach\" needed for this function to work. ", - # "Please install it by command:\n", - # "install.packages('FNN')", - # call. = FALSE) - # } if (is.null(getMatrix(object, "H.norm"))) - stop("Aligned factor loading has to be available for imputation. ", - "Please run `quantileNorm()` in advance.") - - if (length(reference) > 1) { - stop("Can only have ONE reference dataset") - } - reference <- .checkUseDatasets(object, reference) - if (!inherits(dataset(object, reference), "ligerATACDataset")) - stop("Selected reference should be ATAC dataset.") + cli::cli_abort( + "Aligned factor loading has to be available for imputation. + Please run {.fn quantileNorm} in advance.") + reference <- .checkArgLen(reference, n = 1) + reference <- .checkUseDatasets(object, reference)#, modal = "atac") queries <- .checkUseDatasets(object, queries) if (any(queries %in% reference)) { - warning("Reference dataset cannot be inclued in the query ", - "datasets. Removed from query list.") + cli::cli_alert_warning( + "Reference dataset cannot be inclued in the query datasets." + ) + cli::cli_alert_warning( + "Removed from query list: {.val {queries[queries %in% reference]}}" + ) queries <- queries[!queries %in% reference] } object <- recordCommand(object, ..., dependencies = c("RANN", "Matrix")) if (isTRUE(verbose)) { - .log("Imputing all the datasets exept the reference dataset\n", - "Reference dataset: ", reference, "\n", - "Query datasets: ", paste(queries, collapse = ", ")) + cli::cli_alert_info( + "Imputing {length(queries)} query dataset{?s}: {.val {queries}}" + ) + cli::cli_alert_info("from reference dataset: {.val {reference}}") } referenceCells <- colnames(dataset(object, reference)) @@ -194,35 +189,36 @@ linkGenesAndPeaks <- function( ) { ## check dependency if (!requireNamespace("GenomicRanges", quietly = TRUE)) - stop("Package \"GenomicRanges\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('GenomicRanges')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg GenomicRanges} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('GenomicRanges')}") if (!requireNamespace("IRanges", quietly = TRUE)) - stop("Package \"IRanges\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('IRanges')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg IRanges} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('IRanges')}") if (!requireNamespace("psych", quietly = TRUE)) - stop("Package \"psych\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('psych')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg psych} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('psych')}") .deprecateArgs(list(path_to_coords = "pathToCoords", genes.list = "useGenes", dist = "method")) method <- match.arg(method) - if (length(useDataset) != 1) - stop("Please select only one dataset") useDataset <- .checkUseDatasets(object, useDataset) + if (length(useDataset) != 1) + cli::cli_abort("Please select only one dataset") lad <- dataset(object, useDataset) if (!inherits(lad, "ligerATACDataset")) - stop("Specified dataset is not of `ligerATACDataset` class. ", - "Please try `imputeKNN()` with `query = ", useDataset, "`. ") + cli::cli_abort( + "Specified dataset is not of `ligerATACDataset` class. + Please try {.fn imputeKNN} with `query = '{useDataset}'`.") if (is.null(normData(lad))) - stop("Normalized gene expression not found in specified dataset.") + cli::cli_abort("Normalized gene expression not found in specified dataset.") if (is.null(normPeak(lad))) - stop("Normalized peak counts not found in specified dataset.") + cli::cli_abort("Normalized peak counts not found in specified dataset.") ### make GRanges object for peaks peakCounts <- normPeak(lad) @@ -257,23 +253,26 @@ linkGenesAndPeaks <- function( if (is.null(useGenes)) useGenes <- colnames(geneCounts) missingGenes <- !useGenes %in% names(genesCoords) if (sum(missingGenes) != 0 && isTRUE(verbose)) - .log("Ignoring ",sum(missingGenes), - " genes not found in given gene coordinates") - + cli::cli_alert_warning( + "Ignoring {sum(missingGenes)} genes not found in given gene coordinates" + ) useGenes <- useGenes[!missingGenes] - if (length(useGenes) == 0) - stop("Number of genes to be tested equals 0. Please check input ", - "`useGenes` or the coordinate file.") - else { - .log(length(useGenes), " genes to be tested against ", ncol(peakCounts), - " peaks") + if (length(useGenes) == 0) { + cli::cli_abort( + "Number of genes to be tested equals 0. Please check input + {.code useGenes} or the coordinate file." + ) + } else { + cli::cli_alert_info( + "{length(useGenes)} genes to be tested against {ncol(peakCounts)} peaks" + ) } genesCoords <- genesCoords[useGenes] ### construct regnet if (isTRUE(verbose)) { - .log("Calculating correlation for gene-peak pairs...") - pb <- utils::txtProgressBar(0, length(useGenes), style = 3) + cli::cli_alert_info("Calculating correlation for gene-peak pairs...") + cli::cli_progress_bar("", total = length(useGenes), type = "iter") } # Result would be a sparse matrix, initialize the `i`, `p`, `x` vectors. @@ -320,9 +319,10 @@ linkGenesAndPeaks <- function( ind <- c(ind, as.numeric(peaks.use)) indp <- c(indp, as.numeric(eachLen)) values <- c(values, res.corr) - if (isTRUE(verbose)) utils::setTxtProgressBar(pb, pos) + if (isTRUE(verbose)) { + cli::cli_progress_update(set = pos) + } } - if (isTRUE(verbose)) cat("\n") # make final sparse matrix regnet <- Matrix::sparseMatrix( i = ind, p = c(0, indp), x = values, @@ -379,20 +379,22 @@ exportInteractTrack <- function( if (is.null(useGenes)) { useGenes <- colnames(corrMat) } else if (any(!useGenes %in% colnames(corrMat))) { - .log("Removed ", sum(!useGenes %in% colnames(corrMat)), " genes not ", - "found in `corrMat`") + cli::cli_alert_warning( + "Removed {sum(!useGenes %in% colnames(corrMat))} genes not found in {.code corrMat}" + ) useGenes <- useGenes[useGenes %in% colnames(corrMat)] } # Filter useGenes by significance geneSel <- Matrix::colSums(corrMat[, useGenes, drop = FALSE] != 0) > 0 if (length(useGenes) - sum(geneSel) > 0) - .log("Totally ", length(useGenes) - sum(geneSel), " selected genes do ", - "not have significant correlated peaks, out of ", length(useGenes), - " selected genes") + cli::cli_alert_warning( + "Totally {length(useGenes) - sum(geneSel)} selected genes do not have significant correlated peaks, out of {length(useGenes)} selected genes", + wrap = TRUE + ) useGenes <- useGenes[geneSel] if (length(useGenes) == 0) { - stop("No gene requested is either available or ", - "having significant correlated peaks. ") + cli::cli_abort("No gene requested is either available or having + significant correlated peaks. ") } ### make GRanges object for genes @@ -414,8 +416,6 @@ exportInteractTrack <- function( if (!file.exists(outputPath)) file.create(outputPath) outputPath <- normalizePath(outputPath) - .log("Writing result to: ", outputPath) - # Start writing BED file trackDoc <- paste0('track type=interact name="Interaction Track" ', 'description="Gene-Peaks Links" ', @@ -459,6 +459,8 @@ exportInteractTrack <- function( fileEncoding = "" ) } + cli::cli_alert_success("Result written at: {.file {outputPath}}") + invisible(NULL) } #' [Deprecated] Export predicted gene-pair interaction diff --git a/R/DEG_marker.R b/R/DEG_marker.R index 69978dfb..2916215c 100644 --- a/R/DEG_marker.R +++ b/R/DEG_marker.R @@ -96,7 +96,7 @@ runPairwiseDEG <- function( groups <- list(group1Idx, group2Idx) names(groups) <- c(group1Name, group2Name) } else { - stop("Please see `?runPairwiseDEG` for usage.") + cli::cli_abort("Please see {.code ?runPairwiseDEG} for usage.") } result <- .runDEG(object, groups = groups, method = method, usePeak = usePeak, useReplicate = useReplicate, @@ -161,7 +161,7 @@ runMarkerDEG <- function( allCellIdx <- seq(ncol(object))[object$dataset %in% useDatasets] conditionBy <- conditionBy %||% object@uns$defaultCluster if (is.null(conditionBy)) { - stop("No `conditionBy` given or default cluster not set.") + cli::cli_abort("No {.var conditionBy} given or default cluster not set.") } conditionBy <- .fetchCellMetaVar( object, conditionBy, cellIdx = allCellIdx, @@ -241,6 +241,8 @@ runWilcoxon <- function( ) { method <- match.arg(method) allCellIdx <- unlist(groups) + if (length(allCellIdx) == 0) + cli::cli_abort(c(x = "No cell selected")) allCellBC <- colnames(object)[allCellIdx] datasetInvolve <- levels(object$dataset[allCellIdx, drop = TRUE]) var <- factor(rep(names(groups), lengths(groups)), levels = names(groups)) @@ -258,8 +260,10 @@ runWilcoxon <- function( mat <- Reduce(cbind, dataList) mat <- mat[, allCellBC, drop = FALSE] if (method == "wilcoxon") { + cliID <- cli::cli_process_start("Running Wilcoxon rank-sum test") mat <- log1p(1e10*mat) result <- wilcoxauc(mat, var) + cli::cli_process_done(id = cliID) } else if (method == "pseudoBulk") { if (is.null(useReplicate)) { replicateAnn <- setupPseudoRep(var, nRep = nPsdRep, @@ -292,18 +296,20 @@ runWilcoxon <- function( .DE.checkDataAvail <- function(object, useDatasets, method, usePeak) { if (isH5Liger(object, useDatasets)) { # nocov start - stop("HDF5 based datasets detected but is not supported. \n", - "Try `object.sub <- downsample(object, useSlot = ", - "'normData')` to create ANOTHER object with in memory data.") + cli::cli_abort( + c("HDF5 based datasets detected but is not supported. ", + "i" = "Try {.code object.sub <- downsample(object, useSlot = 'normData')} to create another object with in memory data") + ) } # nocov end if (method == "wilcoxon") { slot <- ifelse(usePeak, "normPeak", "normData") } else if (method == "pseudoBulk") { if (!requireNamespace("DESeq2", quietly = TRUE)) # nocov start - stop("Package \"DESeq2\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('DESeq2')", - call. = FALSE) # nocov end + cli::cli_abort( + "Package {.pkg DESeq2} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('DESeq2')}" + ) # nocov end slot <- ifelse(usePeak, "rawPeak", "rawData") } allAvail <- all(sapply(useDatasets, function(d) { @@ -311,8 +317,10 @@ runWilcoxon <- function( !is.null(methods::slot(ld, slot)) })) if (!allAvail) - stop(slot, " not all available for involved datasets. [method = \"", - method, "\", usePeak = ", usePeak, "]") + cli::cli_abort( + c("{.field {slot}} not all available for involved datasets: {.val {useDatasets}}", + "i" = "{.code method = '{method}'}; {.code usePeak = {usePeak}}") + ) return(slot) } @@ -345,9 +353,10 @@ makePseudoBulk <- function(mat, replicateAnn, minCellPerRep, verbose = TRUE) { subrep <- replicateAnn[replicateAnn$groups == gr,] splitLabel <- interaction(subrep, drop = TRUE) if (nlevels(splitLabel) < 2) { - stop("Too few replicates label for condition \"", gr, "\". ", - "Cannot not create pseudo-bulks. Please use ", - "consider creating pseudo-replicates or use wilcoxon instead.") + cli::cli_abort( + c("Too few replicates for condition {.val {gr}}. Cannot create pseudo-bulks.", + "i" = "Please consider creating pseudo-replicates or using {.code method = 'wilcoxon'} instead.") + ) } } splitLabel <- interaction(replicateAnn, drop = TRUE) @@ -360,10 +369,9 @@ makePseudoBulk <- function(mat, replicateAnn, minCellPerRep, verbose = TRUE) { mat <- mat[, idx, drop = FALSE] replicateAnn <- replicateAnn[idx, , drop = FALSE] if (verbose) { - .log("Ignoring replicates with too few cells: ", - paste0(ignored, collapse = ", ")) - .log("Replicate size:") - .log(paste0(levels(splitLabel), ": ", table(splitLabel), collapse = ", "), level = 2) + if (length(ignored) > 0) cli::cli_alert_warning("Ignoring replicates with too few cells: {.val {ignored}}") + cli::cli_alert_info("Replicate sizes:") + print(table(splitLabel)) } pseudoBulks <- colAggregateSums_sparse(mat, as.integer(splitLabel) - 1, nlevels(splitLabel)) @@ -375,7 +383,7 @@ makePseudoBulk <- function(mat, replicateAnn, minCellPerRep, verbose = TRUE) { .callDESeq2 <- function(pseudoBulks, groups, verbose = getOption("ligerVerbose")) { # DESeq2 workflow - if (isTRUE(verbose)) .log("Calling DESeq2 Wald test") + if (isTRUE(verbose)) cliID <- cli::cli_process_start("Calling DESeq2 Wald test") ## NOTE: DESeq2 wishes that the contrast/control group is the first level ## whereas we required it as the second in upstream input. So we need to ## reverse it here. @@ -396,6 +404,7 @@ makePseudoBulk <- function(mat, replicateAnn, minCellPerRep, verbose = TRUE) { res$group <- levels(groups)[2] res <- res[, c(7, 8, 2, 5, 6)] colnames(res) <- c("feature", "group", "logFC", "pval", "padj") + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) return(res) } diff --git a/R/DoubletFinder.R b/R/DoubletFinder.R index fa2e2f3d..1dc4c3dc 100644 --- a/R/DoubletFinder.R +++ b/R/DoubletFinder.R @@ -41,27 +41,29 @@ runDoubletFinder <- function( ... ) { if (!requireNamespace("DoubletFinder", quietly = TRUE)) { # nocov start - stop("DoubletFinder need to be installed. Please run:\n", - "remotes::install_github('chris-mcginnis-ucsf/DoubletFinder')") + cli::cli_abort( + "Package {.pkg DoubletFinder} is needed for this function to work. + Please install it by command: + {.code remotes::install_github('DoubletFinder')}") } if (!requireNamespace("Seurat", quietly = TRUE)) { - stop("Seurat need to be installed. Please run:\n", - "install.packages(\"Seurat\")") + cli::cli_abort( + "Package {.pkg Seurat} is needed for this function to work. + Please install it by command: + {.code install.packages('Seurat')}") } # nocov end useDatasets <- .checkUseDatasets(object, useDatasets = useDatasets) - nNeighbors <- .checkArgLen(nNeighbors, length(useDatasets), repN = TRUE) - if (!is.null(nExp)) { - nExp <- .checkArgLen(nExp, length(useDatasets), repN = TRUE) - } else { - nExp <- sapply(useDatasets, function(d) { + nNeighbors <- .checkArgLen(nNeighbors, length(useDatasets), repN = TRUE, class = "numeric") + nExp <- .checkArgLen(nExp, length(useDatasets), repN = TRUE, class = "numeric") + if (is.null(nExp)) + nExp <- sapply(useDatasets, function(d) round(0.15 * ncol(dataset(object, d))) - }) - } + ) object <- recordCommand(object, ..., dependencies = c("Seurat", "DoubletFinder")) for (i in seq_along(useDatasets)) { d <- useDatasets[i] - if (isTRUE(verbose)) .log("Running DoubletFinder on dataset: ", d) + if (isTRUE(verbose)) cliID <- cli::cli_process_start("Running DoubletFinder on dataset {.val {d}}") seu <- Seurat::CreateSeuratObject(rawData(object, d)) %>% Seurat::NormalizeData(verbose = FALSE) %>% Seurat::FindVariableFeatures(verbose = FALSE) %>% @@ -74,6 +76,7 @@ runDoubletFinder <- function( DFCol <- grep(pattern = "DF.classifications", colnames(seuMeta)) cellMeta(object, "DoubletFinder_pANN", useDatasets = d) <- seuMeta[,pANNCol] cellMeta(object, "DoubletFinder_classification", useDatasets = d) <- seuMeta[,DFCol] + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) } return(object) } diff --git a/R/GSEA.R b/R/GSEA.R index 04c0bcc1..dc6a0899 100644 --- a/R/GSEA.R +++ b/R/GSEA.R @@ -32,22 +32,22 @@ runGSEA <- function( custom_gene_sets = customGenesets ) { if (!requireNamespace("org.Hs.eg.db", quietly = TRUE)) # nocov start - stop("Package \"org.Hs.eg.db\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('org.Hs.eg.db')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg org.Hs.eg.db} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('org.Hs.eg.db')}") if (!requireNamespace("reactome.db", quietly = TRUE)) - stop("Package \"reactome.db\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('reactome.db')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg reactome.db} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('reactome.db')}") if (!requireNamespace("fgsea", quietly = TRUE)) - stop("Package \"fgsea\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('fgsea')", - call. = FALSE) # nocov end + cli::cli_abort( + "Package {.pkg fgsea} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('fgsea')}") # nocov end .deprecateArgs(list(gene_sets = "genesets", mat_w = "useW", @@ -164,14 +164,14 @@ runGOEnrich <- function( ... ) { if (!requireNamespace("gprofiler2", quietly = TRUE)) # nocov start - stop("Package \"gprofiler2\" needed for this function to work. ", - "Please install it by command:\n", - "install.packages('gprofiler2')", - call. = FALSE) # nocov end + cli::cli_abort( + "Package {.pkg gprofiler2} is needed for this function to work. + Please install it by command: + {.code install.packages('gprofiler2')}") # nocov end + group <- group %||% unique(result$group) if (any(!group %in% result$group)) { - stop("Selected groups not available `result$group`: ", - paste(group[!group %in% result$group], collapse = ", ")) + cli::cli_abort("Selected groups not available in {.code result$group}: {.val {group[!group %in% result$group]}}") } bg <- NULL domain_scope <- "annotated" # gprofiler2 default @@ -190,9 +190,9 @@ runGOEnrich <- function( ordered_query <- FALSE if (!is.null(orderBy)) { ordered_query <- TRUE - if (length(orderBy) > 1) stop("Only one `orderBy` metric allowed") + if (length(orderBy) > 1) cli::cli_abort("Only one {.code orderBy} metric allowed") if (!orderBy %in% c("logFC", "pval", "padj")) { - stop("`orderBy` should be one of 'logFC', 'pval' or 'padj'.") + cli::cli_abort("{.code orderBy} should be one of {.val logFC}, {.val pval} or {.val padj}.") } if (orderBy == "logFC") { resultUp <- resultUp[order(resultUp$logFC, decreasing = TRUE),] diff --git a/R/zzz.R b/R/aaa.R similarity index 100% rename from R/zzz.R rename to R/aaa.R diff --git a/R/classConversion.R b/R/classConversion.R index b451f906..3a0747ae 100644 --- a/R/classConversion.R +++ b/R/classConversion.R @@ -1,68 +1,6 @@ -setClass("ligerDataset") -setClassUnion("matrixLike", c("matrix", "dgCMatrix", "dgTMatrix", "dgeMatrix")) - #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # From other things to liger class #### #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#' Converting other classes of data to a liger object -#' @description -#' This function converts data stored in SingleCellExperiment (SCE), Seurat -#' object or a merged sparse matrix (dgCMatrix) into a liger object. This is -#' designed for a container object or matrix that already contains multiple -#' datasets to be integerated with LIGER. For individual datasets, please use -#' \code{\link{createLiger}} instead. -#' @export -#' @param object Object. -#' @param datasetVar Specify the dataset belonging by: 1. Select a variable from -#' existing metadata in the object (e.g. colData column); 2. Specify a -#' vector/factor that assign the dataset belonging. 3. Give a single character -#' string which means that all data is from one dataset (must not be a metadata -#' variable, otherwise it is understood as 1.). Default \code{NULL} gathers -#' things into one dataset and names it "sample" for dgCMatrix, attempts -#' to find variable "sample" from SCE or "orig.ident" from Seurat. -#' @param modal Modality setting for each dataset. See -#' \code{\link{createLiger}}. -#' @param ... Additional arguments passed to \code{\link{createLiger}} -#' @details -#' For Seurat V5 structure, it is highly recommended that users make use of its -#' split layer feature, where things like "counts", "data", and "scale.data" -#' can be held for each dataset in the same Seurat object, e.g. with -#' "count.ctrl", "count.stim", not merged. If a Seurat object with split layers -#' is given, \code{datasetVar} will be ignored and the layers will be directly -#' used. -#' @return a \linkS4class{liger} object. -#' @rdname as.liger -#' @examples -#' # dgCMatrix (common sparse matrix class), usually obtained from other -#' # container object, and contains multiple samples merged in one. -#' matList <- rawData(pbmc) -#' multiSampleMatrix <- mergeSparseAll(matList) -#' # The `datasetVar` argument expects the variable assigning the sample source -#' pbmc2 <- as.liger(multiSampleMatrix, datasetVar = pbmc$dataset) -#' pbmc2 -#' -#' sce <- SingleCellExperiment::SingleCellExperiment( -#' assays = list(counts = multiSampleMatrix) -#' ) -#' sce$sample <- pbmc$dataset -#' pbmc3 <- as.liger(sce, datasetVar = "sample") -#' pbmc3 -#' -#' seu <- SeuratObject::CreateSeuratObject(multiSampleMatrix) -#' # Seurat creates variable "orig.ident" by identifying the cell barcode -#' # prefixes, which is indeed what we need in this case. Users might need -#' # to be careful and have it confirmed first. -#' pbmc4 <- as.liger(seu, datasetVar = "orig.ident") -#' pbmc4 -#' -#' # As per Seurat V5 updates with layered data, specifically helpful udner the -#' # scenario of dataset integration. "counts" and etc for each datasets can be -#' # split into layers. -#' seu5 <- seu -#' seu5[["RNA"]] <- split(seu5[["RNA"]], pbmc$dataset) -#' print(SeuratObject::Layers(seu5)) -#' pbmc5 <- as.liger(seu5) -as.liger <- function(object, ...) UseMethod("as.liger", object) #' @rdname as.liger #' @export @@ -80,7 +18,7 @@ as.liger.dgCMatrix <- function( datasetVar <- droplevels(datasetVar) rawDataList <- splitRmMiss(object, datasetVar) - modal <- .checkArgLen(modal, length(rawDataList)) + modal <- .checkArgLen(modal, length(rawDataList), class = "character") createLiger(rawData = rawDataList, modal = modal, ...) } @@ -94,15 +32,17 @@ as.liger.SingleCellExperiment <- function( ... ) { if (!requireNamespace("SingleCellExperiment", quietly = TRUE)) # nocov start - stop("Package \"SingleCellExperiment\" needed for this function ", - "to work. Please install it by command:\n", - "BiocManager::install('SingleCellExperiment')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg SingleCellExperiment} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('SingleCellExperiment')}" + ) if (!requireNamespace("SummarizedExperiment", quietly = TRUE)) - stop("Package \"SummarizedExperiment\" needed for this function ", - "to work. Please install it by command:\n", - "BiocManager::install('SummarizedExperiment')", - call. = FALSE) # nocov end + cli::cli_abort( + "Package {.pkg SummarizedExperiment} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('SummarizedExperiment')}" + ) # nocov end raw <- SingleCellExperiment::counts(object) if (is.null(datasetVar)) { @@ -121,7 +61,7 @@ as.liger.SingleCellExperiment <- function( if (!is.factor(datasetVar)) datasetVar <- factor(datasetVar) datasetVar <- droplevels(datasetVar) raw <- splitRmMiss(raw, datasetVar) - modal <- .checkArgLen(modal, length(raw)) + modal <- .checkArgLen(modal, length(raw), class = "character") lig <- createLiger(raw, modal = modal, ...) colDataCopy <- SummarizedExperiment::colData(object) for (cdn in colnames(colDataCopy)) { @@ -129,8 +69,8 @@ as.liger.SingleCellExperiment <- function( same <- identical(colDataCopy[[cdn]], cellMeta(lig, cdn)) if (same) next cdnNew <- paste0("SCE_", cdn) - warning("Variable name \"", cdn, "\" in colData of SingleCellExperiment ", - "conflicts with liger default variables. Modified to ", cdnNew, ".") + cli::cli_alert_warning( + "Variable name {.val {cdn}} in colData of SingleCellExperiment conflicts with liger default variables. Modified to {.val {cdnNew}}.") } else { cdnNew <- cdn } @@ -179,7 +119,7 @@ as.liger.Seurat <- function( } datasetVar <- datasetVar %||% "Seurat" - modal <- .checkArgLen(modal, length(raw)) + modal <- .checkArgLen(modal, length(raw), class = "character") lig <- createLiger(raw, modal = modal, ...) colnames(object) <- colnames(lig) for (cdn in colnames(object[[]])) { @@ -187,8 +127,8 @@ as.liger.Seurat <- function( same <- identical(object[[cdn, drop = TRUE]], cellMeta(lig, cdn)) if (same) next cdnNew <- paste0("Seurat_", cdn) - warning("Variable name \"", cdn, "\" in meta.data of Seurat ", - "conflicts with liger default variables. Modified to ", cdnNew, ".") + cli::cli_alert_warning( + "Variable name {.val {cdn}} in meta.data of Seurat conflicts with liger default variables. Modified to {.val {cdnNew}}.") } else { cdnNew <- cdn } @@ -205,32 +145,6 @@ as.liger.Seurat <- function( #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # From other things to ligerDataset class #### #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#' Converting other classes of data to a as.ligerDataset object -#' @description -#' Works for converting a matrix or container object to a single ligerDataset, -#' and can also convert the modality preset of a ligerDataset. When used with -#' a dense matrix object, it automatically converts the matrix to sparse form -#' (\code{\link[Matrix]{dgCMatrix-class}}). When used with container objects -#' such as Seurat or SingleCellExperiment, it is highly recommended that the -#' object contains only one dataset/sample which is going to be integrated with -#' LIGER. For multi-sample objects, please use \code{\link{as.liger}} with -#' dataset source variable specified. -#' @export -#' @param object Object. -#' @param modal Modality setting for each dataset. Choose from \code{"default"}, -#' \code{"rna"}, \code{"atac"}, \code{"spatial"}, \code{"meth"}. -#' @param ... Additional arguments passed to \code{\link{createLigerDataset}} -#' @return a \linkS4class{liger} object. -#' @rdname as.ligerDataset -#' @examples -#' ctrl <- dataset(pbmc, "ctrl") -#' ctrl -#' # Convert the modality preset -#' as.ligerDataset(ctrl, modal = "atac") -#' rawCounts <- rawData(ctrl) -#' class(rawCounts) -#' as.ligerDataset(rawCounts) -as.ligerDataset <- function(object, ...) UseMethod("as.ligerDataset", object) #' @rdname as.ligerDataset #' @export @@ -246,10 +160,9 @@ as.ligerDataset.ligerDataset <- function( slotFromClass <- methods::slotNames(class(object)) slotToClass <- methods::slotNames(newClass) if (any(!slotFromClass %in% slotToClass)) - warning("Will remove information in the following slots when ", - "converting class from `", class(object), "` to `", newClass, - "`: ", paste(slotFromClass[!slotFromClass %in% slotToClass], - collapse = ", ")) + cli::cli_alert_warning( + "Will remove information in the following slots when converting class + from {.cls {class(object)}} to {.cls {newClass}}: {.val {slotFromClass[!slotFromClass %in% slotToClass]}}") newCallArgs <- list(Class = newClass) for (s in slotFromClass) { if (s %in% slotToClass) @@ -308,10 +221,11 @@ as.ligerDataset.SingleCellExperiment <- function( ... ) { if (!requireNamespace("SingleCellExperiment", quietly = "TRUE")) # nocov start - stop("Package \"SingleCellExperiment\" needed for this function ", - "to work. Please install it by command:\n", - "BiocManager::install('SingleCellExperiment')", - call. = FALSE) # nocov end + cli::cli_abort( + "Package {.pkg SingleCellExperiment} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('SingleCellExperiment')}" + ) # nocov end modal <- match.arg(modal) mat <- SingleCellExperiment::counts(object) createLigerDataset(rawData = mat, modal = modal, ...) @@ -351,6 +265,7 @@ as.ligerDataset.SingleCellExperiment <- function( #' @param use.liger.genes [Defunct] Will be ignored and will always set LIGER #' variable features to the place. #' @export +#' @rdname ligerToSeurat #' @return Always returns Seurat object(s) of the latest version. By default a #' Seurat object with split layers, e.g. with layers like "counts.ctrl" and #' "counts.stim". If \code{merge = TRUE}, return a single Seurat object with @@ -386,7 +301,7 @@ ligerToSeurat <- function( rawDataList <- rawDataList[!sapply(rawDataList, is.null)] if (isTRUE(merge)) rawDataList <- mergeSparseAll(rawDataList) if (!length(rawDataList)) { - stop("rawData not found.") + cli::cli_abort("rawData not found.") } Assay <- SeuratObject::CreateAssay5Object(rawDataList) @@ -588,8 +503,8 @@ convertOldLiger.mem <- function(object) { if (!is.null(dataList$rawData)) features <- rownames(dataList$rawData) else features <- rownames(dataList$normData) if (is.null(features)) { - warning("Cannot detect feature names for dataset \"", d, "\". ", - "Skipped.") + cli::cli_alert_danger( + "Cannot detect feature names for dataset {.val {d}}. Skipped.") next } ftPassing <- .checkIDIdentical( @@ -627,10 +542,12 @@ convertOldLiger.mem <- function(object) { } convertOldLiger.H5 <- function(object, h5FilePath = NULL) { - .log("Please use caution when restoring an H5 based liger object, because ", - "old version does not have solid restriction on cell/feature ", - "identifier matching. New rliger assumes all data was produced ", - "with standard old rliger workflow.") + cli::cli_alert_warning( + "Please use caution when restoring an H5 based liger object, because + the old version does not have any solid restriction on cell/feature + identifier matching. New rliger > 1.99 assumes all data was produced + with standard old rliger workflow.", wrap = TRUE + ) dataLists <- list() if (.hasSlot(object, "H")) dataLists$H <- object@H if (.hasSlot(object, "V")) dataLists$V <- object@V @@ -712,8 +629,7 @@ convertOldLiger.H5 <- function(object, h5FilePath = NULL) { if (!is.null(h5FilePath[[d]])) h5Path <- h5FilePath[[d]] else h5Path <- object@h5file.info[[d]]$file.path if (!hdf5r::is_hdf5(name = h5Path)) { - stop("File path for dataset \"", d, "\" not found or is not an H5 ", - "file: ", h5Path) + cli::cli_abort("File path for dataset {.val {d}} not found or is not an H5 file: {.file {h5Path}}") } h5Format <- object@h5file.info[[d]]$format.type ldList[[d]] <- do.call(createH5LigerDataset, c( @@ -750,7 +666,7 @@ convertOldLiger.H5 <- function(object, h5FilePath = NULL) { for (slot in names(onCol)) { if (is.na(slot)) next if (!identical(colnames(onCol[[slot]]), ref)) { - warning("Inconsistent column ID in slot `", slot, "`.") + cli::cli_alert_danger("Inconsistent column ID in slot {slot}.") colPassing[slot] <- FALSE } } @@ -759,7 +675,7 @@ convertOldLiger.H5 <- function(object, h5FilePath = NULL) { for (slot in names(onRow)) { if (is.na(slot)) next if (!identical(rownames(onRow[[slot]]), ref)) { - warning("Inconsistent row ID in slot `", slot, "`.") + cli::cli_alert_danger("Inconsistent row ID in slot {slot}.") rowPassing[slot] <- FALSE } } @@ -784,11 +700,11 @@ convertOldLiger.H5 <- function(object, h5FilePath = NULL) { return(TRUE) }, error = function(e) { - .log("Skipped slot `", name, "` which is not available.") + cli::cli_alert_warning("Skipped slot {name} which is not available.") return(FALSE) }, warining = function(w) { - .log(w) + cli::cli_alert_warning(w) return(FALSE) } ) diff --git a/R/classes.R b/R/classes.R new file mode 100644 index 00000000..d1f473f9 --- /dev/null +++ b/R/classes.R @@ -0,0 +1,480 @@ +setClassUnion("dgCMatrix_OR_NULL", c("dgCMatrix", "NULL")) +setClassUnion("matrix_OR_NULL", c("matrix", "NULL")) +setClassUnion("matrixLike", c("matrix", "dgCMatrix", "dgTMatrix", "dgeMatrix")) +setClassUnion("matrixLike_OR_NULL", c("matrixLike", "NULL")) +setClassUnion("character_OR_NULL", c("character", "NULL")) +# It is quite hard to handle "H5D here, which is indeed defined as an R6 class. +# I'm not sure if this is a proper solution +setOldClass("H5D") +setOldClass("H5Group") +suppressWarnings(setClassUnion("dgCMatrix_OR_H5D_OR_NULL", c("dgCMatrix", "H5D", "NULL"))) +setClassUnion("matrix_OR_H5D_OR_NULL", c("matrix", "H5D", "NULL")) +setClassUnion("matrixLike_OR_H5D_OR_H5Group_OR_NULL", c("matrixLike", "H5D", "H5Group", "NULL")) +setClassUnion("index", + members = c("logical", "numeric", "character")) +setClassUnion("Number_or_NULL", c("integer", "numeric", "NULL")) +setClassUnion("dataframe", c("data.frame", "DataFrame", "NULL", "missing")) + +#' @importClassesFrom Matrix dgCMatrix dgTMatrix dgeMatrix +NULL + + +#' ligerDataset class +#' +#' Object for storing dastaset specific information. Will be embedded within a +#' higher level \linkS4class{liger} object +#' @docType class +#' @rdname ligerDataset-class +#' @slot rawData Raw data. +#' @slot normData Normalized data +#' @slot scaleData Scaled data, usually with subset variable features +#' @slot scaleUnsharedData Scaled data of features not shared with other +#' datasets +#' @slot varUnsharedFeatures Variable features not shared with other datasets +#' @slot V matrix +#' @slot A matrix +#' @slot B matrix +#' @slot H matrix +#' @slot U matrix +#' @slot h5fileInfo list +#' @slot featureMeta Feature metadata, DataFrame +#' @slot colnames character +#' @slot rownames character +#' @importClassesFrom S4Vectors DataFrame +#' @exportClass ligerDataset +ligerDataset <- setClass( + "ligerDataset", + representation( + rawData = "dgCMatrix_OR_H5D_OR_NULL", + normData = "dgCMatrix_OR_H5D_OR_NULL", + scaleData = "matrixLike_OR_H5D_OR_H5Group_OR_NULL", + scaleUnsharedData = "matrixLike_OR_H5D_OR_H5Group_OR_NULL", + varUnsharedFeatures = "character", + H = "matrix_OR_NULL", + V = "matrix_OR_NULL", + A = "matrix_OR_NULL", + B = "matrix_OR_NULL", + U = "matrix_OR_NULL", + h5fileInfo = "list", + featureMeta = "DataFrame", + colnames = "character", + rownames = "character" + ) +) + +.checkLigerDatasetBarcodes <- function(x) { + # cell barcodes all consistant + if (is.null(colnames(x))) { + return(paste0("No valid cell barcode detected for ligerDataset.\n", + "Please create object with matrices with colnames.")) + } + for (slot in c("rawData", "normData", "scaleData", "scaleUnsharedData", + "H")) { + if (!slot %in% methods::slotNames(x)) next + data <- methods::slot(x, slot) + if (!is.null(data)) { + barcodes.slot <- colnames(data) + if (!identical(colnames(x), barcodes.slot)) { + return(paste0("Inconsistant cell identifiers in `", slot, + "` slot.")) + } + } + } + + for (slot in c("scaleData", "V")) { + featuresToCheck <- rownames(methods::slot(x, slot)) + check <- !featuresToCheck %in% rownames(x) + if (any(check)) { + msg <- paste0("Features in ", slot, " not found from dataset: ", + paste(featuresToCheck[check], collapse = ", ")) + return(msg) + } + } + TRUE +} + +.checkH5LigerDatasetLink <- function(x) { + restoreGuide <- "Please try running `restoreH5Liger(object)`." + if (!"H5File" %in% names(h5fileInfo(x))) { + return(paste("`h5fileInfo` incomplete.", restoreGuide)) + } + h5file <- getH5File(x) + if (is.null(h5file)) { + return(paste("`H5File` is NULL in `h5fileInfo` slot.", restoreGuide)) + } + if (!h5file$is_valid) { + return(paste("`H5File` is invalid in `h5fileInfo` slot.", restoreGuide)) + } + if (!is.null(rawData(x))) { + if (!rawData(x)$is_valid) { + return(paste("`rawData` slot is invalid.", restoreGuide)) + } + } + if (!is.null(normData(x))) { + if (!normData(x)$is_valid) { + return(paste("`normData` slot is invalid.", restoreGuide)) + } + } + if (!is.null(scaleData(x))) { + if (!scaleData(x)$is_valid) { + return(paste("`scaleData` slot is invalid.", restoreGuide)) + } + } + TRUE +} + +.valid.ligerDataset <- function(object) { + if (isH5Liger(object)) { + # message("Checking h5 ligerDataset validity") + .checkH5LigerDatasetLink(object) + } else { + # message("Checking in memory ligerDataset validity") + .checkLigerDatasetBarcodes(object) + } + # TODO more checks + # TODO debating on whether to have check of the matching between scaleData + # features and selected variable features. +} + +setValidity("ligerDataset", .valid.ligerDataset) + +#' @title liger class +#' @rdname liger-class +#' @docType class +#' @description \code{liger} object is the main data container for LIGER +#' analysis in R. The slot \code{datasets} is a list where each element should +#' be a \linkS4class{ligerDataset} object containing dataset specific +#' information, such as the expression matrices. The other parts of liger object +#' stores information that can be shared across the analysis, such as the cell +#' metadata and factorization result matrices. +#' +#' This manual provides explanation to the \code{liger} object structure as well +#' as usage of class-specific methods. Please see detail sections for more +#' information. +#' +#' For \code{liger} objects created with older versions of rliger package, +#' please try updating the objects individually with +#' \code{\link{convertOldLiger}}. +#' @slot datasets list of \linkS4class{ligerDataset} objects. Use generic +#' \code{dataset}, \code{dataset<-}, \code{datasets} or \code{datasets<-} to +#' interact with. See detailed section accordingly. +#' @slot cellMeta \linkS4class{DFrame} object for cell metadata. Pre-existing +#' metadata, QC metrics, cluster labeling, low-dimensional embedding and etc. +#' are all stored here. Use generic \code{cellMeta}, \code{cellMeta<-}, +#' \code{$}, \code{[[]]} or \code{[[]]<-} to interact with. See detailed section +#' accordingly. +#' @slot varFeatures Character vector of feature names. Use generic +#' \code{varFeatures} or \code{varFeatures<-} to interact with. See detailed +#' section accordingly. +#' @slot W Matrix of gene loading for each factor. See +#' \code{\link{runIntegration}}. +#' @slot H.norm Matrix of aligned factor loading for each cell. See +#' \code{\link{quantileNorm}} and \code{\link{runIntegration}}. +#' @slot commands List of \linkS4class{ligerCommand} objects. Record of +#' analysis. Use \code{commands} to retrieve information. See detailed section +#' accordingly. +#' @slot uns List for unstructured meta-info of analyses or presets. +#' @slot version Record of version of rliger2 package +#' @importClassesFrom S4Vectors DataFrame +#' @importFrom ggplot2 fortify +liger <- setClass( + "liger", + representation( + datasets = "list", + cellMeta = "DataFrame", + varFeatures = "character_OR_NULL", + W = "matrix_OR_NULL", + H.norm = "matrix_OR_NULL", + uns = "list", + commands = "list", + version = "ANY" + ), + methods::prototype( + cellMeta = methods::new("DFrame"), + version = utils::packageVersion("rliger2") + ) +) + + +.checkAllDatasets <- function(x) { + for (ld in datasets(x)) { + methods::validObject(ld) + } + return(NULL) +} + +.checkLigerBarcodes <- function(x) { + bcFromDatasets <- unlist(lapply(datasets(x), colnames), use.names = FALSE) + if (!identical(colnames(x), bcFromDatasets)) { + return("liger object barcodes do not match to barcodes in datasets") + } + if (!is.null(x@H.norm)) { + if (!identical(rownames(x@H.norm), bcFromDatasets)) { + return("H.norm barcodes do not match to barcodes in datasets.") + } + } + if (!"dataset" %in% names(cellMeta(x))) { + return("`datasets` variable missing in cellMeta(x)") + } + datasetNamesFromDatasets <- rep(names(x), lapply(datasets(x), ncol)) + names(datasetNamesFromDatasets) <- NULL + + if (!identical(datasetNamesFromDatasets, as.character(x$dataset))) { + return("names of datasets do not match + `datasets` variable in cellMeta") + } + return(NULL) +} + +.checkLigerVarFeature <- function(x) { + if (!is.null(varFeatures(x)) && + length(varFeatures(x)) > 0) { + if (!is.null(x@W)) + if (!identical(rownames(x@W), varFeatures(x))) + return("Variable features do not match dimension of W matrix") + for (d in names(x)) { + ld <- dataset(x, d) + if (!is.null(ld@V)) { + if (!identical(rownames(ld@V), varFeatures(x))) + return(paste("Variable features do not match dimension", + "of V matrix in dataset", d)) + } + + if (!is.null(scaleData(ld))) { + if (!isH5Liger(ld)) { + if (!identical(rownames(scaleData(ld)), varFeatures(x))) + return(paste("Variable features do not match dimension", + "of scaleData in dataset", d)) + } else { + if (inherits(scaleData(ld), "H5D")) { + if (scaleData(ld)$dims[1] != length(varFeatures(x))) + return(paste("Variable features do not match ", + "dimension of scaleData in dataset ", + "(H5)", d)) + } else if (inherits(scaleData(ld), "H5Group")) { + if (scaleData(ld)[["featureIdx"]]$dims != length(varFeatures(x))) { + return(paste("Variable features do not match ", + "dimension of scaleData in dataset ", + "(H5)", d)) + } + scaleDataIdx <- scaleData(ld)[["featureIdx"]][] + if (!identical(rownames(ld)[scaleDataIdx], varFeatures(x))) { + return("HDF5 scaled data feature index does not ", + "match variable features") + } + } + } + } + } + } + return(NULL) +} + +.valid.liger <- function(object) { + # message("Checking liger object validity") + res <- .checkAllDatasets(object) + if (!is.null(res)) return(res) + res <- .checkLigerBarcodes(object) + if (!is.null(res)) return(res) + res <- .checkLigerVarFeature(object) + if (!is.null(res)) return(res) + # TODO more checks +} + +setValidity("liger", .valid.liger) + + + +setClassUnion("POSIXct_or_NULL", c("POSIXct", "NULL")) + +#' ligerCommand object: Record the input and time of a LIGER function call +#' @slot funcName Name of the function +#' @slot time A time stamp object +#' @slot call A character string converted from system call +#' @slot parameters List of all arguments except the \linkS4class{liger} object. +#' Large object are summarized to short string. +#' @slot objSummary List of attributes of the \linkS4class{liger} object as a +#' snapshot when command is operated. +#' @slot ligerVersion Character string converted from +#' \code{packageVersion("rliger2")}. +#' @slot dependencyVersion Named character vector of version number, if any +#' dependency library has a chance to be included by the function. A +#' dependency might only be invoked under certain conditions, such as using +#' an alternative algorithm, which a call does not actually reach to, but it +#' would still be included for this call. +#' @exportClass ligerCommand +#' @export +#' @rdname ligerCommand-class +ligerCommand <- setClass( + Class = "ligerCommand", + representation( + funcName = "character", + time = "POSIXct_or_NULL", + call = "character", + parameters = "list", + objSummary = "list", + ligerVersion = "character", + dependencyVersion = "character" + ), + prototype( + funcName = character(), + time = NULL, + parameters = list(), + objSummary = list( + datasets = character(), + nCells = numeric(), + nFeatures = numeric(), + nVarFeatures = numeric(), + cellMetaNames = character(), + ligerVersion = character(), + dependencyVersion = character() + ) + ) +) + + +################################################################################ +# Developer guide for adding a new sub-class of `ligerDataset` for new modality +################################################################################ +# +# Below is a check-list of the TODOs when new sub-classes need to be added. +# Please follow them carefully, and refer to existing code as examples. +# +# 1. Add `setClass` chunk for defining the new subclass, pay attention to: +# a. Naming convention should be `liger{Modal}Dataset`, in camelCase +# b. contains = "ligerDataset" +# c. add new slots for modality specific information with `representation` +# d. if the default new information could be empty, add `prototype` +# 2. In files `zzz.R`, `import.R`, `classConversion.R`, search for +# text "modal". When seeing a multi-option vector argument, add a unique +# abbreviation of this new data type to the vector. Don't forget updating +# valid options in the manual documentaion as well. +# 3. If the new slot(s) added is thought to be retrieved by future developers +# or users, getter and setter methods MUST be implemented. +# 4. Please go through the implementation of the following functions in file +# `ligerDataset-class.R`, and make sure data in the new slot(s) is properly +# handled. +# a. .checkLigerDatasetBarcodes() +# b. `dimnames<-()` (search: `setReplaceMethod("dimnames"`) +# c. `[` (search: "[") +# +################################################################################ + +#' Subclass of ligerDataset for RNA modality +#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots +#' can be referred with the link. This subclass does not have any different from +#' the default \code{ligerDataset} class except the class name. +#' @export +#' @exportClass ligerRNADataset +ligerRNADataset <- setClass( + "ligerRNADataset", contains = "ligerDataset" +) + + +#' Subclass of ligerDataset for ATAC modality +#' +#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots +#' can be referred with the link. +#' @slot rawPeak sparse matrix +#' @slot normPeak sparse matrix +#' @exportClass ligerATACDataset +#' @export +ligerATACDataset <- setClass( + "ligerATACDataset", + contains = "ligerDataset", + representation = representation(rawPeak = "matrixLike_OR_NULL", + normPeak = "matrixLike_OR_NULL"), + prototype = prototype(rawPeak = NULL, normPeak = NULL) +) + +.valid.ligerATACDataset <- function(object) { + passSuperClassCheck <- .valid.ligerDataset(object) + if (!isTRUE(passSuperClassCheck)) return(passSuperClassCheck) + for (slot in c("rawPeak", "normPeak")) { + data <- methods::slot(object, slot) + if (!is.null(data)) { + barcodes.slot <- colnames(data) + if (!identical(object@colnames, barcodes.slot)) { + return(paste0("Inconsistant cell identifiers in `", slot, + "` slot.")) + } + } + } +} + +setValidity("ligerATACDataset", .valid.ligerATACDataset) + +#' Subclass of ligerDataset for Spatial modality +#' +#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots +#' can be referred with the link. +#' @slot coordinate dense matrix +#' @exportClass ligerSpatialDataset +#' @export +ligerSpatialDataset <- setClass( + "ligerSpatialDataset", + contains = "ligerDataset", + representation = representation(coordinate = "matrix_OR_NULL"), + prototype = prototype(coordinate = NULL) +) + +.checkCoords <- function(ld, value) { + if (is.null(rownames(value))) { + cli::cli_alert_warning("No rownames with given spatial coordinate. Assuming they match with the cells.") + rownames(value) <- colnames(ld) + } + if (is.null(colnames(value))) { + if (ncol(value) <= 3) { + colnames(value) <- c("x", "y", "z")[seq(ncol(value))] + } else { + cli::cli_abort("More than 3 dimensions for the coordinates but no colnames are given.") + } + cli::cli_alert_warning( + "No colnames with given spatial coordinate. Setting to {.val {colnames(value)}}" + ) + } + full <- matrix(NA, nrow = ncol(ld), ncol = ncol(value), + dimnames = list(colnames(ld), colnames(value))) + cellIsec <- intersect(rownames(value), colnames(ld)) + full[cellIsec, colnames(value)] <- value[cellIsec,] + if (any(is.na(full))) { + cli::cli_alert_warning("NA generated for missing cells.") + } + if (any(!rownames(value) %in% rownames(full))) { + cli::cli_alert_warning("Cells in given coordinate not found in the dataset.") + } + return(full) +} + +.valid.ligerSpatialDataset <- function(object) { + passSuperClassCheck <- .valid.ligerDataset(object) + if (!isTRUE(passSuperClassCheck)) return(passSuperClassCheck) + coord <- object@coordinate + if (!is.null(coord)) { + barcodes.slot <- rownames(coord) + if (!identical(object@colnames, barcodes.slot)) { + return(paste0("Inconsistant cell identifiers in `coordinate` slot.")) + } + } +} + +setValidity("ligerSpatialDataset", .valid.ligerSpatialDataset) + + + +#' Subclass of ligerDataset for Methylation modality +#' +#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots +#' can be referred with the link. \code{\link{scaleNotCenter}} applied on +#' datasets of this class will automatically be taken by reversing the +#' normalized data instead of scaling the variable features. +#' @exportClass ligerMethDataset +#' @export +ligerMethDataset <- setClass( + "ligerMethDataset", + contains = "ligerDataset" +) + +.valid.ligerMethDataset <- function(object) .valid.ligerDataset(object) + +setValidity("ligerMethDataset", .valid.ligerMethDataset) diff --git a/R/clustering.R b/R/clustering.R index 8b36f690..5b153588 100644 --- a/R/clustering.R +++ b/R/clustering.R @@ -67,7 +67,7 @@ runCluster <- function( groupSingletons = TRUE, clusterName = paste0(method, "_cluster"), seed = 1, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) { method <- match.arg(method) object <- switch(method, @@ -77,12 +77,12 @@ runCluster <- function( Hsearch <- searchH(object, useRaw) H <- Hsearch$H useRaw <- Hsearch$useRaw - type <- ifelse(useRaw, " unnormalized ", " quantile normalized ") + type <- ifelse(useRaw, "unnormalized", "quantile normalized") if (!is.null(useDims)) H <- H[, useDims, drop = FALSE] if (isTRUE(verbose)) - .log(method, " clustering on", type, "cell factor loadings...") + cli::cli_process_start("{method} clustering on {type} cell factor loadings...") knn <- RANN::nn2(H, k = nNeighbors, eps = eps) snn <- ComputeSNN(knn$nn.idx, prune = prune) if (!is.null(seed)) set.seed(seed) @@ -110,7 +110,7 @@ runCluster <- function( nIterations = nIterations, algorithm = 1, randomSeed = seed, - printOutput = TRUE, + printOutput = verbose, edgefilename = edgeOutPath ) unlink(edgeOutPath) @@ -123,7 +123,11 @@ runCluster <- function( groupSingletons = groupSingletons, verbose = verbose) cellMeta(object, clusterName, check = FALSE) <- clusts + if (isTRUE(verbose)) + cli::cli_process_done(msg_done = "{method} clustering on {type} cell factor loadings ... Found {nlevels(clusts)} clusters.") object@uns$defaultCluster <- object@uns$defaultCluster %||% clusterName + if (isTRUE(verbose)) + cli::cli_alert_info("cellMeta variable {.field {clusterName}} is now set as default.") return(object) } @@ -211,7 +215,7 @@ groupSingletons <- function( } if (!isTRUE(groupSingletons)) { if (isTRUE(verbose)) { - .log(length(singletons), " singletons identified. ") + cli::cli_alert_info("{length(singletons)} singletons identified.") } ids <- as.character(ids) ids[ids %in% singletons] <- "singleton" @@ -243,8 +247,7 @@ groupSingletons <- function( } ids <- factor(ids) if (isTRUE(verbose)) - .log(length(singletons), " singletons identified. ", - length(levels(ids)), " final clusters.") + cli::cli_alert_info("{length(singletons)} singletons identified.") return(ids) } @@ -287,13 +290,13 @@ mapCellMeta <- function( ) { object <- recordCommand(object, ...) from <- cellMeta(object, from) - if (!is.factor(from)) stop("`from` must be a factor class variable.") + if (!is.factor(from)) + cli::cli_abort("{.var from} must be a {.cls factor}.") mapping <- list(...) fromCats <- names(mapping) notFound <- fromCats[!fromCats %in% levels(from)] if (length(notFound) > 0) { - stop("The following categories requested not found: ", - paste0(notFound, collapse = ", ")) + cli::cli_abort("{length(notFound)} categor{?y is/ies are} requested but not found: {.val {notFound}}") } toCats <- unlist(mapping) diff --git a/R/dotplot.R b/R/dotplot.R index 0d7c13c7..569098d7 100644 --- a/R/dotplot.R +++ b/R/dotplot.R @@ -290,7 +290,10 @@ plotClusterFactorDot <- function( ) if (length(viridisOption) != 1 || !viridisOption %in% viridisAvail) - stop("`viridisOption` has to be one value from the available choices.") + cli::cli_abort( + c("{.var viridisOption} has to be one value from the available choices: ", + "{.val {viridisAvail}}") + ) ## Font-size specification # Broadcast one-param setting to each diff --git a/R/embedding.R b/R/embedding.R index 71aeb639..7d42dd6e 100644 --- a/R/embedding.R +++ b/R/embedding.R @@ -71,16 +71,19 @@ runUMAP <- function( Hsearch <- searchH(object, useRaw) H <- Hsearch$H useRaw <- Hsearch$useRaw - type <- ifelse(useRaw, " unnormalized ", " quantile normalized ") + type <- ifelse(useRaw, "unnormalized", "quantile normalized") if (isTRUE(verbose)) - .log("Generating UMAP on", type, "cell factor loadings...") + cli::cli_process_start("Generating UMAP on {type} cell factor loadings...") if (!is.null(useDims)) H <- H[, useDims, drop = FALSE] umap <- uwot::umap(H, n_components = as.integer(nDims), metric = distance, n_neighbors = as.integer(nNeighbors), min_dist = minDist) + if (isTRUE(verbose)) cli::cli_process_done() dimRed(object, dimredName) <- umap + if (isTRUE(verbose)) + cli::cli_alert_info("cellMeta variable {.field {dimredName}} is now set as default.") return(object) } @@ -161,10 +164,9 @@ runTSNE <- function( Hsearch <- searchH(object, useRaw) H <- Hsearch$H useRaw <- Hsearch$useRaw - type <- ifelse(useRaw, " unnormalized ", " quantile normalized ") + type <- ifelse(useRaw, "unnormalized", "quantile normalized") if (isTRUE(verbose)) - .log("Generating TSNE (", method, ") on", type, - "cell factor loadings...") + cli::cli_process_start("Generating TSNE ({method}) on {type} cell factor loadings...") if (!is.null(useDims)) H <- H[, useDims, drop = FALSE] if (method == "Rtsne") { set.seed(seed) @@ -183,8 +185,11 @@ runTSNE <- function( theta = theta, perplexity = perplexity) } + if (isTRUE(verbose)) cli::cli_process_done() dimRed(object, dimredName) <- tsne object@uns$TSNE <- list(method = method) + if (isTRUE(verbose)) + cli::cli_alert_info("cellMeta variable {.field {dimredName}} is now set as default.") return(object) } diff --git a/R/factorMarker.R b/R/factorMarker.R index 41299fc2..8ae4fc7b 100644 --- a/R/factorMarker.R +++ b/R/factorMarker.R @@ -66,7 +66,7 @@ getFactorMarkers <- function( dataset1 <- .checkUseDatasets(object, useDatasets = dataset1) dataset2 <- .checkUseDatasets(object, useDatasets = dataset2) if (any(isH5Liger(object, dataset = c(dataset1, dataset2)))) - stop("Please use in-memory liger object for this analysis.`") + cli::cli_abort("Please use in-memory {.cls liger} object for this analysis") if (is.null(nGenes)) { nGenes <- length(varFeatures(object)) } @@ -78,11 +78,13 @@ getFactorMarkers <- function( } useFactors <- which(abs(datasetSpecificity) <= factorShareThresh) if (length(useFactors) == 0) { - stop("No factor passed the dataset specificity threshold, ", - "please try a larger `factorShareThresh`.") + cli::cli_abort( + c("No factor passed the dataset specificity threshold", + i = "please try a larger {.var factorShareThresh}.") + ) } if (length(useFactors) == 1 && isTRUE(verbose)) { - warning("Only 1 factor passed the dataset specificity threshold.") + cli::cli_alert_warning("Only 1 factor passed the dataset specificity threshold.") } H <- getMatrix(object, "H", dataset = c(dataset1, dataset2)) @@ -101,10 +103,16 @@ getFactorMarkers <- function( W_matrices <- list() vargene <- varFeatures(object) if (isTRUE(verbose)) { - .log("Performing wilcoxon test between datasets \"", dataset1, - "\" and \"", dataset2, "\", \nbasing on factor loading.") - if (!isTRUE(printGenes)) - pb <- utils::txtProgressBar(0, length(useFactors), style = 3) + if (isTRUE(printGenes)) { + cli::cli_alert_info( + "Performing wilcoxon test between {.val {dataset1}} and {.val {dataset2}} basing on factor loading." + ) + } else { + cli::cli_progress_bar( + name = "Testing between {.val {dataset1}} and {.val {dataset2}}", + total = length(useFactors), type = "iter", clear = FALSE + ) + } } for (j in seq_along(useFactors)) { i <- useFactors[j] @@ -116,8 +124,7 @@ getFactorMarkers <- function( # if not max factor for any cell in either dataset if (sum(labels[[dataset1]] == i) <= 1 || sum(labels[[dataset2]] == i) <= 1) { - warning("Factor ", i, " did not appear as max in ", - "any cell in either dataset", immediate. = TRUE) + cli::cli_alert_warning("Factor {i} did not appear as max in any cell in either dataset") next } @@ -164,15 +171,16 @@ getFactorMarkers <- function( if (isTRUE(verbose)) { if (isTRUE(printGenes)) { - .log("Factor ", i) - message("Dataset 1:\n", + cli::cli_h2("Factor {i}") + cat("Dataset 1:\n", paste(topGenesV1, collapse = ", "), "\nShared:\n", paste(topGenesW, collapse = ", "), "\nDataset 2\n", paste(topGenesV2, collapse = ", "), "\n") } else { - utils::setTxtProgressBar(pb, j) + cli::cli_progress_update(set = j) + # utils::setTxtProgressBar(pb, j) } } diff --git a/R/generics.R b/R/generics.R new file mode 100644 index 00000000..5bc866b4 --- /dev/null +++ b/R/generics.R @@ -0,0 +1,519 @@ +#' @section Matrix access: +#' For \code{ligerDataset} object, \code{rawData()}, \code{normData}, +#' \code{scaleData()} and \code{scaleUnsharedData()} methods are exported for +#' users to access the corresponding feature expression matrix. Replacement +#' methods are also available to modify the slots. +#' +#' For other matrices, such as the \eqn{H} and \eqn{V}, which are dataset +#' specific, please use \code{getMatrix()} method with specifying slot name. +#' Directly accessing slot with \code{@} is generally not recommended. +#' @export +#' @rdname ligerDataset-class +setGeneric("rawData", function(x, dataset = NULL) standardGeneric("rawData")) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "rawData<-", + function(x, dataset = NULL, check = TRUE, value) standardGeneric("rawData<-") +) + +#' @export +#' @rdname ligerDataset-class +setGeneric("normData", function(x, dataset = NULL) standardGeneric("normData")) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "normData<-", + function(x, dataset = NULL, check = TRUE, value) standardGeneric("normData<-") +) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "scaleData", + function(x, dataset = NULL) standardGeneric("scaleData") +) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "scaleData<-", + function(x, dataset = NULL, check = TRUE, value) standardGeneric("scaleData<-") +) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "scaleUnsharedData", + function(x, dataset = NULL) standardGeneric("scaleUnsharedData") +) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "scaleUnsharedData<-", + function(x, dataset = NULL, check = TRUE, value) standardGeneric("scaleUnsharedData<-") +) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "getMatrix", + function(x, slot = "rawData", dataset = NULL, returnList = FALSE) { + standardGeneric("getMatrix") + } +) + +#' @section H5 file and information access: +#' A \code{ligerDataset} object has a slot called \code{h5fileInfo}, which is a +#' list object. The first element is called \code{$H5File}, which is an +#' \code{H5File} class object and is the connection to the input file. The +#' second element is \code{$filename} which stores the absolute path of the H5 +#' file in the current machine. The third element \code{$formatType} stores the +#' name of preset being used, if applicable. The other following keys pair with +#' paths in the H5 file that point to specific data for constructing a feature +#' expression matrix. +#' +#' \code{h5fileInfo()} method access the list described above and simply +#' retrieves the corresponding value. When \code{info = NULL}, returns the whole +#' list. When \code{length(info) == 1}, returns the requested list value. When +#' more info requested, returns a subset list. +#' +#' The replacement method modifies the list elements and corresponding slot +#' value (if applicable) at the same time. For example, running +#' \code{h5fileInfo(obj, "rawData") <- newPath} not only updates the list, but +#' also updates the \code{rawData} slot with the \code{H5D} class data at +#' "newPath" in the \code{H5File} object. +#' +#' \code{getH5File()} is a wrapper and is equivalent to +#' \code{h5fileInfo(obj, "H5File")}. +#' @export +#' @rdname ligerDataset-class +setGeneric("h5fileInfo", function(x, info = NULL) standardGeneric("h5fileInfo")) + +#' @export +#' @rdname ligerDataset-class +setGeneric( + "h5fileInfo<-", + function(x, info = NULL, check = TRUE, value) { + standardGeneric("h5fileInfo<-") + } +) + + +#' @export +#' @rdname ligerDataset-class +setGeneric("getH5File", function(x, dataset = NULL) standardGeneric("getH5File")) + +#' @export +#' @rdname ligerDataset-class +setMethod("getH5File", + signature = signature(x = "ligerDataset", dataset = "missing"), + function(x, dataset = NULL) h5fileInfo(x, "H5File")) + + +#' @section Feature metadata access: +#' A slot \code{featureMeta} is included for each \code{ligerDataset} object. +#' This slot requires a \code{\link[S4Vectors]{DataFrame-class}} object, which +#' is the same as \code{cellMeta} slot of a \linkS4class{liger} object. However, +#' the associated S4 methods only include access to the whole table for now. +#' Internal information access follows the same way as data.frame operation. +#' For example, \code{featureMeta(ligerD)$nCell} or +#' \code{featureMeta(ligerD)[varFeatures(ligerObj), "gene_var"]}. +#' @export +#' @rdname ligerDataset-class +setGeneric("featureMeta", function(x, check = NULL) { + standardGeneric("featureMeta") +}) + +#' @export +#' @rdname ligerDataset-class +setGeneric("featureMeta<-", function(x, check = TRUE, value) { + standardGeneric("featureMeta<-") +}) + + + + + + + +#' @section Dataset access: +#' \code{datasets()} method only accesses the \code{datasets} slot, the list of +#' \linkS4class{ligerDataset} objects. \code{dataset()} method accesses a single +#' dataset, with subsequent cell metadata updates and checks bonded when adding +#' or modifying a dataset. Therefore, when users want to modify something inside +#' a \code{ligerDataset} while no cell metadata change should happen, it is +#' recommended to use: \code{datasets(x)[[name]] <- ligerD} for efficiency, +#' though the result would be the same as \code{dataset(x, name) <- ligerD}. +#' +#' \code{length()} and \code{names()} methods are implemented to access the +#' number and names of datasets. \code{names<-} method is supported for +#' modifying dataset names, with taking care of the "dataset" variable in cell +#' metadata. +#' @section Matrix access: +#' For \code{liger} object, \code{rawData()}, \code{normData}, +#' \code{scaleData()} and \code{scaleUnsharedData()} methods are exported for +#' users to access the corresponding feature expression matrix with +#' specification of one dataset. For retrieving a type of matrix from multiple +#' datasets, please use \code{getMatrix()} method. +#' +#' When only one matrix is expected to be retrieved by \code{getMatrix()}, the +#' matrix itself will be returned. A list will be returned if multiple matrices +#' is requested (by querying multiple datasets) or \code{returnList} is set to +#' \code{TRUE}. +#' @export +#' @rdname liger-class +setGeneric("datasets", function(x, check = NULL) standardGeneric("datasets")) + +#' @export +#' @rdname liger-class +setGeneric( + "datasets<-", + function(x, check = TRUE, value) standardGeneric("datasets<-") +) + +#' @export +#' @rdname liger-class +setGeneric("dataset", function(x, dataset = NULL) standardGeneric("dataset")) + +#' @export +#' @rdname liger-class +setGeneric("dataset<-", function(x, dataset, type = NULL, qc = TRUE, value) { + standardGeneric("dataset<-") +}) + + +#' @export +#' @rdname liger-class +#' @section Cell metadata access: +#' Three approaches are provided for access of cell metadata. A generic function +#' \code{cellMeta} is implemented with plenty of options and multi-variable +#' accessibility. Besides, users can use double-bracket (e.g. +#' \code{ligerObj[[varName]]}) or dollor-sign (e.g. \code{ligerObj$nUMI}) to +#' access or modify single variables. +#' +#' For users' convenience of generating a customized ggplot with available cell +#' metadata, the S3 method \code{fortify.liger} is implemented. With this under +#' the hook, users can create simple ggplots by directly starting with +#' \code{ggplot(ligerObj, aes(...))} where cell metadata variables can be +#' directly thrown into \code{aes()}. +#' +#' Special partial metadata insertion is implemented specifically for mapping +#' categorical annotation from sub-population (subset object) back to original +#' experiment (full-size object). For example, when sub-clustering and +#' annotation is done for a specific cell-type of cells (stored in +#' \code{subobj}) subset from an experiment (stored as \code{obj}), users can do +#' \code{cellMeta(obj, "sub_ann", cellIdx = colnames(subobj)) <- subobj$sub_ann} +#' to map the value back, leaving other cells non-annotated with NAs. Plotting +#' with this variable will then also show NA cells with default grey color. +#' Furthermore, sub-clustering labels for other cell types can also be mapped +#' to the same variable. For example, \code{cellMeta(obj, "sub_ann", +#' cellIdx = colnames(subobj2)) <- subobj2$sub_ann}. As long as the labeling +#' variables are stored as factor class (categorical), the levels (category +#' names) will be properly handled and merged. Other situations follow the R +#' default behavior (e.g. categories might be converted to integer numbers if +#' mapped to numerical variable in the original object). Note that this feature +#' is only available with using the generic function \code{cellMeta} but not +#' with the \code{`[[`} or \code{`$`} accessing methods due to syntax reasons. +#' +#' The generic \code{defaultCluster} works as both getter and setter. As a +#' setter, users can do \code{defaultCluster(obj) <- "existingVariableName"} to +#' set a categorical variable as default cluster used for visualization or +#' downstream analysis. Users can also do \code{defaultCluster(obj, +#' "newVarName") <- factorOfLabels} to push new labeling into the object and set +#' as default. For getter method, the function returns a factor object of the +#' default cluster labeling. Argument \code{useDatasets} can be used for +#' requiring that given or retrieved labeling should match with cells in +#' specified datasets. We generally don't recommend setting \code{"dataset"} as +#' a default cluster because it is a preserved (always existing) field in +#' metadata and can lead to meaningless result when running analysis that +#' utilizes both clustering information and the dataset source information. +setGeneric( + "cellMeta", + function(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, as.data.frame = FALSE, ...) { + standardGeneric("cellMeta") + } +) + +#' @export +#' @rdname liger-class +setGeneric( + "cellMeta<-", + function(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, inplace = FALSE, check = FALSE, value) { + standardGeneric("cellMeta<-") + } +) + +#' @export +#' @rdname liger-class +setGeneric( + "defaultCluster", + function(x, useDatasets = NULL, ...) { + standardGeneric("defaultCluster") + } +) + +#' @export +#' @rdname liger-class +setGeneric( + "defaultCluster<-", + function(x, name = NULL, useDatasets = NULL, ..., value) { + standardGeneric("defaultCluster<-") + } +) + +#' @export +#' @rdname liger-class +#' @section Dimension reduction access: +#' Currently, low-dimensional representaion of cells, presented as dense +#' matrices, are all stored in \code{cellMeta} slot, and can totally be accessed +#' with generics \code{cellMeta} and \code{cellMeta<-}. In addition to that, +#' we provide specific generics \code{dimRed} and \code{dimRed<-} for getting +#' and setting matrix like cell metadata, respectively. Adding a matrix to the +#' object looks as simple as \code{dimRed(obj, "name") <- matrixLike}. It can +#' be retrived back with \code{dimRed(obj, "name")}. Similar to having a default +#' cluster labeling, we also constructed the feature of default dimRed. It can +#' be set with \code{defaultDimRed(obj) <- "existingMatLikeVar"} and the matrix +#' can be retrieved with \code{defaultDimRed(obj)}. +setGeneric( + "dimRed", + function(x, name = NULL, useDatasets = NULL, ...) { + standardGeneric("dimRed") + } +) + +#' @export +#' @rdname liger-class +setGeneric( + "dimRed<-", + function(x, name = NULL, useDatasets = NULL, ..., value) { + standardGeneric("dimRed<-") + } +) + +#' @export +#' @rdname liger-class +setGeneric( + "defaultDimRed", + function(x, useDatasets = NULL) { + standardGeneric("defaultDimRed") + } +) + +#' @export +#' @rdname liger-class +setGeneric( + "defaultDimRed<-", + function(x, name, useDatasets = NULL, value) { + standardGeneric("defaultDimRed<-") + } +) + +#' @export +#' @rdname liger-class +#' @section Variable feature access: +#' The \code{varFeatures} slot allows for character vectors of gene names. +#' \code{varFeatures(x)} returns this vector and \code{value} for +#' \code{varFeatures<-} method has to be a character vector or \code{NULL}. +#' The replacement method, when \code{check = TRUE} performs checks on gene +#' name consistency check across the \code{scaleData}, \code{H}, \code{V} slots +#' of inner \code{ligerDataset} objects as well as the \code{W} and +#' \code{H.norm} slots of the input \code{liger} object. +setGeneric("varFeatures", function(x) standardGeneric("varFeatures")) + +#' @export +#' @rdname liger-class +setGeneric( + "varFeatures<-", + function(x, check = TRUE, value) standardGeneric("varFeatures<-") +) + + + +#' @export +#' @rdname liger-class +setGeneric("varUnsharedFeatures", function(x, dataset = NULL) { + standardGeneric("varUnsharedFeatures") +}) + +#' @export +#' @rdname liger-class +setGeneric( + "varUnsharedFeatures<-", + function(x, dataset, check = TRUE, value) { + standardGeneric("varUnsharedFeatures<-") + } +) + +#' @section Command records: +#' rliger functions, that perform calculation and update the \code{liger} +#' object, will be recorded in a \code{ligerCommand} object and stored in the +#' \code{commands} slot, a list, of \code{liger} object. Method +#' \code{commands()} is implemented to retrieve or show the log history. +#' Running with \code{funcName = NULL} (default) returns all command labels. +#' Specifying \code{funcName} allows partial matching to all command labels +#' and returns a subset list (of \code{ligerCommand} object) of matches (or +#' the \code{ligerCommand} object if only one match found). If \code{arg} is +#' further specified, a subset list of parameters from the matches will be +#' returned. For example, requesting a list of resolution values used in +#' all louvain cluster attempts: \code{commands(ligerObj, "louvainCluster", +#' "resolution")} +#' @export +#' @rdname liger-class +setGeneric( + "commands", + function(x, funcName = NULL, arg = NULL) standardGeneric("commands") +) + + + + + + + + + + +#' Access ligerSpatialDataset coordinate data +#' @description Similar as how default \linkS4class{ligerDataset} data is +#' accessed. +#' @param x \linkS4class{ligerSpatialDataset} object or a \linkS4class{liger} +#' object. +#' @param dataset Name or numeric index of an spatial dataset. +#' @param check Logical, whether to perform object validity check on setting new +#' value. +#' @param value \code{\link{matrix}}. +#' @return The retrieved coordinate matrix or the updated \code{x} object. +#' @rdname coordinate +#' @export +setGeneric("coordinate", function(x, dataset) standardGeneric("coordinate")) + +#' @rdname coordinate +#' @export +setGeneric("coordinate<-", function(x, dataset, check = TRUE, value) standardGeneric("coordinate<-")) + + + + +#' Access ligerATACDataset peak data +#' @description Similar as how default \linkS4class{ligerDataset} data is +#' accessed. +#' @param x \linkS4class{ligerATACDataset} object or a \linkS4class{liger} +#' object. +#' @param dataset Name or numeric index of an ATAC dataset. +#' @param check Logical, whether to perform object validity check on setting new +#' value. +#' @param value \code{\link[Matrix]{dgCMatrix-class}} matrix. +#' @return The retrieved peak count matrix or the updated \code{x} object. +#' @rdname peak +#' @export +setGeneric("rawPeak", function(x, dataset) standardGeneric("rawPeak")) + +#' @rdname peak +#' @export +setGeneric("rawPeak<-", function(x, dataset, check = TRUE, value) standardGeneric("rawPeak<-")) + +#' @rdname peak +#' @export +setGeneric("normPeak", function(x, dataset) standardGeneric("normPeak")) + +#' @rdname peak +#' @export +setGeneric("normPeak<-", function(x, dataset, check = TRUE, value) standardGeneric("normPeak<-")) + + + + + + +#' Converting other classes of data to a liger object +#' @description +#' This function converts data stored in SingleCellExperiment (SCE), Seurat +#' object or a merged sparse matrix (dgCMatrix) into a liger object. This is +#' designed for a container object or matrix that already contains multiple +#' datasets to be integerated with LIGER. For individual datasets, please use +#' \code{\link{createLiger}} instead. +#' @export +#' @param object Object. +#' @param datasetVar Specify the dataset belonging by: 1. Select a variable from +#' existing metadata in the object (e.g. colData column); 2. Specify a +#' vector/factor that assign the dataset belonging. 3. Give a single character +#' string which means that all data is from one dataset (must not be a metadata +#' variable, otherwise it is understood as 1.). Default \code{NULL} gathers +#' things into one dataset and names it "sample" for dgCMatrix, attempts +#' to find variable "sample" from SCE or "orig.ident" from Seurat. +#' @param modal Modality setting for each dataset. See +#' \code{\link{createLiger}}. +#' @param ... Additional arguments passed to \code{\link{createLiger}} +#' @details +#' For Seurat V5 structure, it is highly recommended that users make use of its +#' split layer feature, where things like "counts", "data", and "scale.data" +#' can be held for each dataset in the same Seurat object, e.g. with +#' "count.ctrl", "count.stim", not merged. If a Seurat object with split layers +#' is given, \code{datasetVar} will be ignored and the layers will be directly +#' used. +#' @return a \linkS4class{liger} object. +#' @rdname as.liger +#' @examples +#' # dgCMatrix (common sparse matrix class), usually obtained from other +#' # container object, and contains multiple samples merged in one. +#' matList <- rawData(pbmc) +#' multiSampleMatrix <- mergeSparseAll(matList) +#' # The `datasetVar` argument expects the variable assigning the sample source +#' pbmc2 <- as.liger(multiSampleMatrix, datasetVar = pbmc$dataset) +#' pbmc2 +#' +#' \donttest{ +#' sce <- SingleCellExperiment::SingleCellExperiment( +#' assays = list(counts = multiSampleMatrix) +#' ) +#' sce$sample <- pbmc$dataset +#' pbmc3 <- as.liger(sce, datasetVar = "sample") +#' pbmc3 +#' +#' seu <- SeuratObject::CreateSeuratObject(multiSampleMatrix) +#' # Seurat creates variable "orig.ident" by identifying the cell barcode +#' # prefixes, which is indeed what we need in this case. Users might need +#' # to be careful and have it confirmed first. +#' pbmc4 <- as.liger(seu, datasetVar = "orig.ident") +#' pbmc4 +#' +#' # As per Seurat V5 updates with layered data, specifically helpful udner the +#' # scenario of dataset integration. "counts" and etc for each datasets can be +#' # split into layers. +#' seu5 <- seu +#' seu5[["RNA"]] <- split(seu5[["RNA"]], pbmc$dataset) +#' print(SeuratObject::Layers(seu5)) +#' pbmc5 <- as.liger(seu5) +#' } +as.liger <- function(object, ...) UseMethod("as.liger", object) + +#' Converting other classes of data to a as.ligerDataset object +#' @description +#' Works for converting a matrix or container object to a single ligerDataset, +#' and can also convert the modality preset of a ligerDataset. When used with +#' a dense matrix object, it automatically converts the matrix to sparse form +#' (\code{\link[Matrix]{dgCMatrix-class}}). When used with container objects +#' such as Seurat or SingleCellExperiment, it is highly recommended that the +#' object contains only one dataset/sample which is going to be integrated with +#' LIGER. For multi-sample objects, please use \code{\link{as.liger}} with +#' dataset source variable specified. +#' @export +#' @param object Object. +#' @param modal Modality setting for each dataset. Choose from \code{"default"}, +#' \code{"rna"}, \code{"atac"}, \code{"spatial"}, \code{"meth"}. +#' @param ... Additional arguments passed to \code{\link{createLigerDataset}} +#' @return a \linkS4class{liger} object. +#' @rdname as.ligerDataset +#' @examples +#' ctrl <- dataset(pbmc, "ctrl") +#' ctrl +#' # Convert the modality preset +#' as.ligerDataset(ctrl, modal = "atac") +#' rawCounts <- rawData(ctrl) +#' class(rawCounts) +#' as.ligerDataset(rawCounts) +as.ligerDataset <- function(object, ...) UseMethod("as.ligerDataset", object) diff --git a/R/ggplotting.R b/R/ggplotting.R index 3bf6c7e8..1cfdb6ab 100644 --- a/R/ggplotting.R +++ b/R/ggplotting.R @@ -143,13 +143,13 @@ plotCellScatter <- function( } plotList <- list() - titles <- .checkArgLen(titles, n = length(plotDFList), .stop = FALSE) + titles <- .checkArgLen(titles, n = length(plotDFList), class = "ANY", .stop = FALSE) for (i in seq_along(plotDFList)) { - .log("Plotting feature: ", names(plotDFList)[i], ", on ", - nrow(plotDFList[[i]]), " cells...") + cliID <- cli::cli_process_start("Plotting feature {.val {names(plotDFList)[i]}} on {.val {nrow(plotDFList[[i]])}} cells") plotList[[i]] <- .ggScatter(plotDF = plotDFList[[i]], x = x, y = y, colorBy = colorByParam[[i]], shapeBy = shapeBy, title = titles[i], ...) + cli::cli_process_done(cliID) } names(plotList) <- names(plotDFList) @@ -450,7 +450,7 @@ plotCellViolin <- function( names(yParam) <- names(plotDFList) } plotList <- list() - titles <- .checkArgLen(titles, n = length(plotDFList), .stop = FALSE) + titles <- .checkArgLen(titles, n = length(plotDFList), class = "ANY", .stop = FALSE) for (i in seq_along(plotDFList)) { plotList[[i]] <- .ggCellViolin(plotDF = plotDFList[[i]], y = yParam[[i]], groupBy = groupBy, @@ -799,11 +799,14 @@ plotCellViolin <- function( } if (isTRUE(plotly)) { - if (requireNamespace("plotly", quietly = FALSE)) { + if (requireNamespace("plotly", quietly = TRUE)) { plot <- plotly::ggplotly(plot) } else { - warning('Run `install.packages("plotly")` to enable web based ', - "interactive browsing. Returning original ggplot.") + cli::cli_alert_danger( + "Package {.pkg plotly} is needed for interactive browsing." + ) + cli::cli_alert_info("Please run {.code install.packages('plotly')} to enable it.") + cli::cli_alert_info("Returning the original {.cls ggplot}.") } } return(plot) diff --git a/R/h5Utility.R b/R/h5Utility.R index 7f38a995..e2a8d861 100644 --- a/R/h5Utility.R +++ b/R/h5Utility.R @@ -56,8 +56,12 @@ H5Apply <- function( colptr <- h5file[[h5meta$indptrName]] rowind <- h5file[[h5meta$indicesName]] data <- h5file[[h5meta[[useData]]]] - if (isTRUE(verbose)) pb <- utils::txtProgressBar(0, numChunks, style = 3) + if (isTRUE(verbose)) + cliID <- cli::cli_progress_bar(name = "HDF5 chunk processing", type = "iter", + total = numChunks, clear = FALSE) + # pb <- utils::txtProgressBar(0, numChunks, style = 3) for (i in seq(numChunks)) { + Sys.sleep(0.1) start <- (i - 1)*chunkSize + 1 end <- if (i*chunkSize > ncol(object)) ncol(object) else i*chunkSize colptrStart <- start @@ -78,7 +82,8 @@ H5Apply <- function( init <- do.call(FUN, c(list(chunk, nnzStart:nnzEnd, start:end, init), fun.args)) - if (isTRUE(verbose)) utils::setTxtProgressBar(pb, i) + # if (isTRUE(verbose)) utils::setTxtProgressBar(pb, i) + if (isTRUE(verbose)) cli::cli_progress_update(id = cliID, set = i) } # Break a new line otherwise next message comes right after the "%" sign. if (isTRUE(verbose)) cat("\n") @@ -182,7 +187,7 @@ safeH5Create <- function(object, #' lig <- restoreH5Liger(lig) restoreH5Liger <- function(object, filePath = NULL) { if (!inherits(object, "liger") && !inherits(object, "ligerDataset")) { - stop("Please specify a liger or ligerDataset object to restore.") + cli::cli_abort("Please specify a {.cls liger} or {.cls ligerDataset} object to restore.") } if (inherits(object, "ligerDataset")) { if (isTRUE(methods::validObject(object, test = TRUE))) { @@ -191,13 +196,12 @@ restoreH5Liger <- function(object, filePath = NULL) { h5.meta <- h5fileInfo(object) if (is.null(filePath)) filePath <- h5.meta$filename if (is.null(filePath)) { - stop("No filename identified") + cli::cli_abort("No filename identified.") } if (!file.exists(filePath)) { - stop("HDF5 file path does not exist:\n", - filePath) + cli::cli_abort("HDF5 file path does not exist: {.file {filePath}}") } - .log("filename identified: ", filePath) + cliID <- cli::cli_process_start("Restoring HDF5 link from: {.file {filePath}}") h5file <- hdf5r::H5File$new(filePath, mode = "r+") h5.meta$filename <- h5file$filename pathChecks <- unlist(lapply(h5.meta[4:10], function(x) { @@ -207,18 +211,23 @@ restoreH5Liger <- function(object, filePath = NULL) { if (any(!pathChecks)) { info.name <- names(pathChecks)[!pathChecks] paths <- unlist(h5.meta[info.name]) - errorMsg <- paste(paste0('HDF5 info "', info.name, - '" not found at path: "', paths, '"'), - collapse = "\n ") - stop(errorMsg) + errMsg_cli <- paste0("HDF5 info {.val ", info.name, "} not found at path: {.val ", paths, "}") + lapply(errMsg_cli, cli::cli_alert_danger) + cli::cli_abort( + "Cannot restore this dataset." + ) + # errorMsg <- paste(paste0('HDF5 info "', info.name, + # '" not found at path: "', paths, '"'), + # collapse = "\n ") + # stop(errorMsg) } barcodes <- h5file[[h5.meta$barcodesName]] if (identical(barcodes, colnames(object))) { - stop("Barcodes in the HDF5 file do not match to object.") + cli::cli_abort("Barcodes in the HDF5 file do not match to object.") } features <- h5file[[h5.meta$genesName]] if (identical(features, rownames(object))) { - stop("Features in the HDF5 file do not match to object.") + cli::cli_abort("Features in the HDF5 file do not match to object.") } # All checks passed! h5.meta$H5File <- h5file @@ -230,25 +239,27 @@ restoreH5Liger <- function(object, filePath = NULL) { scaleData(object, check = FALSE) <- h5file[[h5.meta$scaleData]] } methods::validObject(object) + cli::cli_process_done(id = cliID) } else { # Working for liger object if (!is.null(filePath)) { if (!is.list(filePath) || is.null(names(filePath))) - stop("`filePath` has to be a named list for liger object.") + cli::cli_abort( + "{.var filePath} has to be named list of {.cls liger} objects." + ) } for (d in names(object)) { if (isH5Liger(object, d)) { path <- NULL if (d %in% names(filePath)) { - if (!hdf5r::is.h5file(filePath[[d]])) - warning("Path for dataset \"", d, - "\" is not an HDF5 file: ", - filePath[[d]]) - else path <- filePath[[d]] + if (!hdf5r::is.h5file(filePath[[d]])) { + cli::cli_alert_danger("Path for dataset {.val {d}} is not an HDF5 file: {.file {filePath[[d]]}}") + } else path <- filePath[[d]] } - .log("Restoring dataset \"", d, "\"") + cliID <- cli::cli_process_start("Restoring dataset {.val {d}}") datasets(object, check = FALSE)[[d]] <- restoreH5Liger(dataset(object, d), filePath[[d]]) + cli::cli_process_done(id = cliID) } } } @@ -270,7 +281,7 @@ restoreOnlineLiger <- function(object, file.path = NULL) { .inspectH5Path <- function(path) { if (length(path) != 1 || !is.character(path)) { - stop("`path` has to be a single character.") + cli::cli_abort("{.var path} has to be a single {.cls character}.") } path <- trimws(path, whitespace = "/") path <- strsplit(path, "/")[[1]] diff --git a/R/heatmap.R b/R/heatmap.R index c746eb27..b016f240 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -225,7 +225,10 @@ plotFactorHeatmap <- function( ) if (length(viridisOption) != 1 || !viridisOption %in% viridisAvail) - stop("`viridisOption` has to be one value from the available choices.") + cli::cli_abort( + c("{.var viridisOption} has to be one value from the available choices: ", + "{.val {viridisAvail}}") + ) ## Font-size specification # Broadcast one-param setting to each @@ -391,9 +394,9 @@ plotFactorHeatmap <- function( if (var %in% names(colList)) { df[[var]] <- droplevels(df[[var]]) if (any(!levels(df[[var]]) %in% names(colList[[var]]))) { - stop("Given customized annotation color must have ", - "names matching to all available levels in the ", - "annotation.") + cli::cli_abort( + "Given customized annotation color must have names matching to all available levels in the annotation." + ) } annCol[[var]] <- colList[[var]][levels(df[[var]])] } else { diff --git a/R/import.R b/R/import.R index 571aa616..84d96313 100644 --- a/R/import.R +++ b/R/import.R @@ -83,12 +83,12 @@ createLiger <- function( indices.name = "indicesName", indptr.name = "indptrName", genes.name = "genesName", barcodes.name = "barcodesName")) - if (!is.list(rawData)) stop("`rawData` has to be a named list.") + if (!is.list(rawData)) cli::cli_abort("{.var rawData} has to be a named list.") nData <- length(rawData) if (missing(modal) || is.null(modal)) modal <- "default" modal <- tolower(modal) - modal <- .checkArgLen(modal, nData, repN = TRUE) + modal <- .checkArgLen(modal, nData, repN = TRUE, class = "character") # TODO handle h5 specific argument for hybrid of H5 and in memory stuff. datasets <- list() @@ -188,9 +188,8 @@ createLigerDataset <- function( args <- as.list(environment()) additional <- list(...) # Necessary initialization of slots - if (is.null(rawData) && is.null(normData) && is.null(scaleData)) { - stop("At least one type of expression data (rawData, normData or ", - "scaleData) has to be provided") + if (is.null(rawData) && is.null(normData)) { + cli::cli_abort("At least one of {.field rawData} or {.field normData} has to be provided.") } # Look for proper colnames and rownames cn <- NULL @@ -291,8 +290,7 @@ createH5LigerDataset <- function( genesName <- "raw.var" genes <- h5file[[genesName]][] } else { - stop("Specified `formatType` '", formatType, - "' is not supported for now.") + cli::cli_abort("Specified {.var formatType} ({.val {formatType}}) is not supported for now.") } } else { barcodes <- h5file[[barcodesName]][] @@ -373,22 +371,23 @@ readLiger <- function( h5FilePath = NULL, update = TRUE) { obj <- readRDS(filename) - if (!inherits(obj, "liger")) - stop("Object is not of class \"liger\".") + if (!inherits(obj, "liger")) # nocov start + cli::cli_abort("Object is not of class {.cls liger}.") # nocov end ver <- obj@version if (ver >= package_version("1.99.0")) { if (isH5Liger(obj)) obj <- restoreH5Liger(obj) return(obj) } - .log("Older version (", ver, ") of liger object detected.") + cli::cli_alert_info("Older version ({.val {ver}}) of {.cls liger} object detected.") if (isTRUE(update)) { - .log("Updating the object structure to make it compatible ", - "with current version (", utils::packageVersion("rliger2"), ")") + cli::cli_alert_info( + "Updating the object structure to make it compatible with current version {.val {utils::packageVersion('rliger2')}}" + ) return(convertOldLiger(obj, dimredName = dimredName, clusterName = clusterName, h5FilePath = h5FilePath)) } else { - .log("`update = FALSE` specified. Returning the original object.") + cli::cli_alert_info("{.code update = FALSE} specified. Returning the original object.") return(obj) } } @@ -451,12 +450,15 @@ importPBMC <- function( for (i in seq(nrow(info))) { f <- info$filename[i] if (file.exists(f) && isFALSE(overwrite)) { - warning("File already exists, skipped. set `overwrite = TRUE` ", - "to force downloading: ", f) + cli::cli_alert_warning( + "Skipping file already exists at: {.file {f}}. " + ) + cli::cli_alert_info("Set {.code overwrite = TRUE} to forcing download.") doDownload[i] <- FALSE next } - if (isTRUE(verbose)) .log("Downloading from ", info$url[i], " to ", f) + if (isTRUE(verbose)) + cli::cli_alert_info("Downloading from {.url {info$url[i]}} to {.file {f}}") } if (sum(doDownload) > 0) { utils::download.file(info$url[doDownload], @@ -500,12 +502,15 @@ importBMMC <- function( for (i in seq(nrow(info))) { f <- info$filename[i] if (file.exists(f) && isFALSE(overwrite)) { - warning("File already exists, skipped. set `overwrite = TRUE` ", - "to force downloading: ", f) + cli::cli_alert_warning( + "Skipping file already exists at: {.file {f}}. " + ) + cli::cli_alert_info("Set {.code overwrite = TRUE} to forcing download.") doDownload[i] <- FALSE next } - if (isTRUE(verbose)) .log("Downloading from ", info$url[i], " to ", f) + if (isTRUE(verbose)) + cli::cli_alert_info("Downloading from {.url {info$url[i]}} to {.file {f}}") } if (sum(doDownload) > 0) { utils::download.file(info$url[doDownload], @@ -548,12 +553,15 @@ importCGE <- function( for (i in seq(nrow(info))) { f <- info$filename[i] if (file.exists(f) && isFALSE(overwrite)) { - warning("File already exists, skipped. set `overwrite = TRUE` ", - "to force downloading: ", f) + cli::cli_alert_warning( + "Skipping file already exists at: {.file {f}}. " + ) + cli::cli_alert_info("Set {.code overwrite = TRUE} to forcing download.") doDownload[i] <- FALSE next } - if (isTRUE(verbose)) .log("Downloading from ", info$url[i], " to ", f) + if (isTRUE(verbose)) + cli::cli_alert_info("Downloading from {.url {info$url[i]}} to {.file {f}}") } if (sum(doDownload) > 0) { utils::download.file(info$url[doDownload], @@ -708,34 +716,38 @@ read10X <- function( if (is.null(reference)) { if (length(refsExist) == 1) { reference <- refsExist - .log("Using reference: ", reference) + cli::cli_alert_info("Using referece {.val {reference}}") } else { - stop("Multiple references found, please select one ", - "from: ", paste0(refsExist, collapse = ", ")) + cli::cli_abort( + "Multiple references found, please select one from: {.val {refsExist}}" + ) } } else if (length(reference) == 1) { if (!reference %in% refsExist) { - stop("Specified reference not found, please select ", - "one from: ", paste0(refsExist, collapse = ", ")) + cli::cli_abort( + "Specified reference not found, please select one from: {.val {refsExist}}" + ) } } else { - stop("Multiple reference specified but only one allowed.") + cli::cli_abort("Multiple reference specified but only one allowed.") } path <- file.path(path, reference) } names(path) <- dirSampleNames - .log("Found the following sample folders with possible sub-folder ", - "structure: \n", paste0(dirSampleNames, collapse = ", ")) + cli::cli_alert_info( + c("Found the following sample folders with possible sub-folder structure: ", + "{.val {dirSampleNames}}") + ) } # else mtxDirs } # else mtxDirs allData <- list() - sampleNames <- .checkArgLen(sampleNames, length(path), repN = FALSE) + sampleNames <- .checkArgLen(sampleNames, length(path), repN = FALSE, class = "character") if (is.null(sampleNames) && !is.null(names(path))) { sampleNames <- names(path) } else { if (any(duplicated(sampleNames))) { - stop("Cannot set duplicated sample names.") + cli::cli_abort("Cannot set duplicated sample names.") } } @@ -743,14 +755,13 @@ read10X <- function( if (isTRUE(verbose)) { name <- sampleNames[i] if (is.null(name)) name <- paste0("sample ", i) - .log("Reading from ", name, "...") + cliID <- cli::cli_process_start("Reading from {.val {name}}") } if (is.list(path)) run <- path[[i]] else run <- path[i] if (!dir.exists(run)) { - stop("Directory provided does not exist: ", - normalizePath(run, mustWork = FALSE)) + cli::cli_abort("Directory provided does not exist: {.file {normalizePath(run, mustWork = FALSE)}}") } barcode.loc <- file.path(run, 'barcodes.tsv') gene.loc <- file.path(run, 'genes.tsv') @@ -764,15 +775,13 @@ read10X <- function( matrix.loc <- addgz(matrix.loc) } if (!file.exists(barcode.loc)) { - stop("Barcode file missing. Expecting ", basename(barcode.loc)) + cli::cli_abort("Barcode file is missing. Expecting {.file {barcode.loc}}") } if (!isOldVer && !file.exists(features.loc) ) { - stop("Gene name or features file missing. Expecting ", - basename(features.loc)) + cli::cli_abort("Gene name or features file is missing. Expecting {.file {features.loc}}") } if (!file.exists(matrix.loc)) { - stop("Expression matrix file missing. Expecting ", - basename(matrix.loc)) + cli::cli_abort("Expression matrix file is missing. Expecting {.file {matrix.loc}}") } data <- read10XFiles(matrixPath = matrix.loc, barcodesPath = barcode.loc, featuresPath = ifelse(isOldVer, gene.loc, features.loc), @@ -780,6 +789,7 @@ read10X <- function( cellCol = cellCol) if (isOldVer) names(data) <- "Gene Expression" allData[[i]] <- data + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) } if (!is.null(sampleNames)) names(allData) <- sampleNames if (isTRUE(returnList)) return(allData) @@ -858,23 +868,25 @@ read10XATAC <- function( # Now paths are sample/outs/*_peak_bc_matrix/ path <- file.path(outsPaths, subdir) if (!dir.exists(path)) { - stop("Cannot find folder '", path, "', not standard ", - "`cellranger-", pipeline, "` output. ", - "Please try with the other `pipeline`.") + cli::cli_abort( + c("Cannot find folder {.file {path}}, not standard {.code cellranger-{pipeline}} output. ", + "i" = "Please try with the other {.code pipeline}.") + ) } names(path) <- dirSampleNames - .log("Found the following sample folders with possible sub-folder ", - "structure: \n", paste0(dirSampleNames, collapse = ", ")) + cli::cli_alert_info( + "Found the following sample folders with possible sub-folder structure: {.val {dirSampleNames}}" + ) } # else mtxDirs } # else mtxDirs allData <- list() - sampleNames <- .checkArgLen(sampleNames, length(path), repN = FALSE) + sampleNames <- .checkArgLen(sampleNames, length(path), repN = FALSE, class = "character") if (is.null(sampleNames) && !is.null(names(path))) { sampleNames <- names(path) } else { if (any(duplicated(sampleNames))) { - stop("Cannot set duplicated sample names.") + cli::cli_abort("Cannot set duplicated sample names.") } } @@ -882,14 +894,13 @@ read10XATAC <- function( if (isTRUE(verbose)) { name <- sampleNames[i] if (is.null(name)) name <- paste0("sample ", i) - .log("Reading from ", name, "...") + cliID <- cli::cli_process_start("Reading from {.val {name}}") } if (is.list(path)) run <- path[[i]] else run <- path[i] if (!dir.exists(run)) { - stop("Directory provided does not exist: ", - normalizePath(run, mustWork = FALSE)) + cli::cli_abort("Directory provided does not exist: {.file {normalizePath(run, mustWork = FALSE)}}") } barcode.loc <- switch(pipeline, arc = "barcodes.tsv.gz", @@ -904,15 +915,13 @@ read10XATAC <- function( ) if (!file.exists(barcode.loc)) { - stop("Barcode file missing. Expecting ", basename(barcode.loc)) + cli::cli_abort("Barcode file is missing. Expecting {.file {barcode.loc}}") } if (!file.exists(feature.loc) ) { - stop("Peak or feature file missing. Expecting ", - basename(feature.loc)) + cli::cli_abort("Peak or feature file is missing. Expecting {.file {feature.loc}}") } if (!file.exists(matrix.loc)) { - stop("Expression matrix file missing. Expecting ", - basename(matrix.loc)) + cli::cli_abort("Expression matrix file is missing. Expecting {.file {matrix.loc}}") } data <- read10XFiles(matrixPath = matrix.loc, barcodesPath = barcode.loc, @@ -921,16 +930,18 @@ read10XATAC <- function( geneCol = geneCol, cellCol = cellCol, isATAC = pipeline == "atac") if (pipeline == "arc" && !arcFeatureType %in% names(data)) { - stop("No ATAC data retrieved from cellranger-arc pipeline. ", - "Please see if the following available feature types match ", - "with need and select one for `arcFeatureType`: ", - paste0(names(data), collapse = ", ")) + cli::cli_abort( + c("No ATAC data retrieved from cellranger-arc pipeline. ", + "Please see if the following available feature types match ", + "with need and select one for `arcFeatureType`: {.val {names(data)}}") + ) } data <- switch(pipeline, arc = data[[arcFeatureType]], atac = data[[1]] ) allData[[i]] <- data + cli::cli_process_done(id = cliID) } if (!is.null(sampleNames)) names(allData) <- sampleNames if (isTRUE(returnList)) return(allData) @@ -984,16 +995,14 @@ read10XFiles <- function( "-", feature.names[, 3]) } else { if (ncol(feature.names) < geneCol) { - stop("`geneCol` was set to ", geneCol, " but feature.tsv.gz ", - "(or genes.tsv) only has ", ncol(feature.names), " columns.", - " Try setting `geneCol` to a value <= ", - ncol(feature.names), ".") + cli::cli_abort( + c("{.var geneCol} was set to {.val {geneCol}} but {.file feature.tsv.gz} (or {.file genes.tsv}) only has {ncol(fetures.names)} columns.", + "i" = "Try setting {.var geneCol} to a value <= {ncol(feature.names)}.") + ) } if (any(is.na(feature.names[, geneCol]))) { - warning( - "Some features names are NA. Replacing NA names with ID from the ", - "opposite column requested", - call. = FALSE, immediate. = TRUE + cli::cli_alert_warning( + "Some feature names are NA. Replacing NA names with ID from the opposite column requested" ) na.features <- which(is.na(feature.names[, geneCol])) replacement.column <- ifelse(geneCol == 2, 1, 2) @@ -1009,8 +1018,9 @@ read10XFiles <- function( data_types <- factor(feature.names$V3) lvls <- levels(data_types) if (length(lvls) > 1) { - .log("10X data contains more than one type and is being ", - "returned as a list containing matrices of each type.") + cli::cli_alert_warning( + "10X data contains more than one type and is being returned as a list containing matrices of each type." + ) } expr_name <- "Gene Expression" # Return Gene Expression first diff --git a/R/integration.R b/R/integration.R index ac37b71b..41130fd6 100644 --- a/R/integration.R +++ b/R/integration.R @@ -243,14 +243,12 @@ runINMF.liger <- function( object <- removeMissing(object, orient = "cell", verbose = verbose) data <- lapply(datasets(object), function(ld) { if (is.null(scaleData(ld))) - stop("Scaled data not available. ", - "Run `scaleNotCenter(object)` first") + cli::cli_abort("Scaled data not available. Run {.fn scaleNotCenter} first.") return(scaleData(ld)) }) dataClasses <- sapply(data, function(x) class(x)[1]) if (!all(dataClasses == dataClasses[1])) { - stop("Currently the scaledData of all datasets have to be of the ", - "same class.") + cli::cli_abort("Currently the scaledData of all datasets have to be of the same class.") } out <- .runINMF.list( object = data, @@ -326,8 +324,8 @@ runINMF.Seurat <- function( } for (i in seq_along(Es)) { if (any(Es[[i]]@x < 0)) { - stop("Negative data encountered for integrative Non-negative ", - "Matrix Factorization. Please run `scaleNotCenter()` first.") + cli::cli_abort("Negative data encountered for integrative {.emph Non-negative} Matrix Factorization. + Please run {.fn scaleNotCenter} first.") } } @@ -380,27 +378,23 @@ runINMF.Seurat <- function( features = NULL ) { if (!requireNamespace("RcppPlanc", quietly = TRUE)) # nocov start - stop("RcppPlanc installation required. Currently, please get the ", - "GitHub private repository access from the lab and run: \n", - "devtools::install_github(\"welch-lab/RcppPlanc\")") # nocov end + cli::cli_abort( + "Package {.pkg RcppPlanc} is required for iNMF integration. + Please install it by command: + {.code devtools::install_github('welch-lab/RcppPlanc')}") # nocov end bestResult <- list() bestObj <- Inf bestSeed <- seed for (i in seq(nRandomStarts)) { if (isTRUE(verbose) && nRandomStarts > 1) { - .log("Replicate run ", i, "...") + cli::cli_alert_info("Replicate run [{i}/{nRandomStarts}]") } set.seed(seed = seed + i - 1) - if (inherits(object[[1]], "H5D")) { - # RcppPlanc::bppinmf_h5dense() - stop("TODO: Push Yichen to test bppinmf_h5sparse/bppinmf_h5dense!") - } else { - out <- RcppPlanc::inmf(objectList = object, k = k, lambda = lambda, - niter = nIteration, Hinit = HInit, - Vinit = VInit, Winit = WInit, - verbose = verbose) - } + out <- RcppPlanc::inmf(objectList = object, k = k, lambda = lambda, + niter = nIteration, Hinit = HInit, + Vinit = VInit, Winit = WInit, + verbose = verbose) if (out$objErr < bestObj) { bestResult <- out bestObj <- out$objErr @@ -408,7 +402,7 @@ runINMF.Seurat <- function( } } if (isTRUE(verbose) && nRandomStarts > 1) { - .log("Best objective error: ", bestObj, "\nBest seed: ", bestSeed) + cli::cli_alert_success("Best objective error: {bestObj}; Best seed: {bestSeed}") } barcodeList <- lapply(object, colnames) features <- rownames(object[[1]]) @@ -684,7 +678,7 @@ runOnlineINMF.liger <- function( Es <- lapply(datasets(object), function(ld) { sd <- scaleData(ld) if (is.null(sd)) - stop("Scaled data not available. Run `scaleNotCenter()` first") + cli::cli_abort("Scaled data not available. Run {.fn scaleNotCenter} first.") # if (inherits(sd, "H5D")) return(.H5DToH5Mat(sd)) # else if (inherits(sd, "H5Group")) @@ -699,19 +693,20 @@ runOnlineINMF.liger <- function( BInit <- BInit %||% getMatrix(object, "B", returnList = TRUE) if (is.null(WInit) || any(sapply(VInit, is.null)) || any(sapply(AInit, is.null)) || any(sapply(BInit, is.null))) { - stop("Cannot find complete online iNMF result for current ", - "datasets. Please run `runOnlineINMF()` without `newDataset` ", - "first.") + cli::cli_abort( + "Cannot find complete online iNMF result for current datasets. + Please run {.fn runOnlineINMF} without {.code newDataset} first" + ) } newNames <- names(newDatasets) if (any(newNames %in% names(object))) { - stop("Names of `newDatasets` overlap with existing datasets.") + cli::cli_abort("Names of {.var newDatasets} overlap with existing datasets.") } if (is.list(newDatasets)) { # A list of raw data if (is.null(names(newDatasets))) { - stop("The list of new datasets must be named.") + cli::cli_abort("The list of new datasets must be named.") } for (i in seq_along(newDatasets)) { if (inherits(newDatasets[[i]], "dgCMatrix")) { @@ -721,14 +716,14 @@ runOnlineINMF.liger <- function( ld <- createH5LigerDataset(newDatasets[[i]]) dataset(object, names(newDatasets[i])) <- ld } else { - stop("Cannot interpret `newDatasets` element ", i) + cli::cli_abort("Cannot interpret {.var newDatasets} element {i}") } } } else if (inherits(newDatasets, "liger")) { # A liger object with all new datasets object <- c(object, newDatasets) } else { - stop("`newDatasets` must be either a named list or a liger object") + cli::cli_abort("{.var newDatasets} must be either a named list or a {.cls liger} object") } object <- normalize(object, useDatasets = newNames) @@ -807,27 +802,16 @@ runOnlineINMF.liger <- function( ... ) { if (!requireNamespace("RcppPlanc", quietly = TRUE)) # nocov start - stop("RcppPlanc installation required. Currently, please get the ", - "GitHub private repository access from the lab and run: \n", - "devtools::install_github(\"welch-lab/RcppPlanc\")") # nocov end + cli::cli_abort( + "Package {.pkg RcppPlanc} is required for online iNMF integration. + Please install it by command: + {.code devtools::install_github('welch-lab/RcppPlanc')}") # nocov end nDatasets <- length(object) + length(newDatasets) barcodeList <- c(lapply(object, colnames), lapply(newDatasets, colnames)) names(barcodeList) <- c(names(object), names(newDatasets)) features <- rownames(object[[1]]) if (!is.null(seed)) set.seed(seed) - # # If minibatchSize > smallest invovled dataset, auto reset with warning - # minibatchSize_min <- min(sapply(object, ncol)) - # if (!is.null(newDatasets)) { - # minibatchSize_min2 <- min(sapply(newDatasets, ncol)) - # minibatchSize_min <- min(minibatchSize_min, minibatchSize_min2) - # } - # if (minibatchSize > minibatchSize_min) { - # warning("Minibatch size larger than the smallest dataset involved.\n", - # " Setting to the smallest dataset size: ", minibatchSize_min, - # immediate. = TRUE) - # minibatchSize <- minibatchSize_min - # } res <- RcppPlanc::onlineINMF(objectList = object, newDatasets = newDatasets, project = projection, k = k, lambda = lambda, maxEpoch = maxEpochs, @@ -901,8 +885,8 @@ runOnlineINMF.Seurat <- function( } for (i in seq_along(Es)) { if (any(Es[[i]]@x < 0)) { - stop("Negative data encountered for integrative Non-negative ", - "Matrix Factorization. Please run `scaleNotCenter()` first.") + cli::cli_abort("Negative data encountered for integrative {.emph Non-negative} Matrix Factorization. + Please run {.fn scaleNotCenter} first.") } } @@ -1147,15 +1131,15 @@ runUINMF.liger <- function( Elist <- lapply(datasets(object), function(ld) { if (is.null(scaleData(ld))) - stop("Scaled data not available. ", - "Run `scaleNotCenter(object)` first") + cli::cli_abort("Scaled data not available. Run {.fn scaleNotCenter} first.") return(scaleData(ld)) }) Ulist <- getMatrix(object, "scaleUnsharedData", returnList = TRUE) if (all(sapply(Ulist, is.null))) { - stop("No scaled data for unshared feature found. Run `selectGenes()` ", - "with `useUnsharedDatasets` specified, ", - "and then `scaleNotCenter()`.") + cli::cli_abort( + "No scaled data for unshared feature found. Run {.fn selectGenes} + with {.code useUnsharedDatasets} specified, and then {.fn scaleNotCenter}." + ) } res <- .runUINMF.list(Elist, Ulist, k = k, lambda = lambda, nIteration = nIteration, @@ -1187,10 +1171,16 @@ runUINMF.liger <- function( seed = 1, verbose = getOption("ligerVerbose") ) { + if (!requireNamespace("RcppPlanc", quietly = TRUE)) # nocov start + cli::cli_abort( + "Package {.pkg RcppPlanc} is required for mosaic iNMF integration with unshared features. + Please install it by command: + {.code devtools::install_github('welch-lab/RcppPlanc')}")# nocov end bestObj <- Inf bestRes <- NULL bestSeed <- NULL for (i in seq(nRandomStarts)) { + cli::cli_alert_info("Replicate start [{i}/{nRandomStarts}]") seed <- seed + i - 1 set.seed(seed) res <- RcppPlanc::uinmf(object, unsharedList, k = k, lambda = lambda, @@ -1202,7 +1192,7 @@ runUINMF.liger <- function( } } if (isTRUE(verbose) && nRandomStarts > 1) { - .log("Best objective error: ", bestObj, "\nBest seed: ", bestSeed) + cli::cli_alert_success("Best objective error: {bestObj}; Best seed: {bestSeed}") } rm(res) features <- rownames(object[[1]]) @@ -1323,8 +1313,9 @@ quantileNorm.liger <- function( .checkValidFactorResult(object, checkV = FALSE) reference <- reference %||% names(which.max(sapply(datasets(object), ncol))) reference <- .checkUseDatasets(object, useDatasets = reference) - if (length(reference) != 1) - stop("Should specify only one reference dataset.") + if (length(reference) != 1) { + cli::cli_abort("Should specify only one reference dataset.") + } object <- recordCommand(object, ..., dependencies = "RANN") out <- .quantileNorm.HList( object = getMatrix(object, "H"), @@ -1369,7 +1360,7 @@ quantileNorm.Seurat <- function( resName <- paste0(reduction, "Norm") reduction <- object[[reduction]] if (!inherits(reduction, "DimReduc")) { - stop("Specified `reduction` does not points to a DimReduc.") + cli::cli_abort("Specified {.var reduction} does not points to a {.cls DimReduc}.") } # Retrieve some information. Might have better ways instead of using `@` ## Due to proper formatting in Seurat object, Hconcat is already cell x k @@ -1416,19 +1407,15 @@ quantileNorm.Seurat <- function( set.seed(seed) if (is.character(reference)) { if (length(reference) != 1 || !reference %in% names(object)) - stop("Should specify one existing dataset as reference. ", - "(character `reference` wrong length or not found)") + cli::cli_abort("Should specify one existing dataset as reference.") } else if (is.numeric(reference)) { if (length(reference) != 1 || reference > length(object)) - stop("Should specify one dataset within the range. ", - "(numeric `reference` wrong length or out of bound)") + cli::cli_abort("Should specify one existing dataset as reference.") } else if (is.logical(reference)) { if (length(reference) != length(object) || sum(reference) != 1) - stop("Should specify one dataset within the range. ", - "(logical `reference` wrong length or ", - "too many selection)") + cli::cli_abort("Should specify one existing dataset as reference.") } else { - stop("Unable to understand `reference`. See `?quantileNorm`.") + cli::cli_abort("Unable to understand {.var reference}. See {.code ?quantileNorm}.") } useDims <- useDims %||% seq_len(nrow(object[[1]])) # Transposing all H to cell x k diff --git a/R/liger-class.R b/R/liger-methods.R similarity index 60% rename from R/liger-class.R rename to R/liger-methods.R index b1af5db9..bb11127f 100644 --- a/R/liger-class.R +++ b/R/liger-methods.R @@ -1,164 +1,3 @@ -setClassUnion("dgCMatrix_OR_NULL", c("dgCMatrix", "NULL")) -setClassUnion("matrix_OR_NULL", c("matrix", "NULL")) -setClassUnion("matrixLike_OR_NULL", c( - "matrix", "dgCMatrix", "dgTMatrix", "dgeMatrix", "NULL" -)) -setClassUnion("character_OR_NULL", c("character", "NULL")) -setClassUnion("matrixLike", c( - "matrix", "dgCMatrix", "dgTMatrix", "dgeMatrix" -)) -setClassUnion("Number_or_NULL", c("integer", "numeric", "NULL")) - -setClassUnion("dataframe", c("data.frame", "DataFrame", "NULL", "missing")) - -#' @title liger class -#' @rdname liger-class -#' @docType class -#' @description \code{liger} object is the main data container for LIGER -#' analysis in R. The slot \code{datasets} is a list where each element should -#' be a \linkS4class{ligerDataset} object containing dataset specific -#' information, such as the expression matrices. The other parts of liger object -#' stores information that can be shared across the analysis, such as the cell -#' metadata and factorization result matrices. -#' -#' This manual provides explanation to the \code{liger} object structure as well -#' as usage of class-specific methods. Please see detail sections for more -#' information. -#' -#' For \code{liger} objects created with older versions of rliger package, -#' please try updating the objects individually with -#' \code{\link{convertOldLiger}}. -#' @slot datasets list of \linkS4class{ligerDataset} objects. Use generic -#' \code{dataset}, \code{dataset<-}, \code{datasets} or \code{datasets<-} to -#' interact with. See detailed section accordingly. -#' @slot cellMeta \linkS4class{DFrame} object for cell metadata. Pre-existing -#' metadata, QC metrics, cluster labeling, low-dimensional embedding and etc. -#' are all stored here. Use generic \code{cellMeta}, \code{cellMeta<-}, -#' \code{$}, \code{[[]]} or \code{[[]]<-} to interact with. See detailed section -#' accordingly. -#' @slot varFeatures Character vector of feature names. Use generic -#' \code{varFeatures} or \code{varFeatures<-} to interact with. See detailed -#' section accordingly. -#' @slot W Matrix of gene loading for each factor. See -#' \code{\link{runIntegration}}. -#' @slot H.norm Matrix of aligned factor loading for each cell. See -#' \code{\link{quantileNorm}} and \code{\link{runIntegration}}. -#' @slot commands List of \linkS4class{ligerCommand} objects. Record of -#' analysis. Use \code{commands} to retrieve information. See detailed section -#' accordingly. -#' @slot uns List for unstructured meta-info of analyses or presets. -#' @slot version Record of version of rliger2 package -#' @importClassesFrom S4Vectors DataFrame -#' @importFrom ggplot2 fortify -liger <- setClass( - "liger", - representation( - datasets = "list", - cellMeta = "DataFrame", - varFeatures = "character_OR_NULL", - W = "matrix_OR_NULL", - H.norm = "matrix_OR_NULL", - uns = "list", - commands = "list", - version = "ANY" - ), - methods::prototype( - cellMeta = methods::new("DFrame"), - version = utils::packageVersion("rliger2") - ) -) - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Validity #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -.checkAllDatasets <- function(x) { - for (ld in datasets(x)) { - methods::validObject(ld) - } - return(NULL) -} - -.checkLigerBarcodes <- function(x) { - bcFromDatasets <- unlist(lapply(datasets(x), colnames), use.names = FALSE) - if (!identical(colnames(x), bcFromDatasets)) { - return("liger object barcodes do not match to barcodes in datasets") - } - if (!is.null(x@H.norm)) { - if (!identical(rownames(x@H.norm), bcFromDatasets)) { - return("H.norm barcodes do not match to barcodes in datasets.") - } - } - if (!"dataset" %in% names(cellMeta(x))) { - return("`datasets` variable missing in cellMeta(x)") - } - datasetNamesFromDatasets <- rep(names(x), lapply(datasets(x), ncol)) - names(datasetNamesFromDatasets) <- NULL - - if (!identical(datasetNamesFromDatasets, as.character(x$dataset))) { - return("names of datasets do not match - `datasets` variable in cellMeta") - } - return(NULL) -} - -.checkLigerVarFeature <- function(x) { - if (!is.null(varFeatures(x)) && - length(varFeatures(x)) > 0) { - if (!is.null(x@W)) - if (!identical(rownames(x@W), varFeatures(x))) - return("Variable features do not match dimension of W matrix") - for (d in names(x)) { - ld <- dataset(x, d) - if (!is.null(ld@V)) { - if (!identical(rownames(ld@V), varFeatures(x))) - return(paste("Variable features do not match dimension", - "of V matrix in dataset", d)) - } - - if (!is.null(scaleData(ld))) { - if (!isH5Liger(ld)) { - if (!identical(rownames(scaleData(ld)), varFeatures(x))) - return(paste("Variable features do not match dimension", - "of scaleData in dataset", d)) - } else { - if (inherits(scaleData(ld), "H5D")) { - if (scaleData(ld)$dims[1] != length(varFeatures(x))) - return(paste("Variable features do not match ", - "dimension of scaleData in dataset ", - "(H5)", d)) - } else if (inherits(scaleData(ld), "H5Group")) { - if (scaleData(ld)[["featureIdx"]]$dims != length(varFeatures(x))) { - return(paste("Variable features do not match ", - "dimension of scaleData in dataset ", - "(H5)", d)) - } - scaleDataIdx <- scaleData(ld)[["featureIdx"]][] - if (!identical(rownames(ld)[scaleDataIdx], varFeatures(x))) { - return("HDF5 scaled data feature index does not ", - "match variable features") - } - } - } - } - } - } - return(NULL) -} - -.valid.liger <- function(object) { - # message("Checking liger object validity") - res <- .checkAllDatasets(object) - if (!is.null(res)) return(res) - res <- .checkLigerBarcodes(object) - if (!is.null(res)) return(res) - res <- .checkLigerVarFeature(object) - if (!is.null(res)) return(res) - # TODO more checks -} - -setValidity("liger", .valid.liger) - #' Check if given liger object if under new implementation #' @param object A liger object #' @return \code{TRUE} if the version of \code{object} is later than or equal to @@ -191,10 +30,6 @@ is.newLiger <- function(object) { #' \code{NULL} uses all cells. #' @param as.data.frame Logical, whether to apply #' \code{\link[base]{as.data.frame}} on the subscription. Default \code{FALSE}. -#' @param i,j Feature and cell index for \code{`[`} method. For \code{`[[`} -#' method, use a single variable name with \code{i} while \code{j} is not -#' applicable. -#' @param drop Not applicable. #' @param slot Name of slot to retrieve matrix from. Options shown in Usage. #' @param returnList Logical, whether to force return a list even when only one #' dataset-specific matrix (i.e. expression matrices, H, V or U) is requested. @@ -276,6 +111,16 @@ setMethod( #' with \code{NULL} as the first element and valid cell identifiers as the #' second element. For \code{colnames<-} method, the character vector of cell #' identifiers. \code{rownames<-} method is not applicable. +#' @section Subsetting: +#' For more detail of subsetting a \code{liger} object or a +#' \linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} +#' and \code{\link{subsetLigerDataset}}. Here, we set the S4 method +#' "single-bracket" \code{[} as a quick wrapper to subset a \code{liger} object. +#' Note that \code{j} serves as cell subscriptor which can be any valid index +#' refering the collection of all cells (i.e. \code{rownames(cellMeta(obj))}). +#' While \code{i}, the feature subscriptor can only be character vector because +#' the features for each dataset can vary. \code{...} arugments are passed to +#' \code{subsetLiger} so that advanced options are allowed. #' @rdname liger-class #' @export setMethod("dim", "liger", function(x) { @@ -306,77 +151,51 @@ setReplaceMethod("dimnames", c("liger", "list"), function(x, value) { #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Subsetting #### #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#' @section Subsetting: -#' For more detail of subsetting a \code{liger} object or a -#' \linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} -#' and \code{\link{subsetLigerDataset}}. Here, we set the S4 method -#' "single-bracket" \code{[} as a quick wrapper to subset a \code{liger} object. -#' Note that \code{j} serves as cell subscriptor which can be any valid index -#' refering the collection of all cells (i.e. \code{rownames(cellMeta(obj))}). -#' While \code{i}, the feature subscriptor can only be character vector because -#' the features for each dataset can vary. \code{...} arugments are passed to -#' \code{subsetLiger} so that advanced options are allowed. -#' @export -#' @rdname liger-class -setMethod( - "[", - signature(x = "liger", i = "character", j = "missing"), - function(x, i, j, ...) subsetLiger(x, featureIdx = i, cellIdx = NULL, ...) -) - -#' @export -#' @rdname liger-class -setMethod( - "[", - signature(x = "liger", i = "missing", j = "index"), - function(x, i, j, ...) subsetLiger(x, featureIdx = NULL, cellIdx = j, ...) -) +#' Subset liger with brackets +#' @name sub-liger +#' @param x A \linkS4class{liger} object +#' @param i Feature subscriptor, passed to \code{featureIdx} of +#' \code{\link{subsetLiger}}. +#' @param j Cell subscriptor, passed to \code{cellIdx} of +#' \code{\link{subsetLiger}}. +#' @param ... Additional arguments passed to \code{\link{subsetLiger}}. #' @export -#' @rdname liger-class -setMethod( - "[", - signature(x = "liger", i = "character", j = "index"), - function(x, i, j, ...) subsetLiger(x, featureIdx = i, cellIdx = j, ...) -) +#' @return Subset of \code{x} with specified features and cells. +#' @seealso \code{\link{subsetLiger}} +#' @method [ liger +#' @examples +#' pbmcPlot[varFeatures(pbmcPlot)[1:10], 1:10] +`[.liger` <- function(x, i, j, ...) { + if (missing(i)) i <- NULL + if (missing(j)) j <- NULL + subsetLiger(x, featureIdx = i, cellIdx = j, ...) +} +# setMethod( +# "[", +# signature(x = "liger", i = "character", j = "missing"), +# function(x, i, j, ...) subsetLiger(x, featureIdx = i, cellIdx = NULL, ...) +# ) +# +# #' @export +# #' @rdname liger-class +# setMethod( +# "[", +# signature(x = "liger", i = "missing", j = "index"), +# function(x, i, j, ...) subsetLiger(x, featureIdx = NULL, cellIdx = j, ...) +# ) +# +# #' @export +# #' @rdname liger-class +# setMethod( +# "[", +# signature(x = "liger", i = "character", j = "index"), +# function(x, i, j, ...) subsetLiger(x, featureIdx = i, cellIdx = j, ...) +# ) #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Datasets #### #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#' @section Dataset access: -#' \code{datasets()} method only accesses the \code{datasets} slot, the list of -#' \linkS4class{ligerDataset} objects. \code{dataset()} method accesses a single -#' dataset, with subsequent cell metadata updates and checks bonded when adding -#' or modifying a dataset. Therefore, when users want to modify something inside -#' a \code{ligerDataset} while no cell metadata change should happen, it is -#' recommended to use: \code{datasets(x)[[name]] <- ligerD} for efficiency, -#' though the result would be the same as \code{dataset(x, name) <- ligerD}. -#' -#' \code{length()} and \code{names()} methods are implemented to access the -#' number and names of datasets. \code{names<-} method is supported for -#' modifying dataset names, with taking care of the "dataset" variable in cell -#' metadata. -#' @section Matrix access: -#' For \code{liger} object, \code{rawData()}, \code{normData}, -#' \code{scaleData()} and \code{scaleUnsharedData()} methods are exported for -#' users to access the corresponding feature expression matrix with -#' specification of one dataset. For retrieving a type of matrix from multiple -#' datasets, please use \code{getMatrix()} method. -#' -#' When only one matrix is expected to be retrieved by \code{getMatrix()}, the -#' matrix itself will be returned. A list will be returned if multiple matrices -#' is requested (by querying multiple datasets) or \code{returnList} is set to -#' \code{TRUE}. -#' @export -#' @rdname liger-class -setGeneric("datasets", function(x, check = NULL) standardGeneric("datasets")) - -#' @export -#' @rdname liger-class -setGeneric( - "datasets<-", - function(x, check = TRUE, value) standardGeneric("datasets<-") -) #' @export #' @rdname liger-class @@ -403,15 +222,7 @@ setReplaceMethod("datasets", signature(x = "liger", check = "missing"), x }) -#' @export -#' @rdname liger-class -setGeneric("dataset", function(x, dataset = NULL) standardGeneric("dataset")) -#' @export -#' @rdname liger-class -setGeneric("dataset<-", function(x, dataset, type = NULL, qc = TRUE, value) { - standardGeneric("dataset<-") -}) #' @export #' @rdname liger-class @@ -420,8 +231,7 @@ setMethod("dataset", signature(x = "liger", dataset = "character_OR_NULL"), if (is.null(dataset)) return(datasets(x)[[1]]) else { if (!dataset %in% names(x)) { - stop('Specified dataset name "', dataset, - '" not found in liger object.') + cli::cli_abort("Specified dataset name {.val {dataset}} not found in {.cls liger} object") } return(datasets(x)[[dataset]]) } @@ -491,6 +301,7 @@ setMethod("dataset", signature(x = "liger", dataset = "numeric"), } return(newDF) } + .expandDataFrame <- function(df, idx) { dfList <- as.list(df) dfList <- lapply(dfList, function(x, idx) { @@ -532,7 +343,7 @@ setReplaceMethod("dataset", signature(x = "liger", dataset = "character", x@cellMeta <- cm # x@W is genes x k, no need to worry if (!is.null(x@H.norm)) { - message("Filling in NAs to H.norm matrix") + cli::cli_alert_info("Finning in NAs to H.norm matrix") H.normNew <- matrix( NA, ncol(value), ncol(x@H.norm), dimnames = list(colnames(value), NULL)) @@ -568,22 +379,24 @@ setReplaceMethod("dataset", signature(x = "liger", dataset = "character", #' @export #' @rdname liger-class -setReplaceMethod("dataset", signature(x = "liger", dataset = "character", - type = "missing", qc = "ANY", - value = "NULL"), - function(x, dataset, type = NULL, qc = TRUE, value) { - if (!dataset %in% names(x)) { - warning("Specified dataset name not found in ", - "liger object. Nothing would happen.") - } else { - idxToRemove <- x$dataset == dataset - x@datasets[[dataset]] <- NULL - x@cellMeta <- x@cellMeta[!idxToRemove, , drop = FALSE] - x@H.norm <- x@H.norm[!idxToRemove, , drop = FALSE] - x@cellMeta$dataset <- droplevels(x@cellMeta$dataset) - } - x - }) +setReplaceMethod( + "dataset", + signature(x = "liger", dataset = "character", type = "missing", qc = "ANY", + value = "NULL"), + function(x, dataset, type = NULL, qc = TRUE, value) { + if (!dataset %in% names(x)) { + cli::cli_alert_warning( + "Specified dataset name {.val {dataset}} not found in {.cls liger} object. Nothing would happen.") + } else { + idxToRemove <- x$dataset == dataset + x@datasets[[dataset]] <- NULL + x@cellMeta <- x@cellMeta[!idxToRemove, , drop = FALSE] + x@H.norm <- x@H.norm[!idxToRemove, , drop = FALSE] + x@cellMeta$dataset <- droplevels(x@cellMeta$dataset) + } + x + } +) #' @rdname liger-class #' @export @@ -622,66 +435,6 @@ setMethod("length", signature(x = "liger"), function(x) { #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Cell metadata #### #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#' @export -#' @rdname liger-class -#' @section Cell metadata access: -#' Three approaches are provided for access of cell metadata. A generic function -#' \code{cellMeta} is implemented with plenty of options and multi-variable -#' accessibility. Besides, users can use double-bracket (e.g. -#' \code{ligerObj[[varName]]}) or dollor-sign (e.g. \code{ligerObj$nUMI}) to -#' access or modify single variables. -#' -#' For users' convenience of generating a customized ggplot with available cell -#' metadata, the S3 method \code{fortify.liger} is implemented. With this under -#' the hook, users can create simple ggplots by directly starting with -#' \code{ggplot(ligerObj, aes(...))} where cell metadata variables can be -#' directly thrown into \code{aes()}. -#' -#' Special partial metadata insertion is implemented specifically for mapping -#' categorical annotation from sub-population (subset object) back to original -#' experiment (full-size object). For example, when sub-clustering and -#' annotation is done for a specific cell-type of cells (stored in -#' \code{subobj}) subset from an experiment (stored as \code{obj}), users can do -#' \code{cellMeta(obj, "sub_ann", cellIdx = colnames(subobj)) <- subobj$sub_ann} -#' to map the value back, leaving other cells non-annotated with NAs. Plotting -#' with this variable will then also show NA cells with default grey color. -#' Furthermore, sub-clustering labels for other cell types can also be mapped -#' to the same variable. For example, \code{cellMeta(obj, "sub_ann", -#' cellIdx = colnames(subobj2)) <- subobj2$sub_ann}. As long as the labeling -#' variables are stored as factor class (categorical), the levels (category -#' names) will be properly handled and merged. Other situations follow the R -#' default behavior (e.g. categories might be converted to integer numbers if -#' mapped to numerical variable in the original object). Note that this feature -#' is only available with using the generic function \code{cellMeta} but not -#' with the \code{`[[`} or \code{`$`} accessing methods due to syntax reasons. -#' -#' The generic \code{defaultCluster} works as both getter and setter. As a -#' setter, users can do \code{defaultCluster(obj) <- "existingVariableName"} to -#' set a categorical variable as default cluster used for visualization or -#' downstream analysis. Users can also do \code{defaultCluster(obj, -#' "newVarName") <- factorOfLabels} to push new labeling into the object and set -#' as default. For getter method, the function returns a factor object of the -#' default cluster labeling. Argument \code{useDatasets} can be used for -#' requiring that given or retrieved labeling should match with cells in -#' specified datasets. We generally don't recommend setting \code{"dataset"} as -#' a default cluster because it is a preserved (always existing) field in -#' metadata and can lead to meaningless result when running analysis that -#' utilizes both clustering information and the dataset source information. -setGeneric( - "cellMeta", - function(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, as.data.frame = FALSE, ...) { - standardGeneric("cellMeta") - } -) - -#' @export -#' @rdname liger-class -setGeneric( - "cellMeta<-", - function(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, check = FALSE, value) { - standardGeneric("cellMeta<-") - } -) .subsetCellMeta <- function( object, @@ -696,8 +449,8 @@ setGeneric( if (!is.null(columns)) { notFound <- !columns %in% colnames(res) if (any(notFound)) { - warning("Specified variables from cellMeta not found: ", - .nfstr(columns, colnames(res))) + cli::cli_alert_danger( + "Specified variables from cellMeta not found: {.val {columns[notFound]}}") columns <- columns[!notFound] } res <- res[, columns, ...] @@ -713,7 +466,7 @@ setGeneric( cellIdx <- .idxCheck(object, idx = cellIdx, orient = "cell") if (is.vector(res) || is.factor(res)) res <- res[cellIdx] else if (!is.null(dim(res))) res <- res[cellIdx, , ...] - else stop("Result before idx subscription corrupted") + else cli::cli_abort("Result before idx subscription corrupted") } return(res) } @@ -774,26 +527,64 @@ setReplaceMethod( #' @export #' @rdname liger-class +#' @param inplace For \code{cellMeta<-} method, when \code{columns} is for +#' existing variable and \code{useDatasets} or \code{cellIdx} indicate partial +#' insertion to the object, whether to by default (\code{TRUE}) in-place insert +#' \code{value} into the variable for selected cells or to replace the whole +#' variable with non-selected part left as NA. setReplaceMethod( "cellMeta", signature(x = "liger", columns = "character"), - function(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, check = FALSE, value) { + function(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, + inplace = TRUE, check = FALSE, value) { + # 1 - check cell selection if (is.null(cellIdx) && !is.null(useDatasets)) { if (!is.character(useDatasets)) useDatasets <- names(x)[useDatasets] cellIdx <- which(x@cellMeta$dataset %in% useDatasets) } else { cellIdx <- .idxCheck(x, cellIdx, "cell") } + if (length(cellIdx) == 0) + cli::cli_abort("No cell selected with either {.val cellIdx} or {.var useDatasets}.") + + # 2 - check value matching or length/dimension + barcodes <- colnames(x)[cellIdx] if (is.null(dim(value))) { # Vector/factor like - value <- .checkArgLen(value, n = length(cellIdx)) - } - # if (is.null(dim(value)) && length(value) != length(cellIdx)) { - # stop("Length of value does not match with cell index.") - # } - if (!is.null(dim(value)) && nrow(value) != length(cellIdx)) { - stop("nrow of value does not match with cell index.") + if (is.null(names(value))) { + # No name matching, require exact length + value <- .checkArgLen(value, n = length(cellIdx), class = c("vector", "factor")) + } else { + if (!all(barcodes %in% names(value))) { + cli::cli_abort( + c("{.code names(value)} do not contain all cells selected. ", + "These are not involved: ", + "{.val {barcodes[!barcodes %in% names(value)]}}") + ) + } + value <- value[barcodes] + } + } else { + # matrix like + if (is.null(rownames(value))) { + # no rowname matching, require extact nrow + if (nrow(value) != length(cellIdx)) { + cli::cli_abort( + "{.code nrow(value)} ({nrow(value)}) does not match with cells selected ({length(cellIdx)}).") + } + } else { + if (!all(barcodes %in% rownames(value))) { + cli::cli_abort( + c("{.code rownames(value)} do not contain all cells selected. ", + "These are not involved: ", + "{.val {barcodes[!barcodes %in% rownames(value)]}}") + ) + } + value <- value[barcodes, , drop = FALSE] + } } + + # 3 - Insert value if (length(cellIdx) == ncol(x)) { x@cellMeta[[columns]] <- value } else if (length(cellIdx) < ncol(x)) { @@ -811,65 +602,322 @@ setReplaceMethod( } else { x@cellMeta[[columns]][cellIdx] <- value } - if (!is.null(names(value))) { - if (!identical(colnames(x)[cellIdx], names(value))) { - warning("Names of inserted values do not ", - "match to cell IDs at specified index ", - "of the object. Forced to store using ", - "object colnames.") - } - } } else { # matrix like - x@cellMeta[[columns]] <- matrix(NA, ncol(x), ncol(value)) + x@cellMeta[[columns]] <- matrix( + NA, ncol(x), ncol(value), + dimnames = list(colnames(x), colnames(value)) + ) x@cellMeta[[columns]][cellIdx,] <- value - if (!is.null(colnames(value))) { - colnames(x@cellMeta[[columns]]) <- colnames(value) - } - if (!is.null(rownames(value))) { - if (!identical(rownames(value), colnames(x)[cellIdx])) { - warning("Rownames of inserted values do not match ", - "to cell IDs at specified index of the ", - "object. Forced to store using object ", - "colnames.") - } - } } } else { - if (is.null(dim(value)) && is.null(dim(x@cellMeta[[columns]]))) { - # Both are 1D - if (is.factor(value) && is.factor(x@cellMeta[[columns]])) { - charVar <- as.character(x@cellMeta[[columns]]) - charVar[cellIdx] <- as.character(value) - x@cellMeta[[columns]] <- - factor( - charVar, - levels = unique(c(levels(x@cellMeta[[columns]]), - levels(value))) - ) + if (isTRUE(inplace)) { + # Modifying existing variable + if (is.null(dim(value)) && is.null(dim(x@cellMeta[[columns]]))) { + # Both are 1-D + if (is.factor(value) && is.factor(x@cellMeta[[columns]])) { + charVar <- as.character(x@cellMeta[[columns]]) + charVar[cellIdx] <- as.character(value) + x@cellMeta[[columns]] <- + factor( + charVar, + levels = unique(c(levels(x@cellMeta[[columns]]), + levels(value))) + ) + } else { + x@cellMeta[[columns]][cellIdx] <- value + } + } else if (!is.null(dim(value)) && !is.null(dim(x@cellMeta[[columns]]))) { + # Both are dimensional + if (ncol(value) != ncol(x@cellMeta[[columns]])) { + cli::cli_abort("Cannot insert value to a variable of different dimensionality") + } + x@cellMeta[[columns]][cellIdx,] <- value } else { - x@cellMeta[[columns]][cellIdx] <- value + cli::cli_abort("Cannot insert value to a variable of different dimensionality") } - } else if (!is.null(dim(value)) && !is.null(dim(x@cellMeta[[columns]]))) { - # Both are dimensional - if (ncol(value) != ncol(x@cellMeta[[columns]])) { - stop("Cannot insert value to a variable of different ", - "dimensionality") - } - x@cellMeta[[columns]][cellIdx,] <- value } else { - stop("Cannot insert value to a variable of different ", - "dimensionality") + x@cellMeta[[columns]] <- NULL + # Remove and go to "Add new variable" case above + cellMeta(x, columns = columns, cellIdx = cellIdx, check = check) <- value } } } else { - stop("`cellIdx` pointing to more cells than available") + cli::cli_abort("{.var cellIdx} pointing to more cells than available") } if (isTRUE(check)) methods::validObject(x) x } ) + +#' @export +#' @rdname liger-class +setMethod("rawData", c("liger", "ANY"), + function(x, dataset = NULL) { + if (is.null(dataset)) { + getMatrix(x, slot = "rawData", returnList = TRUE) + } else { + getMatrix(x, "rawData", dataset = dataset) + } + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "rawData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@datasets[[dataset]]@rawData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "rawData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (!isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@datasets[[dataset]]@rawData <- value + if (isTRUE(check)) methods::validObject(x@datasets[[dataset]]) + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setMethod("normData", c("liger", "ANY"), + function(x, dataset = NULL) { + if (is.null(dataset)) { + getMatrix(x, slot = "normData", returnList = TRUE) + } else { + getMatrix(x, "normData", dataset = dataset) + } + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "normData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@datasets[[dataset]]@normData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "normData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (!isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@datasets[[dataset]]@normData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setMethod( + "scaleData", + signature(x = "liger", dataset = "ANY"), + function(x, dataset = NULL) { + if (is.null(dataset)) { + getMatrix(x, slot = "scaleData", returnList = TRUE) + } else { + getMatrix(x, "scaleData", dataset = dataset) + } + } +) + + +#' @export +#' @rdname liger-class +setReplaceMethod( + "scaleData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@datasets[[dataset]]@scaleData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "scaleData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (!isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@datasets[[dataset]]@scaleData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "scaleData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5Group"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (!isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@datasets[[dataset]]@scaleData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setMethod( + "scaleUnsharedData", + signature(x = "liger", dataset = "character"), + function(x, dataset) { + scaleUnsharedData(dataset(x, dataset)) + } +) + + +#' @export +#' @rdname liger-class +setMethod( + "scaleUnsharedData", + signature(x = "liger", dataset = "numeric"), + function(x, dataset) { + scaleUnsharedData(dataset(x, dataset)) + } +) + + +#' @export +#' @rdname liger-class +setReplaceMethod( + "scaleUnsharedData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@datasets[[dataset]]@scaleUnsharedData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "scaleUnsharedData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (!isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@datasets[[dataset]]@scaleUnsharedData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "scaleUnsharedData", + signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5Group"), + function(x, dataset = NULL, check = TRUE, value) { + dataset <- .checkUseDatasets(x, dataset) + if (length(dataset) != 1) cli::cli_abort("Need to specify one dataset to insert.") + if (!isH5Liger(x, dataset)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@datasets[[dataset]]@scaleUnsharedData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + + +#' @export +#' @rdname liger-class +setMethod( + "getMatrix", signature(x = "liger"), + function(x, + slot = c("rawData", "normData", "scaleData", + "scaleUnsharedData", "H", "V", "U", "A", "B", + "W", "H.norm"), + dataset = NULL, + returnList = FALSE) { + slot <- match.arg(slot) + if (slot == "W") return(x@W) + if (slot == "H.norm") return(x@H.norm) + if (is.null(dataset)) { + return(lapply(datasets(x), function(ld) getMatrix(ld, slot))) + } else { + if (length(dataset) == 1) { + if (isTRUE(returnList)) { + result <- list(getMatrix(dataset(x, dataset), slot)) + names(result) <- dataset + return(result) + } else if (isFALSE(returnList)) + return(getMatrix(dataset(x, dataset), slot)) + } else { + lds <- datasets(x)[dataset] + return(lapply(lds, function(ld) getMatrix(ld, slot))) + } + } + }) + +#' @export +#' @rdname liger-class +setMethod("getH5File", + signature = signature(x = "liger", dataset = "ANY"), + function(x, dataset = NULL) { + if (is.null(dataset)) dataset <- names(x) + dataset <- .checkUseDatasets(x, dataset) + results <- lapply(datasets(x)[dataset], + function(ld) h5fileInfo(ld, "H5File")) + if (length(results) == 1) results <- results[[1]] + results + }) + + #' Get cell metadata variable #' @name sub-sub-liger #' @param x A \linkS4class{liger} object @@ -944,23 +992,6 @@ setReplaceMethod("$", signature(x = "liger"), cellMeta(x, columns = name) <- value return(x) }) -#' @export -#' @rdname liger-class -setGeneric( - "defaultCluster", - function(x, useDatasets = NULL, ...) { - standardGeneric("defaultCluster") - } -) - -#' @export -#' @rdname liger-class -setGeneric( - "defaultCluster<-", - function(x, name = NULL, useDatasets = NULL, ..., value) { - standardGeneric("defaultCluster<-") - } -) #' @export #' @rdname liger-class @@ -987,17 +1018,14 @@ setReplaceMethod( "defaultCluster", signature(x = "liger", value = "character"), function(x, name = NULL, useDatasets = NULL, ..., value) { - useDatasets <- .checkUseDatasets(x, useDatasets) - cellIdx <- x$dataset %in% useDatasets if (length(value) == 1) { # If doing defaultCluster(obj) <- "someName" if (!is.null(name)) { - warning("Cannot have `name` when selecting a name with ", - "`value`.") + cli::cli_alert_danger("Cannot have {.code name} when selecting a variable with {.code value}.") } name <- value if (!name %in% colnames(cellMeta(x))) { - stop("Selected name does not exist in `cellMeta(x)`") + cli::cli_abort("Selected variable does not exist in {.code cellMeta(x)}.") } x@uns$defaultCluster <- name } else { @@ -1018,14 +1046,27 @@ setReplaceMethod( useDatasets <- .checkUseDatasets(x, useDatasets) cellIdx <- x$dataset %in% useDatasets if (length(value) != sum(cellIdx)) { - stop("Length of `value` does not match with the number of cells") + cli::cli_abort("Length of {.code value} does not match with the number of cells.") } if (is.null(name)) { - .log("Storing given cluster labels to cellMeta(x) field: ", - "\"defaultCluster\"") + cli::cli_alert_info( + c("Storing given cluster labels to {.code cellMeta(x)} field: ", + "{.val defaultCluster}.") + ) name <- "defaultCluster" } if (is.null(names(value))) names(value) <- colnames(x)[cellIdx] + else { + if (all(names(value) %in% colnames(x)[cellIdx])) { + value <- value[colnames(x)[cellIdx]] + } else { + cli::cli_abort( + c(x = "Not all {.code names(value)} match with target cells: ", + "{.val {names(value)[!names(value) %in% colnames(x)[cellIdx]]}}", + i = "Please have a check or try {.code unname(value)}.") + ) + } + } cellMeta(x, name, cellIdx) <- value x@uns$defaultCluster <- name return(x) @@ -1046,35 +1087,6 @@ setReplaceMethod( } ) -#' @export -#' @rdname liger-class -#' @section Dimension reduction access: -#' Currently, low-dimensional representaion of cells, presented as dense -#' matrices, are all stored in \code{cellMeta} slot, and can totally be accessed -#' with generics \code{cellMeta} and \code{cellMeta<-}. In addition to that, -#' we provide specific generics \code{dimRed} and \code{dimRed<-} for getting -#' and setting matrix like cell metadata, respectively. Adding a matrix to the -#' object looks as simple as \code{dimRed(obj, "name") <- matrixLike}. It can -#' be retrived back with \code{dimRed(obj, "name")}. Similar to having a default -#' cluster labeling, we also constructed the feature of default dimRed. It can -#' be set with \code{defaultDimRed(obj) <- "existingMatLikeVar"} and the matrix -#' can be retrieved with \code{defaultDimRed(obj)}. -setGeneric( - "dimRed", - function(x, name = NULL, useDatasets = NULL, ...) { - standardGeneric("dimRed") - } -) - -#' @export -#' @rdname liger-class -setGeneric( - "dimRed<-", - function(x, name = NULL, useDatasets = NULL, ..., value) { - standardGeneric("dimRed<-") - } -) - #' @export #' @rdname liger-class setMethod( @@ -1088,14 +1100,14 @@ setMethod( if (is.null(name)) { for (i in seq_along(cellMeta(x))) { if (!is.null(dim(cellMeta(x)[[i]]))) { - warning("No default dimRed recorded. Returning the first ", - "matrix like object in cellMeta(object)") + cli::cli_alert_warning( + "No default dimRed recorded. Returning the first matrix alike in {.code cellMeta(object)}.") dimred <- cellMeta(x)[[i]] break } } if (is.null(dimred)) { - stop("No possible dimRed can be found in this liger object.") + cli::cli_abort("No possible dimRed can be found in this {.cls liger} object.") } } else { dimred <- cellMeta(x, name, x$dataset %in% useDatasets) @@ -1117,7 +1129,7 @@ setMethod( useDatasets <- .checkUseDatasets(x, useDatasets) dimred <- cellMeta(x, name, x$dataset %in% useDatasets) if (is.null(dim(dimred))) { - stop("Retrieved data for \"", name, "\" is not a matrix.") + cli::cli_abort("Retrieved data for {.val {name}} is not a matrix.") } dimred <- as.matrix(dimred) rownames(dimred) <- colnames(x)[x$dataset %in% useDatasets] @@ -1150,24 +1162,6 @@ setReplaceMethod( } ) -#' @export -#' @rdname liger-class -setGeneric( - "defaultDimRed", - function(x, useDatasets = NULL) { - standardGeneric("defaultDimRed") - } -) - -#' @export -#' @rdname liger-class -setGeneric( - "defaultDimRed<-", - function(x, name, useDatasets = NULL, value) { - standardGeneric("defaultDimRed<-") - } -) - #' @export #' @rdname liger-class setMethod( @@ -1189,10 +1183,10 @@ setReplaceMethod( value <- value[1] dimred <- cellMeta(x, value) if (is.null(dim(dimred))) { - stop("Specified variable is not matrix like.") + cli::cli_abort("Specified variable is not a matrix alike.") } if (ncol(dimred) == 0) { - stop("Cannot set unexisting variable as default dimRed.") + cli::cli_abort("Cannot set unexisting variable as default dimRed.") } x@uns$defaultDimRed <- value return(x) @@ -1215,25 +1209,6 @@ setReplaceMethod( } ) -#' @export -#' @rdname liger-class -#' @section Variable feature access: -#' The \code{varFeatures} slot allows for character vectors of gene names. -#' \code{varFeatures(x)} returns this vector and \code{value} for -#' \code{varFeatures<-} method has to be a character vector or \code{NULL}. -#' The replacement method, when \code{check = TRUE} performs checks on gene -#' name consistency check across the \code{scaleData}, \code{H}, \code{V} slots -#' of inner \code{ligerDataset} objects as well as the \code{W} and -#' \code{H.norm} slots of the input \code{liger} object. -setGeneric("varFeatures", function(x) standardGeneric("varFeatures")) - -#' @export -#' @rdname liger-class -setGeneric( - "varFeatures<-", - function(x, check = TRUE, value) standardGeneric("varFeatures<-") -) - #' @export #' @rdname liger-class setMethod("varFeatures", signature(x = "liger"), @@ -1256,10 +1231,9 @@ setReplaceMethod( all(value %in% rownames(ld)) }), use.names = FALSE) if (!all(checkResult)) { - problem <- names(x)[!checkResult] - warning("Not all variable features passed are ", - "found in datasets: ", - paste(problem, collapse = ", ")) + cli::cli_alert_warning( + "Not all variable features passed are found in datasets: {.val {names(x)[!checkResult]}}" + ) } } x @@ -1267,22 +1241,6 @@ setReplaceMethod( ) - -#' @export -#' @rdname liger-class -setGeneric("varUnsharedFeatures", function(x, dataset = NULL) { - standardGeneric("varUnsharedFeatures") -}) - -#' @export -#' @rdname liger-class -setGeneric( - "varUnsharedFeatures<-", - function(x, dataset, check = TRUE, value) { - standardGeneric("varUnsharedFeatures<-") - } -) - #' @export #' @rdname liger-class setMethod("varUnsharedFeatures", signature(x = "liger"), @@ -1298,12 +1256,6 @@ setMethod("varUnsharedFeatures", signature(x = "liger"), } ) -#' @export -#' @rdname liger-class -setMethod("varUnsharedFeatures", - signature(x = "ligerDataset", dataset = "missing"), - function(x, dataset = NULL) x@varUnsharedFeatures) - #' @export #' @rdname liger-class setReplaceMethod( @@ -1314,32 +1266,13 @@ setReplaceMethod( x@datasets[[dataset]]@varUnsharedFeatures <- value if (isTRUE(check)) { if (!all(value %in% rownames(x@datasets[[dataset]]))) { - warning("Not all features passed are found in dataset \"", - dataset, "\".") - } - } - return(x) - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "varUnsharedFeatures", - signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "character"), - function(x, dataset = NULL, check = TRUE, value) { - x@varUnsharedFeatures <- value - if (isTRUE(check)) { - if (!all(value %in% rownames(x))) { - warning("Not all features passed are found.") + cli::cli_alert_warning("Not all features passed are found in dataset {.val {dataset}}.") } } return(x) } ) - - #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # S3 methods #### #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1364,7 +1297,7 @@ fortify.liger <- function(model, data, ...) { c.liger <- function(...) { objList <- list(...) if (any(sapply(objList, function(obj) !inherits(obj, "liger")))) - stop("Can only combine `liger` objects with `c(...)` method for now.") + cli::cli_abort("Can only combine {.cls liger} objects with {.fn c} method for now.") objList[[length(objList)]] <- recordCommand(objList[[length(objList)]]) allDatasets <- list() allCellMeta <- NULL @@ -1381,3 +1314,105 @@ c.liger <- function(...) { varFeatures = varFeatures, commands = allCommands, version = utils::packageVersion("rliger2")) } + + +#' @export +#' @rdname liger-class +setMethod( + "commands", + signature(x = "liger", funcName = "ANY", arg = "ANY"), + function(x, funcName = NULL, arg = NULL) { + if (is.null(funcName)) return(names(x@commands)) + cmdIdx <- c() + for (n in funcName) { + pat <- paste0("^", n) + cmdIdx <- c(cmdIdx, grep(pat, names(x@commands))) + } + cmdIdx <- sort(unique(cmdIdx)) + result <- x@commands[cmdIdx] + + if (length(result) == 1) result <- result[[1]] + + if (!is.null(arg)) { + if (is.list(result)) + result <- lapply(result, function(cmd) cmd@parameters[arg]) + else result <- unlist(result@parameters[arg]) + } + return(result) + } +) + + +#' @rdname peak +#' @export +setMethod("rawPeak", signature(x = "liger", dataset = "character"), + function(x, dataset) { + atac <- dataset(x, dataset) + if (!inherits(atac, "ligerATACDataset")) { + cli::cli_abort("Specified dataset is not of {.cls ligerATACDataset} class.") + } + atac@rawPeak + }) + +#' @rdname peak +#' @export +setReplaceMethod( + "rawPeak", + signature(x = "liger", dataset = "character"), + function(x, dataset, check = TRUE, value) { + if (!inherits(dataset(x, dataset), "ligerATACDataset")) + cli::cli_abort("Specified dataset is not of {.cls ligerATACDataset} class.") + x@datasets[[dataset]]@rawPeak <- value + if (isTRUE(check)) methods::validObject(dataset(x, dataset)) + x + }) + +#' @rdname peak +#' @export +setMethod("normPeak", signature(x = "liger", dataset = "character"), + function(x, dataset) { + atac <- dataset(x, dataset) + if (!inherits(atac, "ligerATACDataset")) { + cli::cli_abort("Specified dataset is not of {.cls ligerATACDataset} class.") + } + atac@normPeak + }) + +#' @rdname peak +#' @export +setReplaceMethod( + "normPeak", + signature(x = "liger", dataset = "character"), + function(x, dataset, check = TRUE, value) { + if (!inherits(dataset(x, dataset), "ligerATACDataset")) + cli::cli_abort("Specified dataset is not of {.cls ligerATACDataset} class.") + x@datasets[[dataset]]@normPeak <- value + if (isTRUE(check)) methods::validObject(dataset(x, dataset)) + x + }) + + +#' @rdname coordinate +#' @export +setMethod("coordinate", signature(x = "liger", dataset = "character"), + function(x, dataset) { + spatial <- dataset(x, dataset) + if (!inherits(spatial, "ligerSpatialDataset")) { + cli::cli_abort("Specified dataset is not of {.cls ligerSpatialDataset} class.") + } + spatial@coordinate + }) + +#' @rdname coordinate +#' @export +setReplaceMethod( + "coordinate", + signature(x = "liger", dataset = "character"), + function(x, dataset, check = TRUE, value) { + if (!inherits(dataset(x, dataset), "ligerSpatialDataset")) + cli::cli_abort("Specified dataset is not of {.cls ligerSpatialDataset} class.") + value <- .checkCoords(ld = dataset(x, dataset), value = value) + x@datasets[[dataset]]@coordinate <- value + if (isTRUE(check)) methods::validObject(dataset(x, dataset)) + x + }) diff --git a/R/ligerCommand-class.R b/R/ligerCommand_relates.R similarity index 66% rename from R/ligerCommand-class.R rename to R/ligerCommand_relates.R index 8ec6e695..f79652b7 100644 --- a/R/ligerCommand-class.R +++ b/R/ligerCommand_relates.R @@ -1,50 +1,3 @@ -setClassUnion("POSIXct_or_NULL", c("POSIXct", "NULL")) - -#' ligerCommand object: Record the input and time of a LIGER function call -#' @slot funcName Name of the function -#' @slot time A time stamp object -#' @slot call A character string converted from system call -#' @slot parameters List of all arguments except the \linkS4class{liger} object. -#' Large object are summarized to short string. -#' @slot objSummary List of attributes of the \linkS4class{liger} object as a -#' snapshot when command is operated. -#' @slot ligerVersion Character string converted from -#' \code{packageVersion("rliger2")}. -#' @slot dependencyVersion Named character vector of version number, if any -#' dependency library has a chance to be included by the function. A -#' dependency might only be invoked under certain conditions, such as using -#' an alternative algorithm, which a call does not actually reach to, but it -#' would still be included for this call. -#' @exportClass ligerCommand -#' @export -#' @rdname ligerCommand-class -ligerCommand <- setClass( - Class = "ligerCommand", - representation( - funcName = "character", - time = "POSIXct_or_NULL", - call = "character", - parameters = "list", - objSummary = "list", - ligerVersion = "character", - dependencyVersion = "character" - ), - prototype( - funcName = character(), - time = NULL, - parameters = list(), - objSummary = list( - datasets = character(), - nCells = numeric(), - nFeatures = numeric(), - nVarFeatures = numeric(), - cellMetaNames = character(), - ligerVersion = character(), - dependencyVersion = character() - ) - ) -) - # Call in a way like: # object <- recordCommand(object, dependencies = ...) # Conditionally, should be placed after input checks @@ -56,8 +9,6 @@ recordCommand <- function( ..., dependencies = NULL ) { - #if (!inherits(object, "liger")) - # stop("Can only record commands for operation on a liger object") # Generate time stamp time <- Sys.time() # Capture the call @@ -192,12 +143,12 @@ setMethod( commandDiff <- function(object, cmd1, cmd2) { cmd1 <- commands(object, cmd1) if (!inherits(cmd1, "ligerCommand")) - stop("`cmd1` matching with multiple command records. ", - "Available options could be viewed with `commands(object)`.") + cli::cli_abort("{.code cmd1} matching with multiple command records. + Availble options could be viewed with {.code commands(object)}.") cmd2 <- commands(object, cmd2) if (!inherits(cmd2, "ligerCommand")) - stop("`cmd2` matching with multiple command records. ", - "Available options could be viewed with `commands(object)`.") + cli::cli_abort("{.code cmd2} matching with multiple command records. + Availble options could be viewed with {.code commands(object)}.") .cmdDiff(cmd1, cmd2) } @@ -255,49 +206,4 @@ commandDiff <- function(object, cmd1, cmd2) { return(msg) } -#' @section Command records: -#' rliger functions, that perform calculation and update the \code{liger} -#' object, will be recorded in a \code{ligerCommand} object and stored in the -#' \code{commands} slot, a list, of \code{liger} object. Method -#' \code{commands()} is implemented to retrieve or show the log history. -#' Running with \code{funcName = NULL} (default) returns all command labels. -#' Specifying \code{funcName} allows partial matching to all command labels -#' and returns a subset list (of \code{ligerCommand} object) of matches (or -#' the \code{ligerCommand} object if only one match found). If \code{arg} is -#' further specified, a subset list of parameters from the matches will be -#' returned. For example, requesting a list of resolution values used in -#' all louvain cluster attempts: \code{commands(ligerObj, "louvainCluster", -#' "resolution")} -#' @export -#' @rdname liger-class -setGeneric( - "commands", - function(x, funcName = NULL, arg = NULL) standardGeneric("commands") -) - -#' @export -#' @rdname liger-class -setMethod( - "commands", - signature(x = "liger", funcName = "ANY", arg = "ANY"), - function(x, funcName = NULL, arg = NULL) { - if (is.null(funcName)) return(names(x@commands)) - cmdIdx <- c() - for (n in funcName) { - pat <- paste0("^", n) - cmdIdx <- c(cmdIdx, grep(pat, names(x@commands))) - } - cmdIdx <- sort(unique(cmdIdx)) - result <- x@commands[cmdIdx] - - if (length(result) == 1) result <- result[[1]] - - if (!is.null(arg)) { - if (is.list(result)) - result <- lapply(result, function(cmd) cmd@parameters[arg]) - else result <- unlist(result@parameters[arg]) - } - return(result) - } -) diff --git a/R/ligerDataset-class.R b/R/ligerDataset-class.R deleted file mode 100644 index 89f942ed..00000000 --- a/R/ligerDataset-class.R +++ /dev/null @@ -1,1120 +0,0 @@ -setClassUnion("dgCMatrix_OR_NULL", c("dgCMatrix", "NULL")) -setClassUnion("matrix_OR_NULL", c("matrix", "NULL")) -setClassUnion("matrixLike", c("matrix", "dgCMatrix", "dgTMatrix", "dgeMatrix")) -setClassUnion("matrixLike_OR_NULL", c("matrixLike", "NULL")) -# It is quite hard to handle "H5D here, which is indeed defined as an R6 class. -# I'm not sure if this is a proper solution -setOldClass("H5D") -setOldClass("H5Group") -suppressWarnings(setClassUnion("dgCMatrix_OR_H5D_OR_NULL", c("dgCMatrix", "H5D", "NULL"))) -setClassUnion("matrix_OR_H5D_OR_NULL", c("matrix", "H5D", "NULL")) -setClassUnion("matrixLike_OR_H5D_OR_H5Group_OR_NULL", c("matrixLike", "H5D", "H5Group", "NULL")) -setClassUnion("index", - members = c("logical", "numeric", "character")) - -#' ligerDataset class -#' -#' Object for storing dastaset specific information. Will be embedded within a -#' higher level \linkS4class{liger} object -#' @docType class -#' @rdname ligerDataset-class -#' @slot rawData Raw data. -#' @slot normData Normalized data -#' @slot scaleData Scaled data, usually with subset variable features -#' @slot scaleUnsharedData Scaled data of features not shared with other -#' datasets -#' @slot varUnsharedFeatures Variable features not shared with other datasets -#' @slot V matrix -#' @slot A matrix -#' @slot B matrix -#' @slot H matrix -#' @slot U matrix -#' @slot h5fileInfo list -#' @slot featureMeta Feature metadata, DataFrame -#' @slot colnames character -#' @slot rownames character -#' @importClassesFrom S4Vectors DataFrame -#' @exportClass ligerDataset -ligerDataset <- setClass( - "ligerDataset", - representation( - rawData = "dgCMatrix_OR_H5D_OR_NULL", - normData = "dgCMatrix_OR_H5D_OR_NULL", - scaleData = "matrixLike_OR_H5D_OR_H5Group_OR_NULL", - scaleUnsharedData = "matrixLike_OR_H5D_OR_H5Group_OR_NULL", - varUnsharedFeatures = "character", - H = "matrix_OR_NULL", - V = "matrix_OR_NULL", - A = "matrix_OR_NULL", - B = "matrix_OR_NULL", - U = "matrix_OR_NULL", - h5fileInfo = "list", - featureMeta = "DataFrame", - colnames = "character", - rownames = "character" - ) -) - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Dataset creatinfg function #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#' Check if a liger or ligerDataset object is made of HDF5 file -#' @param object A liger or ligerDataset object. -#' @param dataset If \code{object} is of liger class, check a specific dataset. -#' If \code{NULL}, Check if all datasets are made of HDF5 file. Default -#' \code{NULL}. -#' @return \code{TRUE} or \code{FALSE} for the specified check. -#' @export -#' @examples -#' isH5Liger(pbmc) -#' isH5Liger(pbmc, "ctrl") -#' ctrl <- dataset(pbmc, "ctrl") -#' isH5Liger(ctrl) -isH5Liger <- function(object, dataset = NULL) { - if (inherits(object, "ligerDataset")) { - if (length(object@h5fileInfo) == 0) { - return(FALSE) - } else { - return(TRUE) - } - } else if (inherits(object, "liger")) { - dataset <- .checkUseDatasets(object, dataset) - allCheck <- unlist(lapply(datasets(object)[dataset], isH5Liger)) - return(all(allCheck)) - } else { - warning("Given object is not of liger or ligerDataset class.") - return(FALSE) - } -} - -#' Return preset modality of a ligerDataset object or that of all datasets in a -#' liger object -#' @param object a \linkS4class{ligerDataset} object or a \linkS4class{liger} -#' object -#' @return A single character of modality setting value for -#' \linkS4class{ligerDataset} \code{object}, or a named vector for -#' \linkS4class{liger} object, where the names are dataset names. -#' @export -#' @examples -#' modalOf(pbmc) -#' ctrl <- dataset(pbmc, "ctrl") -#' modalOf(ctrl) -#' ctrl.atac <- as.ligerDataset(ctrl, modal = "atac") -#' modalOf(ctrl.atac) -modalOf <- function(object) { - if (inherits(object, "ligerDataset")) { - if (class(object) %in% names(.classModalDict)) - return(.classModalDict[[class(object)]]) - else { - warning("DEVELOPERS, please add this ligerDataset sub-class to ", - "`.classModalDict`", immediate. = TRUE) - return("UNKNOWN") - } - } else if (inherits(object, "liger")) { - return(sapply(datasets(object), modalOf)) - } -} - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Object validity #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -.checkLigerDatasetBarcodes <- function(x) { - # cell barcodes all consistant - if (is.null(colnames(x))) { - return(paste0("No valid cell barcode detected for ligerDataset.\n", - "Please create object with matrices with colnames.")) - } - for (slot in c("rawData", "normData", "scaleData", "scaleUnsharedData", - "H")) { - if (!slot %in% methods::slotNames(x)) next - data <- methods::slot(x, slot) - if (!is.null(data)) { - barcodes.slot <- colnames(data) - if (!identical(colnames(x), barcodes.slot)) { - return(paste0("Inconsistant cell identifiers in `", slot, - "` slot.")) - } - } - } - - for (slot in c("scaleData", "V")) { - featuresToCheck <- rownames(methods::slot(x, slot)) - check <- !featuresToCheck %in% rownames(x) - if (any(check)) { - msg <- paste0("Features in ", slot, " not found from dataset: ", - paste(featuresToCheck[check], collapse = ", ")) - return(msg) - } - } - TRUE -} - -.checkH5LigerDatasetLink <- function(x) { - restoreGuide <- "Please try running `restoreH5Liger(object)`." - if (!"H5File" %in% names(h5fileInfo(x))) { - return(paste("`h5fileInfo` incomplete.", restoreGuide)) - } - h5file <- getH5File(x) - if (is.null(h5file)) { - return(paste("`H5File` is NULL in `h5fileInfo` slot.", restoreGuide)) - } - if (!h5file$is_valid) { - return(paste("`H5File` is invalid in `h5fileInfo` slot.", restoreGuide)) - } - if (!is.null(rawData(x))) { - if (!rawData(x)$is_valid) { - return(paste("`rawData` slot is invalid.", restoreGuide)) - } - } - if (!is.null(normData(x))) { - if (!normData(x)$is_valid) { - return(paste("`normData` slot is invalid.", restoreGuide)) - } - } - if (!is.null(scaleData(x))) { - if (!scaleData(x)$is_valid) { - return(paste("`scaleData` slot is invalid.", restoreGuide)) - } - } - TRUE -} - -.valid.ligerDataset <- function(object) { - if (isH5Liger(object)) { - # message("Checking h5 ligerDataset validity") - .checkH5LigerDatasetLink(object) - } else { - # message("Checking in memory ligerDataset validity") - .checkLigerDatasetBarcodes(object) - } - # TODO more checks - # TODO debating on whether to have check of the matching between scaleData - # features and selected variable features. -} - -setValidity("ligerDataset", .valid.ligerDataset) - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# S4 Methods #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#' @param x,object A \code{ligerDataset} object. -#' @param dataset Not applicable for \code{ligerDataset} methods. -#' @param value See detail sections for requirements -#' @param check Whether to perform object validity check on setting new value. -#' @param info Name of the entry in \code{h5fileInfo} slot. -#' @param slot The slot name when using \code{getMatrix}. -#' @param returnList Not applicable for \code{ligerDataset} methods. -#' @param ... See detailed sections for explanation. -#' @rdname ligerDataset-class -#' @export -#' @examples -#' ctrl <- dataset(pbmc, "ctrl") -#' -#' # Methods for base generics -#' ctrl -#' print(ctrl) -#' dim(ctrl) -#' ncol(ctrl) -#' nrow(ctrl) -#' colnames(ctrl)[1:5] -#' rownames(ctrl)[1:5] -#' ctrl[1:5, 1:5] -#' -#' # rliger generics -#' ## raw data -#' m <- rawData(ctrl) -#' class(m) -#' dim(m) -#' ## normalized data -#' pbmc <- normalize(pbmc) -#' ctrl <- dataset(pbmc, "ctrl") -#' m <- normData(ctrl) -#' class(m) -#' dim(m) -#' ## scaled data -#' pbmc <- selectGenes(pbmc) -#' pbmc <- scaleNotCenter(pbmc) -#' ctrl <- dataset(pbmc, "ctrl") -#' m <- scaleData(ctrl) -#' class(m) -#' dim(m) -#' n <- scaleData(pbmc, "ctrl") -#' identical(m, n) -#' ## Any other matrices -#' if (requireNamespace("RcppPlanc", quietly = TRUE)) { -#' pbmc <- runOnlineINMF(pbmc, k = 20, minibatchSize = 100) -#' ctrl <- dataset(pbmc, "ctrl") -#' V <- getMatrix(ctrl, "V") -#' V[1:5, 1:5] -#' Vs <- getMatrix(pbmc, "V") -#' length(Vs) -#' names(Vs) -#' identical(Vs$ctrl, V) -#' } -setMethod( - f = "show", - signature(object = "ligerDataset"), - definition = function(object) { - # Use class(object) so that the inheriting classes can be shown properly - cat("An object of class", class(object), "with", - ncol(object), "cells\n") - if (isH5Liger(object) & - !isTRUE(methods::validObject(object, test = TRUE))) { - warning("Link to HDF5 file fails. Please try running ", - "`restoreH5Liger(object)`.") - return() - } - for (slot in c("rawData", "normData", "scaleData", - "scaleUnsharedData")) { - data <- methods::slot(object, slot) - if (!is.null(data)) { - if (inherits(data, c("matrix", "dgCMatrix", - "dgTMatrix", "dgeMatrix"))) { - cat(paste0(slot, ":"), nrow(data), "features\n") - } - if (inherits(data, "H5D")) { - if (length(data$dims) == 1) { - cat(paste0(slot, ":"), data$dims, - "non-zero values in H5D object\n") - } else { - cat(paste0(slot, ":"), - paste(data$dims, collapse = " x "), - "values in H5D object\n") - } - } - if (inherits(data, "H5Group")) { - cat(paste0(slot, ":"), data[["data"]]$dims, - "non-zero values in H5Group object\n") - } - } - } - # Information for sub-classes added below, in condition statements - if ("rawPeak" %in% methods::slotNames(object)) { - if (!is.null(rawPeak(object))) - cat("rawPeak:", nrow(rawPeak(object)), "regions\n") - if (!is.null(normPeak(object))) - cat("normPeak:", nrow(normPeak(object)), "regions\n") - } - - invisible(x = NULL) - } -) - -#' @section Dimensionality: -#' For a \code{ligerDataset} object, the column orientation is assigned for -#' cells and rows are for features. Therefore, for \code{ligerDataset} objects, -#' \code{dim()} returns a numeric vector of two numbers which are number of -#' features and number of cells. \code{dimnames()} returns a list of two -#' character vectors, which are the feature names and the cell barcodes. -#' -#' For direct call of \code{dimnames<-} method, \code{value} should be a list -#' with a character vector of feature names as the first element and cell -#' identifiers as the second element. For \code{colnames<-} method, the -#' character vector of cell identifiers. For \code{rownames<-} method, the -#' character vector of feature names. -#' @section Subsetting: -#' For more detail of subsetting a \code{liger} object or a -#' \linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} -#' and \code{\link{subsetLigerDataset}}. Here, we set the S3 method -#' "single-bracket" \code{[} as a quick wrapper to subset a \code{ligerDataset} -#' object. \code{i} and \code{j} serves as feature and cell subscriptor, -#' respectively, which can be any valid index refering the available features -#' and cells in a dataset. \code{...} arugments are passed to -#' \code{subsetLigerDataset} so that advanced options are allowed. -#' @rdname ligerDataset-class -#' @export -setMethod("dim", "ligerDataset", function(x) { - nr <- length(x@rownames) - nc <- length(x@colnames) - c(nr, nc) -}) - -#' @rdname ligerDataset-class -#' @export -setMethod("dimnames", "ligerDataset", function(x) { - rn <- x@rownames - cn <- x@colnames - list(rn, cn) -}) - -#' @rdname ligerDataset-class -#' @export -setReplaceMethod("dimnames", c("ligerDataset", "list"), function(x, value) { - if (!isH5Liger(x)) { - if (!is.null(rawData(x))) { - rownames(x@rawData) <- value[[1L]] - colnames(x@rawData) <- value[[2L]] - } - if (!is.null(normData(x))) { - rownames(x@normData) <- value[[1L]] - colnames(x@normData) <- value[[2L]] - } - if (!is.null(scaleData(x))) { - colnames(x@scaleData) <- value[[2L]] - rownames(x@scaleData) <- - value[[1L]][.getVarFtIdx(x@rownames, rownames(x@scaleData))] - } - if (!is.null(x@scaleUnsharedData)) { - colnames(x@scaleUnsharedData) <- value[[2L]] - rownames(x@scaleUnsharedData) <- - value[[1L]][.getVarFtIdx(x@rownames, - rownames(x@scaleUnsharedData))] - } - } - if (!is.null(x@H)) - colnames(x@H) <- value[[2L]] - if (!is.null(x@V)) - rownames(x@V) <- value[[1L]][.getVarFtIdx(x@rownames, rownames(x@V))] - if (!is.null(x@B)) - rownames(x@B) <- value[[1L]][.getVarFtIdx(x@rownames, rownames(x@B))] - if (!is.null(x@U)) colnames(x@U) <- value[[2L]] - if ("rawPeak" %in% methods::slotNames(x)) { - if (!is.null(rawPeak(x))) colnames(rawPeak(x)) <- value[[2L]] - } - if ("normPeak" %in% methods::slotNames(x)) { - if (!is.null(normPeak(x))) colnames(normPeak(x)) <- value[[2L]] - } - if ("coordinate" %in% methods::slotNames(x)) { - if (!is.null(coordinate(x))) rownames(coordinate(x)) <- value[[2L]] - } - x@rownames <- value[[1L]] - x@colnames <- value[[2L]] - return(x) -}) - -.getVarFtIdx <- function(full, var) { - # full - character vector of ligerDataset rownames - # var - character vector of var features (in scaleData, V), might not follow - # the original order of `full` - # return numeric value that select ordered corresponding replacement from - # `dimnames<-`'s value[[1]] - fullNamedIdx <- seq_along(full) - names(fullNamedIdx) <- full - varNumIdx <- fullNamedIdx[var] - names(varNumIdx) <- NULL - return(varNumIdx) -} - -#' Subset ligerDataset object -#' @name sub-ligerDataset -#' @param x A \linkS4class{ligerDataset} object -#' @param i Numeric, logical index or character vector of feature names to -#' subscribe. Leave missing for all features. -#' @param j Numeric, logical index or character vector of cell IDs to subscribe. -#' Leave missing for all cells. -#' @param ... Additional arguments passed to \code{\link{subsetLigerDataset}}. -#' @export -#' @method [ ligerDataset -#' @return If \code{i} is given, the selected metadata will be returned; if it -#' is missing, the whole cell metadata table in -#' \code{S4Vectors::\link[S4Vectors]{DataFrame}} class will be returned. -#' @examples -#' ctrl <- dataset(pbmc, "ctrl") -#' ctrl[1:5, 1:5] -`[.ligerDataset` <- function(x, i, j, ...) { - if (missing(i) && missing(j)) { - return(x) - } - if (missing(i)) i <- NULL - if (missing(j)) j <- NULL - subsetLigerDataset(x, featureIdx = i, cellIdx = j, ...) -} - - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Raw, norm, scale data access #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#' @section Matrix access: -#' For \code{ligerDataset} object, \code{rawData()}, \code{normData}, -#' \code{scaleData()} and \code{scaleUnsharedData()} methods are exported for -#' users to access the corresponding feature expression matrix. Replacement -#' methods are also available to modify the slots. -#' -#' For other matrices, such as the \eqn{H} and \eqn{V}, which are dataset -#' specific, please use \code{getMatrix()} method with specifying slot name. -#' Directly accessing slot with \code{@} is generally not recommended. -#' @export -#' @rdname ligerDataset-class -setGeneric("rawData", function(x, dataset = NULL) standardGeneric("rawData")) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "rawData<-", - function(x, dataset = NULL, check = TRUE, value) standardGeneric("rawData<-") -) - -#' @export -#' @rdname ligerDataset-class -setMethod("rawData", "ligerDataset", - function(x, dataset = NULL) x@rawData) - -#' @export -#' @rdname liger-class -setMethod("rawData", c("liger", "ANY"), - function(x, dataset = NULL) { - if (is.null(dataset)) { - getMatrix(x, slot = "rawData", returnList = TRUE) - } else { - getMatrix(x, "rawData", dataset = dataset) - } - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "rawData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - if (isH5Liger(x)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@rawData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "rawData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - if (!isH5Liger(x)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@rawData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "rawData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (isH5Liger(x, dataset)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@datasets[[dataset]]@rawData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "rawData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (!isH5Liger(x, dataset)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@datasets[[dataset]]@rawData <- value - if (isTRUE(check)) methods::validObject(x@datasets[[dataset]]) - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setGeneric("normData", function(x, dataset = NULL) standardGeneric("normData")) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "normData<-", - function(x, dataset = NULL, check = TRUE, value) standardGeneric("normData<-") -) - -#' @export -#' @rdname ligerDataset-class -setMethod("normData", "ligerDataset", - function(x, dataset = NULL) x@normData) - -#' @export -#' @rdname liger-class -setMethod("normData", c("liger", "ANY"), - function(x, dataset = NULL) { - if (is.null(dataset)) { - getMatrix(x, slot = "normData", returnList = TRUE) - } else { - getMatrix(x, "normData", dataset = dataset) - } - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "normData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - if (isH5Liger(x)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@normData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "normData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - if (!isH5Liger(x)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@normData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "normData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (isH5Liger(x, dataset)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@datasets[[dataset]]@normData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "normData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (!isH5Liger(x, dataset)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@datasets[[dataset]]@normData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "scaleData", - function(x, dataset = NULL) standardGeneric("scaleData") -) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "scaleData<-", - function(x, dataset = NULL, check = TRUE, value) standardGeneric("scaleData<-") -) - -#' @export -#' @rdname ligerDataset-class -setMethod("scaleData", c("ligerDataset", "missing"), - function(x, dataset = NULL) x@scaleData) - -#' @export -#' @rdname liger-class -setMethod( - "scaleData", - signature(x = "liger", dataset = "ANY"), - function(x, dataset = NULL) { - if (is.null(dataset)) { - getMatrix(x, slot = "scaleData", returnList = TRUE) - } else { - getMatrix(x, "scaleData", dataset = dataset) - } - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "scaleData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - if (isH5Liger(x)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@scaleData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "scaleData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - if (!isH5Liger(x)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@scaleData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "scaleData", - signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5Group"), - function(x, dataset = NULL, check = TRUE, value) { - if (!isH5Liger(x)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@scaleData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "scaleData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (isH5Liger(x, dataset)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@datasets[[dataset]]@scaleData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "scaleData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (!isH5Liger(x, dataset)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@datasets[[dataset]]@scaleData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "scaleData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5Group"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (!isH5Liger(x, dataset)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@datasets[[dataset]]@scaleData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "scaleUnsharedData", - function(x, dataset = NULL) standardGeneric("scaleUnsharedData") -) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "scaleUnsharedData<-", - function(x, dataset = NULL, check = TRUE, value) standardGeneric("scaleUnsharedData<-") -) - -#' @export -#' @rdname ligerDataset-class -setMethod("scaleUnsharedData", c("ligerDataset", "missing"), - function(x, dataset = NULL) x@scaleUnsharedData) - -#' @export -#' @rdname liger-class -setMethod( - "scaleUnsharedData", - signature(x = "liger", dataset = "character"), - function(x, dataset) { - scaleUnsharedData(dataset(x, dataset)) - } -) - -#' @export -#' @rdname liger-class -setMethod( - "scaleUnsharedData", - signature(x = "liger", dataset = "numeric"), - function(x, dataset) { - scaleUnsharedData(dataset(x, dataset)) - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "scaleUnsharedData", - signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, check = TRUE, value) { - if (isH5Liger(x)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@scaleUnsharedData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "scaleUnsharedData", - signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "H5D"), - function(x, check = TRUE, value) { - if (!isH5Liger(x)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@scaleUnsharedData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "scaleUnsharedData", - signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "H5Group"), - function(x, check = TRUE, value) { - if (!isH5Liger(x)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@scaleUnsharedData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "scaleUnsharedData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (isH5Liger(x, dataset)) - stop("Cannot replace slot with in-memory data for H5 based object.") - x@datasets[[dataset]]@scaleUnsharedData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "scaleUnsharedData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5D"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (!isH5Liger(x, dataset)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@datasets[[dataset]]@scaleUnsharedData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname liger-class -setReplaceMethod( - "scaleUnsharedData", - signature(x = "liger", dataset = "ANY", check = "ANY", value = "H5Group"), - function(x, dataset = NULL, check = TRUE, value) { - dataset <- .checkUseDatasets(x, dataset) - if (length(dataset) != 1) stop("Need to specify one dataset to insert.") - if (!isH5Liger(x, dataset)) - stop("Cannot replace slot with on-disk data for in-memory object.") - x@datasets[[dataset]]@scaleUnsharedData <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - - - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "getMatrix", - function(x, slot = "rawData", dataset = NULL, returnList = FALSE) { - standardGeneric("getMatrix") - } -) - -#' @export -#' @rdname ligerDataset-class -setMethod( - "getMatrix", signature(x = "ligerDataset", dataset = "missing", - returnList = "missing"), - function(x, - slot = c("rawData", "normData", "scaleData", - "scaleUnsharedData", "H", "V", "U", "A", "B"), - dataset = NULL) { - # TODO: Currently directly find the data with slot, but need to - # think about maintainability when we need to change slot name. - slot <- match.arg(slot) - methods::slot(x, slot) - }) - -#' @export -#' @rdname liger-class -setMethod( - "getMatrix", signature(x = "liger"), - function(x, - slot = c("rawData", "normData", "scaleData", - "scaleUnsharedData", "H", "V", "U", "A", "B", - "W", "H.norm"), - dataset = NULL, - returnList = FALSE) { - slot <- match.arg(slot) - if (slot == "W") return(x@W) - if (slot == "H.norm") return(x@H.norm) - if (is.null(dataset)) { - return(lapply(datasets(x), function(ld) getMatrix(ld, slot))) - } else { - if (length(dataset) == 1) { - if (isTRUE(returnList)) { - result <- list(getMatrix(dataset(x, dataset), slot)) - names(result) <- dataset - return(result) - } else if (isFALSE(returnList)) - return(getMatrix(dataset(x, dataset), slot)) - } else { - lds <- datasets(x)[dataset] - return(lapply(lds, function(ld) getMatrix(ld, slot))) - } - } - }) - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# H5 related #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#' @section H5 file and information access: -#' A \code{ligerDataset} object has a slot called \code{h5fileInfo}, which is a -#' list object. The first element is called \code{$H5File}, which is an -#' \code{H5File} class object and is the connection to the input file. The -#' second element is \code{$filename} which stores the absolute path of the H5 -#' file in the current machine. The third element \code{$formatType} stores the -#' name of preset being used, if applicable. The other following keys pair with -#' paths in the H5 file that point to specific data for constructing a feature -#' expression matrix. -#' -#' \code{h5fileInfo()} method access the list described above and simply -#' retrieves the corresponding value. When \code{info = NULL}, returns the whole -#' list. When \code{length(info) == 1}, returns the requested list value. When -#' more info requested, returns a subset list. -#' -#' The replacement method modifies the list elements and corresponding slot -#' value (if applicable) at the same time. For example, running -#' \code{h5fileInfo(obj, "rawData") <- newPath} not only updates the list, but -#' also updates the \code{rawData} slot with the \code{H5D} class data at -#' "newPath" in the \code{H5File} object. -#' -#' \code{getH5File()} is a wrapper and is equivalent to -#' \code{h5fileInfo(obj, "H5File")}. -#' @export -#' @rdname ligerDataset-class -setGeneric("h5fileInfo", function(x, info = NULL) standardGeneric("h5fileInfo")) - -#' @export -#' @rdname ligerDataset-class -setGeneric( - "h5fileInfo<-", - function(x, info = NULL, check = TRUE, value) { - standardGeneric("h5fileInfo<-") - } -) - -#' @export -#' @rdname ligerDataset-class -setMethod( - "h5fileInfo", - signature = signature(x = "ligerDataset", info = "ANY"), - function(x, info = NULL) { - if (is.null(info)) result <- x@h5fileInfo - else { - if (length(info) == 1) result <- x@h5fileInfo[[info]] - else { - if (any(!info %in% names(x@h5fileInfo))) { - stop("Specified h5file info not found: ", - paste(info[!info %in% names(x@h5fileInfo)], - collapse = ", ")) - } - result <- x@h5fileInfo[info] - names(result) <- info - } - } - return(result) - }) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "h5fileInfo", - signature = signature( - x = "ligerDataset", - info = "ANY", - check = "ANY", - value = "ANY" - ), - function(x, info = NULL, check = TRUE, value) { - if (is.null(info)) { - x@h5fileInfo <- value - } else { - if (!is.character(info) | length(info) != 1) - stop("`info` has to be a single character.") - if (info %in% c("indicesName", "indptrName", "barcodesName", - "genesName", "rawData", "normData", - "scaleData")) { - if (!getH5File(x)$exists(value)) { - stop("Specified info is invalid, '", info, - "' does not exists in the HDF5 file.") - } - } - x@h5fileInfo[[info]] <- value - if (info %in% c("rawData", "normData", "scaleData", - "scaleUnsharedData")) { - x <- do.call(paste0(info, "<-"), - list(x = x, - value = getH5File(x)[[h5fileInfo(x, info)]], - check = check)) - } - } - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#' @export -#' @rdname ligerDataset-class -setGeneric("getH5File", function(x, dataset = NULL) standardGeneric("getH5File")) - -#' @export -#' @rdname ligerDataset-class -setMethod("getH5File", - signature = signature(x = "ligerDataset", dataset = "missing"), - function(x, dataset = NULL) h5fileInfo(x, "H5File")) - -#' @export -#' @rdname liger-class -setMethod("getH5File", - signature = signature(x = "liger", dataset = "ANY"), - function(x, dataset = NULL) { - if (is.null(dataset)) dataset <- names(x) - dataset <- .checkUseDatasets(x, dataset) - results <- lapply(datasets(x)[dataset], - function(ld) h5fileInfo(ld, "H5File")) - if (length(results) == 1) results <- results[[1]] - results - }) - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Feature metadata #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#' @section Feature metadata access: -#' A slot \code{featureMeta} is included for each \code{ligerDataset} object. -#' This slot requires a \code{\link[S4Vectors]{DataFrame-class}} object, which -#' is the same as \code{cellMeta} slot of a \linkS4class{liger} object. However, -#' the associated S4 methods only include access to the whole table for now. -#' Internal information access follows the same way as data.frame operation. -#' For example, \code{featureMeta(ligerD)$nCell} or -#' \code{featureMeta(ligerD)[varFeatures(ligerObj), "gene_var"]}. -#' @export -#' @rdname ligerDataset-class -setGeneric("featureMeta", function(x, check = NULL) { - standardGeneric("featureMeta") -}) - -#' @export -#' @rdname ligerDataset-class -setGeneric("featureMeta<-", function(x, check = TRUE, value) { - standardGeneric("featureMeta<-") -}) - -#' @export -#' @rdname ligerDataset-class -setMethod("featureMeta", signature(x = "ligerDataset", check = "ANY"), - function(x, check = NULL) { - x@featureMeta -}) - -#' @export -#' @rdname ligerDataset-class -setReplaceMethod( - "featureMeta", - signature(x = "ligerDataset", check = "ANY"), - function(x, check = TRUE, value) { - if (!inherits(value, "DFrame")) - value <- S4Vectors::DataFrame(value) - x@featureMeta <- value - if (isTRUE(check)) methods::validObject(x) - x - } -) - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# S3 Method #### -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#' @section Concatenate ligerDataset: -#' \code{cbind()} method is implemented for concatenating \code{ligerDataset} -#' objects by cells. When applying, all feature expression matrix will be merged -#' with taking a union of all features for the rows. -#' @param deparse.level Not used here. -#' @rdname ligerDataset-class -#' @export -#' @method cbind ligerDataset -cbind.ligerDataset <- function(x, ..., - deparse.level = 1) { - args <- list(...) - isLD <- sapply(args, function(x) inherits(x, "ligerDataset")) - if (any(!isLD)) { - warning("Discarding arguments that are not of ligerDataset class") - args <- args[isLD] - } - if (!missing(x)) args <- c(list(x), args) - isH5 <- sapply(args, isH5Liger) - # See mergeObject.R - if (all(isH5)) .cbind.ligerDataset.h5(args) - else if (!any(isH5)) .cbind.ligerDataset.mem(args) - else - stop("Cannot `cbind` a hybrid of H5 ligerDatasets and ", - "in-memory ligerDatasets for now.") -} diff --git a/R/ligerDataset-methods.R b/R/ligerDataset-methods.R new file mode 100644 index 00000000..7ee9d8c2 --- /dev/null +++ b/R/ligerDataset-methods.R @@ -0,0 +1,598 @@ + + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# Dataset creatinfg function #### +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#' Check if a liger or ligerDataset object is made of HDF5 file +#' @param object A liger or ligerDataset object. +#' @param dataset If \code{object} is of liger class, check a specific dataset. +#' If \code{NULL}, Check if all datasets are made of HDF5 file. Default +#' \code{NULL}. +#' @return \code{TRUE} or \code{FALSE} for the specified check. +#' @export +#' @examples +#' isH5Liger(pbmc) +#' isH5Liger(pbmc, "ctrl") +#' ctrl <- dataset(pbmc, "ctrl") +#' isH5Liger(ctrl) +isH5Liger <- function(object, dataset = NULL) { + if (inherits(object, "ligerDataset")) { + if (length(object@h5fileInfo) == 0) { + return(FALSE) + } else { + return(TRUE) + } + } else if (inherits(object, "liger")) { + dataset <- .checkUseDatasets(object, dataset) + if (length(dataset) == 0) return(FALSE) + allCheck <- unlist(lapply(datasets(object)[dataset], isH5Liger)) + return(all(allCheck)) + } else { + cli::cli_alert_danger("Given object is not of {.cls liger} or {.cls ligerDataset} class.") + return(FALSE) + } +} + +#' Return preset modality of a ligerDataset object or that of all datasets in a +#' liger object +#' @param object a \linkS4class{ligerDataset} object or a \linkS4class{liger} +#' object +#' @return A single character of modality setting value for +#' \linkS4class{ligerDataset} \code{object}, or a named vector for +#' \linkS4class{liger} object, where the names are dataset names. +#' @export +#' @examples +#' modalOf(pbmc) +#' ctrl <- dataset(pbmc, "ctrl") +#' modalOf(ctrl) +#' ctrl.atac <- as.ligerDataset(ctrl, modal = "atac") +#' modalOf(ctrl.atac) +modalOf <- function(object) { + if (inherits(object, "ligerDataset")) { + if (class(object) %in% names(.classModalDict)) + return(.classModalDict[[class(object)]]) + else { + warning("DEVELOPERS, please add this ligerDataset sub-class to ", + "`.classModalDict`", immediate. = TRUE) + return("UNKNOWN") + } + } else if (inherits(object, "liger")) { + return(sapply(datasets(object), modalOf)) + } +} + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# S4 Methods #### +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#' @param x,object A \code{ligerDataset} object. +#' @param dataset Not applicable for \code{ligerDataset} methods. +#' @param value See detail sections for requirements +#' @param check Whether to perform object validity check on setting new value. +#' @param info Name of the entry in \code{h5fileInfo} slot. +#' @param slot The slot name when using \code{getMatrix}. +#' @param returnList Not applicable for \code{ligerDataset} methods. +#' @param ... See detailed sections for explanation. +#' @rdname ligerDataset-class +#' @export +#' @examples +#' ctrl <- dataset(pbmc, "ctrl") +#' +#' # Methods for base generics +#' ctrl +#' print(ctrl) +#' dim(ctrl) +#' ncol(ctrl) +#' nrow(ctrl) +#' colnames(ctrl)[1:5] +#' rownames(ctrl)[1:5] +#' ctrl[1:5, 1:5] +#' +#' # rliger generics +#' ## raw data +#' m <- rawData(ctrl) +#' class(m) +#' dim(m) +#' ## normalized data +#' pbmc <- normalize(pbmc) +#' ctrl <- dataset(pbmc, "ctrl") +#' m <- normData(ctrl) +#' class(m) +#' dim(m) +#' ## scaled data +#' pbmc <- selectGenes(pbmc) +#' pbmc <- scaleNotCenter(pbmc) +#' ctrl <- dataset(pbmc, "ctrl") +#' m <- scaleData(ctrl) +#' class(m) +#' dim(m) +#' n <- scaleData(pbmc, "ctrl") +#' identical(m, n) +#' ## Any other matrices +#' if (requireNamespace("RcppPlanc", quietly = TRUE)) { +#' pbmc <- runOnlineINMF(pbmc, k = 20, minibatchSize = 100) +#' ctrl <- dataset(pbmc, "ctrl") +#' V <- getMatrix(ctrl, "V") +#' V[1:5, 1:5] +#' Vs <- getMatrix(pbmc, "V") +#' length(Vs) +#' names(Vs) +#' identical(Vs$ctrl, V) +#' } +setMethod( + f = "show", + signature(object = "ligerDataset"), + definition = function(object) { + # Use class(object) so that the inheriting classes can be shown properly + cat("An object of class", class(object), "with", + ncol(object), "cells\n") + if (isH5Liger(object) & + !isTRUE(methods::validObject(object, test = TRUE))) { + cli::cli_alert_danger("Link to HDF5 file fails. Please try running {.code restoreH5Liger(object)}.") + return() + } + for (slot in c("rawData", "normData", "scaleData", + "scaleUnsharedData")) { + data <- methods::slot(object, slot) + if (!is.null(data)) { + if (inherits(data, c("matrix", "dgCMatrix", + "dgTMatrix", "dgeMatrix"))) { + cat(paste0(slot, ":"), nrow(data), "features\n") + } + if (inherits(data, "H5D")) { + if (length(data$dims) == 1) { + cat(paste0(slot, ":"), data$dims, + "non-zero values in H5D object\n") + } else { + cat(paste0(slot, ":"), + paste(data$dims, collapse = " x "), + "values in H5D object\n") + } + } + if (inherits(data, "H5Group")) { + cat(paste0(slot, ":"), data[["data"]]$dims, + "non-zero values in H5Group object\n") + } + } + } + # Information for sub-classes added below, in condition statements + if ("rawPeak" %in% methods::slotNames(object)) { + if (!is.null(rawPeak(object))) + cat("rawPeak:", nrow(rawPeak(object)), "regions\n") + if (!is.null(normPeak(object))) + cat("normPeak:", nrow(normPeak(object)), "regions\n") + } + + invisible(x = NULL) + } +) + +#' @section Dimensionality: +#' For a \code{ligerDataset} object, the column orientation is assigned for +#' cells and rows are for features. Therefore, for \code{ligerDataset} objects, +#' \code{dim()} returns a numeric vector of two numbers which are number of +#' features and number of cells. \code{dimnames()} returns a list of two +#' character vectors, which are the feature names and the cell barcodes. +#' +#' For direct call of \code{dimnames<-} method, \code{value} should be a list +#' with a character vector of feature names as the first element and cell +#' identifiers as the second element. For \code{colnames<-} method, the +#' character vector of cell identifiers. For \code{rownames<-} method, the +#' character vector of feature names. +#' @section Subsetting: +#' For more detail of subsetting a \code{liger} object or a +#' \linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} +#' and \code{\link{subsetLigerDataset}}. Here, we set the S3 method +#' "single-bracket" \code{[} as a quick wrapper to subset a \code{ligerDataset} +#' object. \code{i} and \code{j} serves as feature and cell subscriptor, +#' respectively, which can be any valid index refering the available features +#' and cells in a dataset. \code{...} arugments are passed to +#' \code{subsetLigerDataset} so that advanced options are allowed. +#' @rdname ligerDataset-class +#' @export +setMethod("dim", "ligerDataset", function(x) { + nr <- length(x@rownames) + nc <- length(x@colnames) + c(nr, nc) +}) + +#' @rdname ligerDataset-class +#' @export +setMethod("dimnames", "ligerDataset", function(x) { + rn <- x@rownames + cn <- x@colnames + list(rn, cn) +}) + +#' @rdname ligerDataset-class +#' @export +setReplaceMethod("dimnames", c("ligerDataset", "list"), function(x, value) { + if (!isH5Liger(x)) { + if (!is.null(rawData(x))) { + rownames(x@rawData) <- value[[1L]] + colnames(x@rawData) <- value[[2L]] + } + if (!is.null(normData(x))) { + rownames(x@normData) <- value[[1L]] + colnames(x@normData) <- value[[2L]] + } + if (!is.null(scaleData(x))) { + colnames(x@scaleData) <- value[[2L]] + rownames(x@scaleData) <- + value[[1L]][match(rownames(x@scaleData), x@rownames)] + } + if (!is.null(x@scaleUnsharedData)) { + colnames(x@scaleUnsharedData) <- value[[2L]] + rownames(x@scaleUnsharedData) <- + value[[1L]][match(rownames(x@scaleUnsharedData), x@rownames)] + } + } + if (!is.null(x@H)) + colnames(x@H) <- value[[2L]] + if (!is.null(x@V)) + rownames(x@V) <- value[[1L]][match(rownames(x@V), x@rownames)] + if (!is.null(x@B)) + rownames(x@B) <- value[[1L]][match(rownames(x@B), x@rownames)] + if (!is.null(x@U)) colnames(x@U) <- value[[2L]] + if ("rawPeak" %in% methods::slotNames(x)) { + if (!is.null(rawPeak(x))) colnames(rawPeak(x)) <- value[[2L]] + } + if ("normPeak" %in% methods::slotNames(x)) { + if (!is.null(normPeak(x))) colnames(normPeak(x)) <- value[[2L]] + } + if ("coordinate" %in% methods::slotNames(x)) { + if (!is.null(coordinate(x))) rownames(coordinate(x)) <- value[[2L]] + } + x@rownames <- value[[1L]] + x@colnames <- value[[2L]] + return(x) +}) + + +#' Subset ligerDataset object +#' @name sub-ligerDataset +#' @param x A \linkS4class{ligerDataset} object +#' @param i Numeric, logical index or character vector of feature names to +#' subscribe. Leave missing for all features. +#' @param j Numeric, logical index or character vector of cell IDs to subscribe. +#' Leave missing for all cells. +#' @param ... Additional arguments passed to \code{\link{subsetLigerDataset}}. +#' @export +#' @method [ ligerDataset +#' @return If \code{i} is given, the selected metadata will be returned; if it +#' is missing, the whole cell metadata table in +#' \code{S4Vectors::\link[S4Vectors]{DataFrame}} class will be returned. +#' @examples +#' ctrl <- dataset(pbmc, "ctrl") +#' ctrl[1:5, 1:5] +`[.ligerDataset` <- function(x, i, j, ...) { + if (missing(i) && missing(j)) { + return(x) + } + if (missing(i)) i <- NULL + if (missing(j)) j <- NULL + subsetLigerDataset(x, featureIdx = i, cellIdx = j, ...) +} + + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# Raw, norm, scale data access #### +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + +#' @export +#' @rdname ligerDataset-class +setMethod("rawData", "ligerDataset", + function(x, dataset = NULL) x@rawData) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "rawData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + if (isH5Liger(x)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@rawData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "rawData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + if (!isH5Liger(x)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@rawData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + + +#' @export +#' @rdname ligerDataset-class +setMethod("normData", "ligerDataset", + function(x, dataset = NULL) x@normData) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "normData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + if (isH5Liger(x)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@normData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "normData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + if (!isH5Liger(x)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@normData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setMethod("scaleData", c("ligerDataset", "missing"), + function(x, dataset = NULL) x@scaleData) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "scaleData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, dataset = NULL, check = TRUE, value) { + if (isH5Liger(x)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@scaleData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "scaleData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5D"), + function(x, dataset = NULL, check = TRUE, value) { + if (!isH5Liger(x)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@scaleData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "scaleData", + signature(x = "ligerDataset", dataset = "ANY", check = "ANY", value = "H5Group"), + function(x, dataset = NULL, check = TRUE, value) { + if (!isH5Liger(x)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@scaleData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setMethod("scaleUnsharedData", c("ligerDataset", "missing"), + function(x, dataset = NULL) x@scaleUnsharedData) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "scaleUnsharedData", + signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "matrixLike_OR_NULL"), + function(x, check = TRUE, value) { + if (isH5Liger(x)) + cli::cli_abort("Cannot replace slot with in-memory data for H5 based object.") + x@scaleUnsharedData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "scaleUnsharedData", + signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "H5D"), + function(x, check = TRUE, value) { + if (!isH5Liger(x)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@scaleUnsharedData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "scaleUnsharedData", + signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "H5Group"), + function(x, check = TRUE, value) { + if (!isH5Liger(x)) + cli::cli_abort("Cannot replace slot with on-disk data for in-memory object.") + x@scaleUnsharedData <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname ligerDataset-class +setMethod( + "getMatrix", signature(x = "ligerDataset", dataset = "missing", + returnList = "missing"), + function(x, + slot = c("rawData", "normData", "scaleData", + "scaleUnsharedData", "H", "V", "U", "A", "B"), + dataset = NULL) { + # TODO: Currently directly find the data with slot, but need to + # think about maintainability when we need to change slot name. + slot <- match.arg(slot) + methods::slot(x, slot) + }) + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# H5 related #### +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#' @export +#' @rdname ligerDataset-class +setMethod( + "h5fileInfo", + signature = signature(x = "ligerDataset", info = "ANY"), + function(x, info = NULL) { + if (is.null(info)) result <- x@h5fileInfo + else { + if (length(info) == 1) result <- x@h5fileInfo[[info]] + else { + if (any(!info %in% names(x@h5fileInfo))) { + cli::cli_abort( + "Specified {.code info} not found: {.val {info[!info %in% names(x@h5fileInfo)]}}" + ) + } + result <- x@h5fileInfo[info] + names(result) <- info + } + } + return(result) + }) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "h5fileInfo", + signature = signature( + x = "ligerDataset", + info = "ANY", + check = "ANY", + value = "ANY" + ), + function(x, info = NULL, check = TRUE, value) { + if (is.null(info)) { + x@h5fileInfo <- value + } else { + if (!is.character(info) | length(info) != 1) + cli::cli_abort("{.var info} has to be a single character.") + if (info %in% c("indicesName", "indptrName", "barcodesName", + "genesName", "rawData", "normData", + "scaleData")) { + if (!getH5File(x)$exists(value)) { + cli::cli_abort("Specified {.var info} is invalid, {.field info} does not exist in the HDF5 file.") + } + } + x@h5fileInfo[[info]] <- value + if (info %in% c("rawData", "normData", "scaleData", + "scaleUnsharedData")) { + x <- do.call(paste0(info, "<-"), + list(x = x, + value = getH5File(x)[[h5fileInfo(x, info)]], + check = check)) + } + } + if (isTRUE(check)) methods::validObject(x) + x + } +) + + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# Feature metadata #### +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#' @export +#' @rdname ligerDataset-class +setMethod("featureMeta", signature(x = "ligerDataset", check = "ANY"), + function(x, check = NULL) { + x@featureMeta +}) + +#' @export +#' @rdname ligerDataset-class +setReplaceMethod( + "featureMeta", + signature(x = "ligerDataset", check = "ANY"), + function(x, check = TRUE, value) { + if (!inherits(value, "DFrame")) + value <- S4Vectors::DataFrame(value) + x@featureMeta <- value + if (isTRUE(check)) methods::validObject(x) + x + } +) + +#' @export +#' @rdname liger-class +setMethod("varUnsharedFeatures", + signature(x = "ligerDataset", dataset = "missing"), + function(x, dataset = NULL) x@varUnsharedFeatures) + +#' @export +#' @rdname liger-class +setReplaceMethod( + "varUnsharedFeatures", + signature(x = "ligerDataset", dataset = "missing", check = "ANY", value = "character"), + function(x, dataset = NULL, check = TRUE, value) { + x@varUnsharedFeatures <- value + if (isTRUE(check)) { + if (!all(value %in% rownames(x))) { + cli::cli_alert_warning("Not all features passed are found.") + } + } + return(x) + } +) + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# S3 Method #### +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#' @section Concatenate ligerDataset: +#' \code{cbind()} method is implemented for concatenating \code{ligerDataset} +#' objects by cells. When applying, all feature expression matrix will be merged +#' with taking a union of all features for the rows. +#' @param deparse.level Not used here. +#' @rdname ligerDataset-class +#' @export +#' @method cbind ligerDataset +cbind.ligerDataset <- function(x, ..., + deparse.level = 1) { + args <- list(...) + isLD <- sapply(args, function(x) inherits(x, "ligerDataset")) + if (any(!isLD)) { + cli::cli_alert_warning("Discarding arguments that are not of {.cls ligerDataset} class") + args <- args[isLD] + } + if (!missing(x)) args <- c(list(x), args) + isH5 <- sapply(args, isH5Liger) + # See mergeObject.R + if (all(isH5)) .cbind.ligerDataset.h5(args) + else if (!any(isH5)) .cbind.ligerDataset.mem(args) + else + cli::cli_abort("Cannot {.fn cbind} a hybrid of H5 and in-memory {.cls ligerDataset}s for now.") +} diff --git a/R/ligerDataset_subclass-methods.R b/R/ligerDataset_subclass-methods.R new file mode 100644 index 00000000..502fb1c7 --- /dev/null +++ b/R/ligerDataset_subclass-methods.R @@ -0,0 +1,57 @@ + +#' @rdname peak +#' @export +setMethod("rawPeak", signature(x = "ligerATACDataset", dataset = "missing"), + function(x, dataset = NULL) { + x@rawPeak + }) + +#' @rdname peak +#' @export +setReplaceMethod( + "rawPeak", + signature(x = "ligerATACDataset", dataset = "missing"), + function(x, dataset = NULL, check = TRUE, value) { + x@rawPeak <- value + if (isTRUE(check)) methods::validObject(x) + x + }) + + +#' @rdname peak +#' @export +setMethod("normPeak", signature(x = "ligerATACDataset", dataset = "missing"), + function(x, dataset = NULL) { + x@normPeak + }) + +#' @rdname peak +#' @export +setReplaceMethod( + "normPeak", + signature(x = "ligerATACDataset", dataset = "missing"), + function(x, dataset = NULL, check = TRUE, value) { + x@normPeak <- value + if (isTRUE(check)) methods::validObject(x) + x + }) + +#' @rdname coordinate +#' @export +setMethod("coordinate", signature(x = "ligerSpatialDataset", dataset = "missing"), + function(x, dataset = NULL) { + x@coordinate + }) + +#' @rdname coordinate +#' @export +setReplaceMethod( + "coordinate", + signature(x = "ligerSpatialDataset", dataset = "missing"), + function(x, dataset = NULL, check = TRUE, value) { + value <- .checkCoords(ld = x, value = value) + x@coordinate <- value + if (isTRUE(check)) methods::validObject(x) + x + }) + diff --git a/R/ligerDataset_subclasses.R b/R/ligerDataset_subclasses.R deleted file mode 100644 index 46c690a2..00000000 --- a/R/ligerDataset_subclasses.R +++ /dev/null @@ -1,336 +0,0 @@ - -################################################################################ -# Developer guide for adding a new sub-class of `ligerDataset` for new modality -################################################################################ -# -# Below is a check-list of the TODOs when new sub-classes need to be added. -# Please follow them carefully, and refer to existing code as examples. -# -# 1. Add `setClass` chunk for defining the new subclass, pay attention to: -# a. Naming convention should be `liger{Modal}Dataset`, in camelCase -# b. contains = "ligerDataset" -# c. add new slots for modality specific information with `representation` -# d. if the default new information could be empty, add `prototype` -# 2. In files `zzz.R`, `import.R`, `classConversion.R`, search for -# text "modal". When seeing a multi-option vector argument, add a unique -# abbreviation of this new data type to the vector. Don't forget updating -# valid options in the manual documentaion as well. -# 3. If the new slot(s) added is thought to be retrieved by future developers -# or users, getter and setter methods MUST be implemented. -# 4. Please go through the implementation of the following functions in file -# `ligerDataset-class.R`, and make sure data in the new slot(s) is properly -# handled. -# a. .checkLigerDatasetBarcodes() -# b. `dimnames<-()` (search: `setReplaceMethod("dimnames"`) -# c. `[` (search: "[") -# -################################################################################ - -setClassUnion("matrixLike_OR_NULL", c( - "matrix", "dgCMatrix", "dgTMatrix", "dgeMatrix", "NULL" -)) - -#' Subclass of ligerDataset for RNA modality -#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots -#' can be referred with the link. This subclass does not have any different from -#' the default \code{ligerDataset} class except the class name. -#' @export -#' @exportClass ligerRNADataset -ligerRNADataset <- setClass( - "ligerRNADataset", contains = "ligerDataset" -) - -#------------------------------------------------------------------------------- -# Sub-class for ATAC data #### -#------------------------------------------------------------------------------- - -#' Subclass of ligerDataset for ATAC modality -#' -#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots -#' can be referred with the link. -#' @slot rawPeak sparse matrix -#' @slot normPeak sparse matrix -#' @exportClass ligerATACDataset -#' @export -ligerATACDataset <- setClass( - "ligerATACDataset", - contains = "ligerDataset", - representation = representation(rawPeak = "matrixLike_OR_NULL", - normPeak = "matrixLike_OR_NULL"), - prototype = prototype(rawPeak = NULL, normPeak = NULL) -) - -#' Access ligerATACDataset peak data -#' @description Similar as how default \linkS4class{ligerDataset} data is -#' accessed. -#' @param x \linkS4class{ligerATACDataset} object or a \linkS4class{liger} -#' object. -#' @param dataset Name or numeric index of an ATAC dataset. -#' @param check Logical, whether to perform object validity check on setting new -#' value. -#' @param value \code{\link[Matrix]{dgCMatrix-class}} matrix. -#' @return The retrieved peak count matrix or the updated \code{x} object. -#' @rdname peak -#' @export -setGeneric("rawPeak", function(x, dataset) standardGeneric("rawPeak")) - -#' @rdname peak -#' @export -setGeneric("rawPeak<-", function(x, dataset, check = TRUE, value) standardGeneric("rawPeak<-")) - -#' @rdname peak -#' @export -setGeneric("normPeak", function(x, dataset) standardGeneric("normPeak")) - -#' @rdname peak -#' @export -setGeneric("normPeak<-", function(x, dataset, check = TRUE, value) standardGeneric("normPeak<-")) - -#' @rdname peak -#' @export -setMethod("rawPeak", signature(x = "liger", dataset = "character"), - function(x, dataset) { - atac <- dataset(x, dataset) - if (!inherits(atac, "ligerATACDataset")) { - stop("Specified dataset is not of ligerATACDataset class.") - } - atac@rawPeak - }) - -#' @rdname peak -#' @export -setReplaceMethod( - "rawPeak", - signature(x = "liger", dataset = "character"), - function(x, dataset, check = TRUE, value) { - if (!inherits(dataset(x, dataset), "ligerATACDataset")) - stop("Specified dataset is not of `ligerATACDataset` class.") - x@datasets[[dataset]]@rawPeak <- value - if (isTRUE(check)) methods::validObject(dataset(x, dataset)) - x - }) - -#' @rdname peak -#' @export -setMethod("rawPeak", signature(x = "ligerATACDataset", dataset = "missing"), - function(x, dataset = NULL) { - x@rawPeak - }) - -#' @rdname peak -#' @export -setReplaceMethod( - "rawPeak", - signature(x = "ligerATACDataset", dataset = "missing"), - function(x, dataset = NULL, check = TRUE, value) { - x@rawPeak <- value - if (isTRUE(check)) methods::validObject(x) - x - }) - -#' @rdname peak -#' @export -setMethod("normPeak", signature(x = "liger", dataset = "character"), - function(x, dataset) { - atac <- dataset(x, dataset) - if (!inherits(atac, "ligerATACDataset")) { - stop("Specified dataset is not of ligerATACDataset class.") - } - atac@normPeak - }) - -#' @rdname peak -#' @export -setReplaceMethod( - "normPeak", - signature(x = "liger", dataset = "character"), - function(x, dataset, check = TRUE, value) { - if (!inherits(dataset(x, dataset), "ligerATACDataset")) - stop("Specified dataset is not of `ligerATACDataset` class.") - x@datasets[[dataset]]@normPeak <- value - if (isTRUE(check)) methods::validObject(dataset(x, dataset)) - x - }) - -#' @rdname peak -#' @export -setMethod("normPeak", signature(x = "ligerATACDataset", dataset = "missing"), - function(x, dataset = NULL) { - x@normPeak - }) - -#' @rdname peak -#' @export -setReplaceMethod( - "normPeak", - signature(x = "ligerATACDataset", dataset = "missing"), - function(x, dataset = NULL, check = TRUE, value) { - x@normPeak <- value - if (isTRUE(check)) methods::validObject(x) - x - }) - -.valid.ligerATACDataset <- function(object) { - passSuperClassCheck <- .valid.ligerDataset(object) - if (!isTRUE(passSuperClassCheck)) return(passSuperClassCheck) - for (slot in c("rawPeak", "normPeak")) { - data <- methods::slot(object, slot) - if (!is.null(data)) { - barcodes.slot <- colnames(data) - if (!identical(object@colnames, barcodes.slot)) { - return(paste0("Inconsistant cell identifiers in `", slot, - "` slot.")) - } - } - } -} - -setValidity("ligerATACDataset", .valid.ligerATACDataset) - -#------------------------------------------------------------------------------- -# Sub-class for Spatial data #### -#------------------------------------------------------------------------------- - -#' Subclass of ligerDataset for Spatial modality -#' -#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots -#' can be referred with the link. -#' @slot coordinate dense matrix -#' @exportClass ligerSpatialDataset -#' @export -ligerSpatialDataset <- setClass( - "ligerSpatialDataset", - contains = "ligerDataset", - representation = representation(coordinate = "matrix_OR_NULL"), - prototype = prototype(coordinate = NULL) -) - -#' Access ligerSpatialDataset coordinate data -#' @description Similar as how default \linkS4class{ligerDataset} data is -#' accessed. -#' @param x \linkS4class{ligerSpatialDataset} object or a \linkS4class{liger} -#' object. -#' @param dataset Name or numeric index of an spatial dataset. -#' @param check Logical, whether to perform object validity check on setting new -#' value. -#' @param value \code{\link{matrix}}. -#' @return The retrieved coordinate matrix or the updated \code{x} object. -#' @rdname coordinate -#' @export -setGeneric("coordinate", function(x, dataset) standardGeneric("coordinate")) - -#' @rdname coordinate -#' @export -setGeneric("coordinate<-", function(x, dataset, check = TRUE, value) standardGeneric("coordinate<-")) - - -#' @rdname coordinate -#' @export -setMethod("coordinate", signature(x = "liger", dataset = "character"), - function(x, dataset) { - spatial <- dataset(x, dataset) - if (!inherits(spatial, "ligerSpatialDataset")) { - stop("Specified dataset is not of `ligerSpatialDataset` class.") - } - spatial@coordinate - }) - -#' @rdname coordinate -#' @export -setReplaceMethod( - "coordinate", - signature(x = "liger", dataset = "character"), - function(x, dataset, check = TRUE, value) { - if (!inherits(dataset(x, dataset), "ligerSpatialDataset")) - stop("Specified dataset is not of `ligerSpatialDataset` class.") - value <- .checkCoords(ld = dataset(x, dataset), value = value) - x@datasets[[dataset]]@coordinate <- value - if (isTRUE(check)) methods::validObject(dataset(x, dataset)) - x - }) - -#' @rdname coordinate -#' @export -setMethod("coordinate", signature(x = "ligerSpatialDataset", dataset = "missing"), - function(x, dataset = NULL) { - x@coordinate - }) - -#' @rdname coordinate -#' @export -setReplaceMethod( - "coordinate", - signature(x = "ligerSpatialDataset", dataset = "missing"), - function(x, dataset = NULL, check = TRUE, value) { - value <- .checkCoords(ld = x, value = value) - x@coordinate <- value - if (isTRUE(check)) methods::validObject(x) - x - }) - -.checkCoords <- function(ld, value) { - if (is.null(rownames(value))) { - warning("No rownames with given spatial coordinate, ", - "assuming they match with the cells.") - rownames(value) <- colnames(ld) - } - if (is.null(colnames(value))) { - if (ncol(value) <= 3) { - colnames(value) <- c("x", "y", "z")[seq(ncol(value))] - } else { - stop("More than 3 dimensions for the coordinates but no ", - "colnames are given.") - } - warning("No colnames with given spatial coordinate, ", - "setting to ", paste0(colnames(value), collapse = ", ")) - } - full <- matrix(NA, nrow = ncol(ld), ncol = ncol(value), - dimnames = list(colnames(ld), colnames(value))) - cellIsec <- intersect(rownames(value), colnames(ld)) - full[cellIsec, colnames(value)] <- value[cellIsec,] - if (any(is.na(full))) { - warning("NA generated for missing cells.") - } - if (any(!rownames(value) %in% rownames(full))) { - warning("Cells in given coordinate not found in the dataset.") - } - return(full) -} - -.valid.ligerSpatialDataset <- function(object) { - passSuperClassCheck <- .valid.ligerDataset(object) - if (!isTRUE(passSuperClassCheck)) return(passSuperClassCheck) - coord <- object@coordinate - if (!is.null(coord)) { - barcodes.slot <- rownames(coord) - if (!identical(object@colnames, barcodes.slot)) { - return(paste0("Inconsistant cell identifiers in `coordinate` slot.")) - } - } -} - -setValidity("ligerSpatialDataset", .valid.ligerSpatialDataset) - - - -#------------------------------------------------------------------------------- -# Sub-class for Methylation data #### -#------------------------------------------------------------------------------- - -#' Subclass of ligerDataset for Methylation modality -#' -#' @description Inherits from \linkS4class{ligerDataset} class. Contained slots -#' can be referred with the link. \code{\link{scaleNotCenter}} applied on -#' datasets of this class will automatically be taken by reversing the -#' normalized data instead of scaling the variable features. -#' @exportClass ligerMethDataset -#' @export -ligerMethDataset <- setClass( - "ligerMethDataset", - contains = "ligerDataset" -) - -.valid.ligerMethDataset <- function(object) .valid.ligerDataset(object) - -setValidity("ligerMethDataset", .valid.ligerMethDataset) - diff --git a/R/preprocess.R b/R/preprocess.R index 0faa6553..f2bbc703 100644 --- a/R/preprocess.R +++ b/R/preprocess.R @@ -20,7 +20,7 @@ #' @param chunkSize Integer number of cells to include in a chunk when working #' on HDF5 based dataset. Default \code{1000} #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @return Updated \code{object} with \code{nUMI}, \code{nGene} updated #' in \code{cellMeta(object)}, as well as expression percentage value for each #' feature subset. @@ -36,7 +36,7 @@ runGeneralQC <- function( pattern = NULL, useDatasets = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) { .checkObjVersion(object) useDatasets <- .checkUseDatasets(object, useDatasets) @@ -79,7 +79,8 @@ runGeneralQC <- function( for (d in useDatasets) { ld <- dataset(object, d) - if (isTRUE(verbose)) .log('calculating QC for dataset "', d, '"') + if (isTRUE(verbose)) + cliID <- cli::cli_process_start("calculating QC for dataset {.val {d}}") if (isH5Liger(ld)) results <- runGeneralQC.h5( ld, @@ -96,6 +97,7 @@ runGeneralQC <- function( object@cellMeta[object$dataset == d, newResultNames] <- results$cell featureMeta(ld, check = FALSE)$nCell <- results$feature datasets(object, check = FALSE)[[d]] <- ld + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) } return(object) @@ -111,7 +113,7 @@ runGeneralQC.h5 <- function( object, featureSubsets = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose")) { + verbose = getOption("ligerVerbose", TRUE)) { allFeatures <- rownames(object) # Initialize results cell <- data.frame(row.names = colnames(object)) @@ -154,7 +156,7 @@ runGeneralQC.h5 <- function( runGeneralQC.Matrix <- function( object, featureSubsets = NULL, - verbose = getOption("ligerVerbose")) { + verbose = getOption("ligerVerbose", TRUE)) { nUMI <- Matrix::colSums(rawData(object)) # Instead of using `nonzero <- rawData > 0` which generates dense logical # matrix, keep it sparse with 1 for TRUE @@ -216,7 +218,7 @@ getProportionMito <- function(object, use.norm = FALSE, pattern = "^mt-") { result <- c(result, pctMT) } if (all(result == 0)) { - message("Zero proportion detected in all cells") + cli::cli_alert_warning("Zero proportion detected in all cells") } return(result) } @@ -241,7 +243,7 @@ getProportionMito <- function(object, use.norm = FALSE, pattern = "^mt-") { #' @param filenameSuffix When subsetting H5-based datasets to new H5 files, this #' suffix will be added to all the filenames. Default \code{"removeMissing"}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @param ... Arguments passed to \code{\link{subsetLigerDataset}} #' @return Updated (subset) \code{object}. #' @export @@ -257,19 +259,16 @@ removeMissing <- function( useDatasets = NULL, newH5 = TRUE, filenameSuffix = "removeMissing", - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) { - if (!inherits(object, "liger")) { - stop("Please use a `liger` object.") - } orient <- match.arg(orient) useDatasets <- .checkUseDatasets(object, useDatasets) minCells <- minCells %||% rep(0, length(useDatasets)) - minCells <- .checkArgLen(minCells, length(useDatasets)) + minCells <- .checkArgLen(minCells, length(useDatasets), class = "numeric") names(minCells) <- useDatasets minFeatures <- minFeatures %||% rep(0, length(useDatasets)) - minFeatures <- .checkArgLen(minFeatures, length(useDatasets)) + minFeatures <- .checkArgLen(minFeatures, length(useDatasets), class = "numeric") names(minFeatures) <- useDatasets rmFeature <- ifelse(orient %in% c("both", "feature"), TRUE, FALSE) rmCell <- ifelse(orient %in% c("both", "cell"), TRUE, FALSE) @@ -293,7 +292,8 @@ removeMissing <- function( rmCellDataset <- length(cellIdx) != ncol(ld) subsetted <- c(subsetted, any(c(rmFeatureDataset, rmCellDataset))) if (any(c(rmFeatureDataset, rmCellDataset))) { - if (isTRUE(verbose)) .log("Removing missing in dataset: ", d) + if (isTRUE(verbose)) + cli::cli_alert_info("Removing missing in dataset: {.val {d}}") datasets.new[[d]] <- subsetLigerDataset( ld, featureIdx = featureIdx, @@ -335,11 +335,11 @@ removeMissingObs <- function( object, slot.use = NULL, use.cols = TRUE, - verbose = getOption("ligerVerbose")) { + verbose = getOption("ligerVerbose", TRUE)) { lifecycle::deprecate_warn("1.99.0", "removeMissingObs()", "removeMissing()") if (!missing(slot.use)) { - warning("Argument `slot.use` is ignored. ") + cli::cli_alert_warning("Argument {.code slot.use} is deprecated and ignored.") } orient <- ifelse(isTRUE(use.cols), "cell", "gene") object <- removeMissing(object, orient, verbose = verbose) @@ -348,10 +348,6 @@ removeMissingObs <- function( - - - - ################################ Normalize ##################################### #' Normalize raw counts data @@ -399,9 +395,9 @@ normalize.dgCMatrix <- function( scaleFactor = NULL, ... ) { - if (!is.null(scaleFactor) && scaleFactor <= 0) { - scaleFactor <- .checkArgLen(scaleFactor, ncol(object), repN = TRUE) - warning("Invalid `scaleDactor` given. Setting to `NULL`.") + scaleFactor <- .checkArgLen(scaleFactor, ncol(object), repN = TRUE, class = "numeric") + if (!is.null(scaleFactor) && any(scaleFactor <= 0)) { + cli::cli_alert_danger("Invalid {.code scaleFactor} given. Setting to {.code NULL}.") scaleFactor <- NULL } normed <- object @@ -416,12 +412,12 @@ normalize.dgCMatrix <- function( #' @param chunk Integer. Number of maximum number of cells in each chunk when #' working on HDF5 file based ligerDataset. Default \code{1000}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @method normalize ligerDataset normalize.ligerDataset <- function( object, chunk = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) { if (!isH5Liger(object)) { @@ -483,7 +479,7 @@ normalize.ligerDataset <- function( normalize.liger <- function( object, useDatasets = NULL, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), format.type = NULL, remove.missing = NULL, ... @@ -493,12 +489,13 @@ normalize.liger <- function( useDatasets <- .checkUseDatasets(object, useDatasets) object <- recordCommand(object, ..., dependencies = "hdf5r") for (d in useDatasets) { + if (isTRUE(verbose)) cliID <- cli::cli_process_start("Normalizing datasets {.val {d}}") # `d` is the name of each dataset - if (isTRUE(verbose)) .log("Normalizing dataset: ", d) ld <- dataset(object, d) ld <- normalize(ld, verbose = verbose, ...) datasets(object, check = FALSE)[[d]] <- ld } + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) object } @@ -532,25 +529,22 @@ normalize.Seurat <- function( normalizePeak <- function( object, useDatasets = NULL, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) { useDatasets <- .checkUseDatasets(object, useDatasets, modal = "atac") object <- recordCommand(object, ..., dependencies = "hdf5r") for (d in useDatasets) { + if (isTRUE(verbose)) cliID <- cli::cli_process_start("Normalizing peak of dataset: {.val {d}}") # `d` is the name of each dataset - if (isTRUE(verbose)) .log("Normalizing rawPeak counts in dataset: ", d) ld <- dataset(object, d) normPeak(ld, check = FALSE) <- normalize(rawPeak(ld), ...) datasets(object, check = FALSE)[[d]] <- ld + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) } object } - - - - ############################### Select Genes ################################### #' Select a subset of informative genes @@ -580,7 +574,7 @@ normalizePeak <- function( #' @param combine How to combine variable genes selected from all datasets. #' Choose from \code{"union"} or \code{"intersection"}. Default \code{"union"}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @param ... Arguments passed to other methods. #' @return Updated object #' \itemize{ @@ -643,7 +637,7 @@ selectGenes.liger <- function( unsharedThresh = .1, combine = c("union", "intersection"), chunk = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), var.thresh = thresh, alpha.thresh = alpha, num.genes = nGenes, @@ -670,14 +664,14 @@ selectGenes.liger <- function( datasetUnshared <- .checkUseDatasets(object, useUnsharedDatasets) else datasetUnshared <- NULL useDatasets <- union(datasetShared, datasetUnshared) - thresh <- .checkArgLen(thresh, length(datasetShared)) - nGenes <- .checkArgLen(nGenes, length(datasetShared)) - unsharedThresh <- .checkArgLen(unsharedThresh, length(datasetUnshared)) + thresh <- .checkArgLen(thresh, length(datasetShared), class = "numeric") + nGenes <- .checkArgLen(nGenes, length(datasetShared), class = "numeric") + unsharedThresh <- .checkArgLen(unsharedThresh, length(datasetUnshared), class = "numeric") sharedFeature <- Reduce(intersect, lapply(datasets(object), rownames)) selectList <- list() for (d in useDatasets) { if (isTRUE(verbose)) - .log("Selecting variable features for dataset: ", d) + cli::cli_alert_info("Selecting variable features for dataset {.val {d}}") ld <- dataset(object, d) thresh_i <- thresh[datasetShared == d] nGenes_i <- nGenes[datasetShared == d] @@ -695,12 +689,10 @@ selectGenes.liger <- function( if (combine == "union") selected <- Reduce(union, selectList) else selected <- Reduce(intersect, selectList) if (length(selected) == 0) { - warning("No genes were selected. Lower `thresh` values or set ", - '`combine = "union"`', immediate. = TRUE) + cli::cli_alert_danger("No genes were selected. Lower {.code thresh} values or set {.code combine = 'union'}") } else { if (isTRUE(verbose)) - .log("Finally ", length(selected), - " shared variable features selected.") + cli::cli_alert_success("Finally {length(selected)} shared variable feature{?s} are selected.") } varFeatures(object) <- selected for (d in names(object)) { @@ -734,9 +726,9 @@ selectGenes.liger <- function( unsharedThresh = .1, alpha = .99, chunk = 1000, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) { - if (is.null(normData(object))) stop("Normalized data not available.") + if (is.null(normData(object))) cli::cli_abort("Normalized data not available.") if (is.null(sharedFeature)) sharedFeature <- rownames(object) sharedFeature <- rownames(object) %in% sharedFeature unsharedFeature <- !sharedFeature @@ -760,8 +752,7 @@ selectGenes.liger <- function( featureMeta(object, check = FALSE)$isVariable <- rownames(object) %in% selected.shared if (isTRUE(verbose)) { - .log(length(selected.shared), " features selected out of ", - sum(sharedFeature), " shared features", level = 2) + cli::cli_alert_success("... {length(selected.shared)} feature{?s} selected out of {sum(sharedFeature)} shared feature{?s}.") } if (isTRUE(unshared) && length(unsharedFeature) > 0) { selected.unshared <- .selectGenes.withMetric( @@ -773,8 +764,7 @@ selectGenes.liger <- function( ) object@varUnsharedFeatures <- selected.unshared if (isTRUE(verbose)) { - .log(length(selected.unshared), " features selected out of ", - sum(unsharedFeature), " unshared features", level = 2) + cli::cli_alert_success("... {length(selected.unshared)} feature{?s} selected out of {sum(unsharedFeature)} unshared feature{?s}.") } } return(object) @@ -785,11 +775,11 @@ selectGenes.liger <- function( #' @param chunkSize Integer for the maximum number of cells in each chunk. #' Default \code{1000}. #' @param verbose Logical. Whether to show a progress bar. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @return The input \code{object} with calculated var updated in the H5 file. #' @noRd calcGeneVars.H5 <- function(object, chunkSize = 1000, - verbose = getOption("ligerVerbose")) { + verbose = getOption("ligerVerbose", TRUE)) { h5file <- getH5File(object) geneVars <- rep(0, nrow(object)) geneMeans <- h5file[["gene_means"]][] @@ -831,7 +821,7 @@ selectGenes.Seurat <- function( assay = NULL, datasetVar = "orig.ident", combine = c("union", "intersection"), - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) { combine <- match.arg(combine) @@ -852,7 +842,7 @@ selectGenes.Seurat <- function( featureList <- lapply(matList, rownames) allshared <- Reduce(intersect, featureList) allFeatures <- SeuratObject::Features(object, assay = assay) - thresh <- .checkArgLen(thresh, nlevels(datasetVar)) + thresh <- .checkArgLen(thresh, nlevels(datasetVar), class = "numeric") # Get nUMI metric into list nUMIVar <- paste0("nCount_", assay) @@ -863,7 +853,7 @@ selectGenes.Seurat <- function( hvg.info <- data.frame(row.names = allFeatures) for (d in levels(datasetVar)) { if (isTRUE(verbose)) - .log("Selecting variable features for dataset: ", d) + cli::cli_alert_info("Selecting variable features for dataset: {.val {d}}") submat <- matList[[d]] # submat <- mat[, datasetVar == d, drop = FALSE] # submat <- submat[sort(expressed), , drop = FALSE] @@ -885,20 +875,17 @@ selectGenes.Seurat <- function( nGenes = nGenes ) if (isTRUE(verbose)) { - .log(length(selected), " features selected out of ", - length(allshared), " shared features", level = 2) + cli::cli_alert_success("... {length(selected)} features selected out of {length(allshared)} shared features") } selectList[[d]] <- selected } if (combine == "union") selected <- Reduce(union, selectList) else selected <- Reduce(intersect, selectList) if (length(selected) == 0) { - warning("No genes were selected. Lower `thresh` values or set ", - '`combine = "union"`', immediate. = TRUE) + cli::cli_alert_danger("No genes were selected. Lower {.code thresh} values or set {.code combine = 'union'}") } else { if (isTRUE(verbose)) - .log("Finally ", length(selected), - " shared variable features selected.") + cli::cli_alert_success("Finally {length(selected)} shared variable features selected.") } hvg.info$liger.variable <- allFeatures %in% selected assayObj <- Seurat::GetAssay(object, assay = assay) @@ -1041,7 +1028,7 @@ plotVarFeatures <- function( #' @param useShared Logical. Whether to only select from genes shared by all #' dataset. Default \code{TRUE}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @references Seurat::FindVariableFeatures.default(selection.method = "vst") #' @export #' @examples @@ -1053,13 +1040,12 @@ selectGenesVST <- function( loessSpan = 0.3, clipMax = "auto", useShared = TRUE, - verbose = getOption("ligerVerbose")) + verbose = getOption("ligerVerbose", TRUE)) { useDataset <- .checkUseDatasets(object, useDataset) useDataset <- .checkArgLen(useDataset, 1) if (isTRUE(verbose)) { - .log("Selecting top ", n, " HVGs with VST method for dataset: ", - useDataset) + cli::cli_alert_info("Selecting top {n} HVG{?s} with VST method for dataset: {.val {useDataset}}") } ld <- dataset(object, useDataset) data <- rawData(ld) @@ -1070,10 +1056,7 @@ selectGenesVST <- function( useGenes <- rownames(data) } if (isTRUE(verbose)) { - .log("Totally ", length(useGenes), - ifelse(useShared, " shared ", " dataset specific "), - "genes to be selected from.", - level = 2) + cli::cli_alert_info("... Totally {length(useGenes)} {ifelse(useShared, 'shared', 'dataset specific')} genes to be selected from.") } if (clipMax == "auto") { clipMax <- sqrt(ncol(data)) @@ -1202,12 +1185,12 @@ scaleNotCenter.dgCMatrix <- function( #' @param chunk Integer. Number of maximum number of cells in each chunk, when #' scaling is applied to any HDF5 based dataset. Default \code{1000}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. scaleNotCenter.ligerDataset <- function( object, features = NULL, chunk = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) { features <- .idxCheck(object, features, "feature") @@ -1238,7 +1221,7 @@ scaleNotCenter.ligerDataset <- function( scaleNotCenter.ligerMethDataset <- function( object, features = NULL, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) { raw <- rawData(object) @@ -1262,23 +1245,25 @@ scaleNotCenter.liger <- function( object, useDatasets = NULL, features = varFeatures(object), - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), remove.missing = NULL, ... ) { .deprecateArgs(defunct = "remove.missing") .checkObjVersion(object) if (is.null(features) || length(features) == 0) { - stop("No variable feature specified. Run `selectGenes()` first") + cli::cli_abort("No variable feature specified. Run {.fn selectGenes} first.") } useDatasets <- .checkUseDatasets(object, useDatasets) object <- recordCommand(object, ..., dependencies = c("RcppArmadillo", "Rcpp")) + for (d in useDatasets) { - if (isTRUE(verbose)) .log("Scaling dataset: ", d) + if (isTRUE(verbose)) cliID <- cli::cli_process_start("Scaling dataset {.val {d}}") ld <- dataset(object, d) ld <- scaleNotCenter(ld, features = features, verbose = verbose, ...) datasets(object, check = FALSE)[[d]] <- ld + if (isTRUE(verbose)) cli::cli_process_done(id = cliID) } return(object) } @@ -1310,7 +1295,7 @@ scaleNotCenter.Seurat <- function( features <- features %||% SeuratObject::VariableFeatures(object) if (!length(features)) { - stop("No variable feature specified. Run `selectGenes()` first") + cli::cli_abort("No variable feature specified. Run {.fn selectGenes} first") } if (is.list(normed)) { @@ -1346,10 +1331,9 @@ scaleNotCenter.Seurat <- function( geneRootMeanSumSq = sqrt(geneSumSq / (nCells - 1)) h5file <- getH5File(ld) # Count the subset nnz first before creating data space - if (isTRUE(verbose)) .log("Counting number of non-zero values...") nnz <- 0 nnz <- H5Apply( - ld, useData = "normData", chunkSize = chunk, verbose = verbose, + ld, useData = "normData", chunkSize = chunk, verbose = FALSE, FUN = function(chunk, sparseXIdx, cellIdx, values) { chunk <- chunk[featureIdx, , drop = FALSE] values <- values + length(chunk@x) @@ -1413,7 +1397,7 @@ scaleNotCenter.Seurat <- function( #' logical vector of the index of the datasets that should be identified as #' methylation data where the reversed data will be created. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @return The input \linkS4class{liger} object, where the \code{scaleData} slot #' of the specified datasets will be updated with value as described in #' Description. @@ -1425,16 +1409,15 @@ scaleNotCenter.Seurat <- function( #' pbmc <- scaleNotCenter(pbmc, useDatasets = 1) #' pbmc <- reverseMethData(pbmc, useDatasets = 2) reverseMethData <- function(object, useDatasets, - verbose = getOption("ligerVerbose")) { + verbose = getOption("ligerVerbose", TRUE)) { useDatasets <- .checkUseDatasets(object, useDatasets) if (is.null(varFeatures(object)) || length(varFeatures(object)) == 0) { - stop("Variable genes have to be identified first. ", - "Please run `selectGenes(object)`.") + cli::cli_abort("No variable feature available. Run {.fn selectGenes} first.") } for (d in useDatasets) { ld <- dataset(object, d) raw <- rawData(ld) - if (isTRUE(verbose)) .log("Substracting methylation data: ", d) + if (isTRUE(verbose)) cli::cli_alert_info("Substracting methylation data: {.val {d}}") scaleData(ld, check = FALSE) <- methods::as( max(raw) - raw[varFeatures(object), , drop = FALSE], "CsparseMatrix" diff --git a/R/subsetObject.R b/R/subsetObject.R index 9cafc4f5..ad09cba1 100644 --- a/R/subsetObject.R +++ b/R/subsetObject.R @@ -22,7 +22,7 @@ #' @param chunkSize Integer. Number of maximum number of cells in each chunk, #' Default \code{1000}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @param returnObject Logical, whether to return a \linkS4class{liger} object #' for result. Default \code{TRUE}. \code{FALSE} returns a list containing #' requested values. @@ -40,7 +40,7 @@ subsetLiger <- function( cellIdx = NULL, useSlot = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), newH5 = TRUE, returnObject = TRUE, ... @@ -53,7 +53,8 @@ subsetLiger <- function( return(object) } if (!inherits(object, "liger")) { - warning("`object` is not a liger obejct. Nothing to be done.") + cli::cli_alert_danger("{.var object} is not a {.cls liger} object.") + cli::cli_alert_info("Nothing to be done.") return(object) } # Check subscription parameters #### @@ -65,26 +66,28 @@ subsetLiger <- function( # feature idx need different check from ligerDataset's .idxCheck if (!is.null(featureIdx)) { if (!is.character(featureIdx)) { - stop("Feature subscription from liger object can only take ", - "character vector.") + cli::cli_abort( + "Feature subscription from a {.cls liger} object can only take {.cls character} vector (e.g. gene names)." + ) } genesList <- lapply(datasets(object)[useDatasets], rownames) allGenes <- unique(unlist(genesList, use.names = FALSE)) if (!all(featureIdx %in% allGenes)) { notFound <- featureIdx[!featureIdx %in% allGenes] - warning(length(notFound), " out of ", length(featureIdx), - " given features were not found in the union of all ", - "features of used datasets") + cli::cli_alert_warning( + c("{length(notFound)} out of {length(featureIdx)} given ", + "features were not found in the union of all features of ", + "used datasets: {.val {notFound}}") + ) } featureIdx <- featureIdx[featureIdx %in% allGenes] - if (length(featureIdx) == 0) - stop("No feature can be retrieved") + if (length(featureIdx) == 0) cli::cli_abort("No feature can be retrieved") } # Subset each involved dataset and create new liger object datasets.new <- list() for (d in useDatasets) { - if (isTRUE(verbose)) .log("Subsetting dataset: ", d) + if (isTRUE(verbose)) cli::cli_process_start("Subsetting dataset: {.val {d}}") ld <- dataset(object, d) featureIdxDataset <- featureIdx if (isFALSE(returnObject)) @@ -194,16 +197,19 @@ retrieveCellFeature <- function( value <- data.frame(value, row.names = colnames(ld)) colnames(value) <- feature if (!inherits(ld, "ligerATACDataset")) { - warning("Dataset ", d, " is not of ATAC modality, returning ", - "NAs for cells belonging to this dataset.", - immediate. = TRUE) + cli::cli_alert_warning( + c("Dataset {.val {d}} is not of ATAC modality, returning ", + "NAs for cells belonging to this dataset") + ) return(value) } else { peak <- methods::slot(ld, slot) if (any(!feature %in% rownames(peak))) { nf <- feature[!feature %in% rownames(peak)] - warning("Specified feature not found in dataset ", d, - ", returning NAs.", immediate. = TRUE) + cli::cli_alert_warning( + c("Specified features are not found in dataset ", + "{.val {d}}, returning NAs.") + ) feature <- feature[feature %in% rownames(peak)] } value[,feature] <- peak[feature, ] @@ -247,7 +253,7 @@ retrieveCellFeature <- function( #' @param chunkSize Integer. Number of maximum number of cells in each chunk, #' Default \code{1000}. #' @param verbose Logical. Whether to show information of the progress. Default -#' \code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set. +#' \code{getOption("ligerVerbose")} or \code{TRUE} if users have not set. #' @param returnObject Logical, whether to return a \linkS4class{ligerDataset} #' object for result. Default \code{TRUE}. \code{FALSE} returns a list #' containing requested values. @@ -268,7 +274,7 @@ subsetLigerDataset <- function( filename = NULL, filenameSuffix = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), returnObject = TRUE, ... ) { @@ -295,13 +301,15 @@ subsetH5LigerDataset <- function( filename = NULL, filenameSuffix = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), returnObject = TRUE ) { if (isTRUE(newH5)) { if (isFALSE(returnObject)) - warning("Cannot set `returnObject = FALSE` when subsetting", - "H5 based ligerDataset to new H5 file.") + cli::cli_alert_danger( + c("Cannot set {.code returnObject = FALSE} when subsetting H5 based {.cls ligerDataset} to new H5 file.", + "i" = "Will return subset to new object.") + ) if (is.null(filename) && is.null(filenameSuffix)) { oldFN <- h5fileInfo(object, "filename") bn <- basename(oldFN) @@ -323,7 +331,10 @@ subsetH5LigerDataset <- function( useSlot = useSlot, chunkSize = chunkSize, verbose = verbose ) }, error=function(e) { - message('An error occurred during subseting from H5 to H5.') + cli::cli_alert_danger( + "An error occurred during subseting from H5 to H5." + ) + cli::cli_alert_warning("The new H5 file will be removed.") unlink(filename) stop(e) } @@ -345,14 +356,16 @@ subsetH5LigerDatasetToMem <- function( useSlot = NULL, returnObject = TRUE, chunkSize = 1000, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) { if (!inherits(object, "ligerDataset")) { - warning("`object` is not a ligerDataset obejct. Nothing to be done.") + cli::cli_alert_danger("{.var object} is not a {.cls ligerDataset} object.") + cli::cli_alert_info("Nothing to be done.") return(object) } if (!isH5Liger(object)) { - warning("`object` is not HDF5 based. Nothing to be done.") + cli::cli_alert_info("{.var object} is not HDF5 based.") + cli::cli_alert_info("Nothing to be done.") return(object) } modal <- modalOf(object) @@ -364,7 +377,8 @@ subsetH5LigerDatasetToMem <- function( value <- list() # Process rawData #### if ("rawData" %in% slotInvolved & !is.null(rawData(object))) { - if (isTRUE(verbose)) .log("Subsetting `rawData`", level = 2) + if (isTRUE(verbose)) + cli::cli_process_start("... Subsetting {.field rawData}") rawData <- H5Apply( object, init = NULL, useData = "rawData", chunkSize = chunkSize, verbose = verbose, @@ -376,11 +390,13 @@ subsetH5LigerDatasetToMem <- function( rownames(rawData) <- rownames(object)[featureIdx] colnames(rawData) <- colnames(object)[cellIdx] value$rawData <- rawData + if (isTRUE(verbose)) cli::cli_process_done() } # Process normData #### if ("normData" %in% slotInvolved & !is.null(normData(object))) { - if (isTRUE(verbose)) .log("Subsetting `normData`", level = 2) + if (isTRUE(verbose)) + cli::cli_process_start("... Subsetting {.field normData}") normData <- H5Apply( object, init = NULL, useData = "normData", chunkSize = chunkSize, verbose = verbose, @@ -392,6 +408,7 @@ subsetH5LigerDatasetToMem <- function( rownames(normData) <- rownames(object)[featureIdx] colnames(normData) <- colnames(object)[cellIdx] value$normData <- normData + if (isTRUE(verbose)) cli::cli_process_done() } # Process scaled data #### @@ -402,7 +419,8 @@ subsetH5LigerDatasetToMem <- function( secondIdx <- as.numeric(stats::na.omit(match(featureIdx, scaledFeatureIdx))) } if ("scaleData" %in% slotInvolved & !is.null(scaleData(object))) { - if (isTRUE(verbose)) .log("Subsetting `scaleData`", level = 2) + if (isTRUE(verbose)) + cli::cli_process_start("... Subsetting {.field scaleData}") # scaledFeatureIdx <- NULL # if (getH5File(object)$exists("scaleData.featureIdx")) { # scaledFeatureIdx <- getH5File(object)[["scaleData.featureIdx"]][] @@ -463,6 +481,7 @@ subsetH5LigerDatasetToMem <- function( # } # } value$scaleData <- scaleDataSubset + if (isTRUE(verbose)) cli::cli_process_done() } # `NULL[idx1, idx2]` returns `NULL` # V: k x genes @@ -500,15 +519,17 @@ subsetH5LigerDatasetToH5 <- function( filename = NULL, filenameSuffix = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) { # Input checks #### if (!inherits(object, "ligerDataset")) { - warning("`object` is not a ligerDataset obejct. Nothing to be done.") + cli::cli_alert_danger("{.var object} is not a {.cls ligerDataset} object.") + cli::cli_alert_info("Nothing to be done.") return(object) } if (!isH5Liger(object)) { - warning("`object` is not HDF5 based. Nothing to be done.") + cli::cli_alert_info("{.var object} is not HDF5 based.") + cli::cli_alert_info("Nothing to be done.") return(object) } modal <- modalOf(object) @@ -522,7 +543,7 @@ subsetH5LigerDatasetToH5 <- function( } else { newH5File <- hdf5r::H5File$new(filename, mode = "w") } - if (isTRUE(verbose)) .log("New H5 file at: ", filename) + if (isTRUE(verbose)) cli::cli_alert_info("New H5 file at: {.file {filename}}") newH5Meta <- h5fileInfo(object) newH5Meta$H5File <- newH5File newH5Meta$filename <- filename @@ -532,6 +553,10 @@ subsetH5LigerDatasetToH5 <- function( newH5File[[newH5Meta$barcodesName]][1:length(cellIdx)] <- colnames(object)[cellIdx] } else { + cli::cli_abort( + c("AnnData (H5AD) format not supported yet.", + "i" = "Please submit an issue on GitHub if this is highly desired.") + ) # TODO: AnnData style barcodes storage. } @@ -542,7 +567,8 @@ subsetH5LigerDatasetToH5 <- function( # Process Raw Data #### if ("rawData" %in% useSlot & !is.null(rawData(object))) { # 1. Create paths to store i, p, x of sparse matrix - if (isTRUE(verbose)) .log("Subsetting `rawData`", level = 2) + if (isTRUE(verbose)) + cli::cli_process_start("... Subsetting {.field rawData}") safeH5Create(newH5File, newH5Meta$indicesName, dims = 1, chunkSize = 4096, dtype = "int") i.h5d <- newH5File[[newH5Meta$indicesName]] @@ -590,10 +616,12 @@ subsetH5LigerDatasetToH5 <- function( return(values) } ) + if (isTRUE(verbose)) cli::cli_process_done() } # Process Normalized Data #### if ("normData" %in% useSlot & !is.null(normData(object))) { - if (isTRUE(verbose)) .log("Subsetting `normData`", level = 2) + if (isTRUE(verbose)) + cli::cli_process_start("... Subsetting {.field normData}") safeH5Create(newH5File, newH5Meta$normData, dims = 1, chunkSize = 4096, dtype = "double") x.h5d <- newH5File[[newH5Meta$normData]] @@ -636,6 +664,7 @@ subsetH5LigerDatasetToH5 <- function( return(values) } ) + if (isTRUE(verbose)) cli::cli_process_done() } # Process Scaled Data #### secondIdx <- NULL @@ -663,8 +692,7 @@ subsetH5LigerDatasetToH5 <- function( if ("scaleData" %in% useSlot & !is.null(scaleData(object))) { scaledFeatureIdxNew <- which(featureIdx %in% scaledFeatureIdx) if (isTRUE(verbose)) - .log(length(secondIdx), - " features used in scaleData were selected. ", level = 3) + cli::cli_process_start("... Subsetting {.field scaleData}") newH5File$create_group(newH5Meta$scaleData) safeH5Create( newH5File, @@ -752,6 +780,12 @@ subsetH5LigerDatasetToH5 <- function( # "feature selection. Unable to subset from H5.") # } # } + if (isTRUE(verbose)) { + cli::cli_process_done() + cli::cli_alert_info( + "...... {length(secondIdx)} features used in {.field scaleData} were selected." + ) + } } newH5File$close() if (!"rawData" %in% useSlot) newH5Meta$rawData <- NULL @@ -788,11 +822,12 @@ subsetH5LigerDatasetToH5 <- function( subsetMemLigerDataset <- function(object, featureIdx = NULL, cellIdx = NULL, useSlot = NULL, returnObject = TRUE) { if (!inherits(object, "ligerDataset")) { - warning("`object` is not a ligerDataset obejct. Nothing to be done.") + cli::cli_alert_danger("{.var object} is not a {.cls ligerDataset} object.") + cli::cli_alert_info("Nothing to be done.") return(object) } if (isH5Liger(object)) { - stop("`object` is HDF5 based. Use `subsetH5LigerDataset()` instead.") + cli::cli_abort("{.var object} is HDF5 based. Use {.fn subsetH5LigerDataset} instead.") } if (is.null(cellIdx) && is.null(featureIdx)) return(object) modal <- modalOf(object) @@ -858,18 +893,18 @@ subsetMemLigerDataset <- function(object, featureIdx = NULL, cellIdx = NULL, else return(subsetData) } -.getOrderedSubsetIdx <- function(allNames, subsetNames) { - # subsetNames must be real subset, but can be in a different order from - # original allNames - - # Label the order of original allNames - idx <- seq_along(allNames) - names(idx) <- allNames - # Subscribe with named vector, so the value (label for original order) get - # ordered by subscription - subsetIdx <- idx[subsetNames] - subsetIdx <- subsetIdx[!is.na(subsetIdx)] - names(subsetIdx) <- NULL - subsetIdx -} +# .getOrderedSubsetIdx <- function(allNames, subsetNames) { +# # subsetNames must be real subset, but can be in a different order from +# # original allNames +# +# # Label the order of original allNames +# idx <- seq_along(allNames) +# names(idx) <- allNames +# # Subscribe with named vector, so the value (label for original order) get +# # ordered by subscription +# subsetIdx <- idx[subsetNames] +# subsetIdx <- subsetIdx[!is.na(subsetIdx)] +# names(subsetIdx) <- NULL +# subsetIdx +# } diff --git a/R/util.R b/R/util.R index 7eb82c43..1b1457d0 100644 --- a/R/util.R +++ b/R/util.R @@ -9,11 +9,15 @@ message(pref, msg) } +cli_or <- function(x) cli::cli_vec(x, list("vec-last" = " or ")) + .checkObjVersion <- function(object) { if (inherits(object, "liger")) { if (!is.newLiger(object)) - stop("Old version of liger object detected. Please update the ", - "object with command:\nobject <- convertOldLiger(object)") + cli::cli_abort( + "Old version of liger object is detected. Please run: + {.code object <- convertOldLiger(object)}" + ) } } @@ -31,7 +35,7 @@ .checkUseDatasets <- function(object, useDatasets = NULL, modal = NULL) { if (!inherits(object, "liger")) - stop("A liger object is required.") + cli::cli_abort("A liger object is required.") if (is.null(useDatasets)) { if (is.null(modal)) useDatasets <- names(object) else { @@ -43,35 +47,41 @@ } else { if (is.numeric(useDatasets)) { if (max(useDatasets) > length(object)) { - stop("Numeric dataset index out of bound. Only ", - length(object), " datasets exist.") + cli::cli_abort( + "Numeric dataset index out of bound. Only {length(object)} + dataset{?s} exist.") } useDatasets <- unique(useDatasets) useDatasets <- names(object)[useDatasets] } else if (is.logical(useDatasets)) { if (length(useDatasets) != length(object)) { - stop("Logical dataset subscription does not match the number ", - "of datasets (", length(object), ").") + cli::cli_abort( + "Logical dataset subscription does not match the number + of datasets ({length(object)}).") } useDatasets <- names(object)[useDatasets] } else if (is.character(useDatasets)) { if (any(!useDatasets %in% names(object))) { - notFound <- useDatasets[!useDatasets %in% names(object)] - stop("Specified dataset name(s) not found: ", - paste(notFound, collapse = ", ")) + cli::cli_abort( + "Specified dataset name(s) not found: + {.val {useDatasets[!useDatasets %in% names(object)]}}" + ) } } else { - stop("Please use a proper numeric/logical/character vector to ", - "select dataset to use.") + cli::cli_abort( + "Please use a proper numeric/logical/character vector to + select dataset to use.") } if (!is.null(modal)) { passing <- sapply(useDatasets, function(d) { inherits(dataset(object, d), .modalClassDict[[modal]]) }) if (!all(passing)) - stop("Not all specified datasets are of `", - .modalClassDict[[modal]], "` class: ", - paste(useDatasets[!passing], collapse = ", ")) + cli::cli_abort( + "Not all specified datasets are of + {(.modalClassDict[[modal]])} class: + {.val {useDatasets[!passing]}}" + ) } } useDatasets @@ -88,8 +98,8 @@ droplevels = TRUE, returnList = FALSE ) { - df <- cellMeta(object, columns = variables, cellIdx = cellIdx, as.data.frame = TRUE, - drop = FALSE) + df <- cellMeta(object, columns = variables, cellIdx = cellIdx, + as.data.frame = TRUE, drop = FALSE) if (isTRUE(checkCategorical)) { passing <- sapply(variables, function(v) { vec <- df[[v]] @@ -97,25 +107,27 @@ if (is.factor(vec)) return(TRUE) if (is.character(vec)) { if (length(unique(vec)) > 50) - warning("Categorical variable selection `", v, - "' has more than 100 unique values.", - immediate. = TRUE) + cli::cli_alert_warning( + "Categorical variable selection `{v}` has more than 50 unique values." + ) return(TRUE) } if (is.numeric(vec)) { if (length(unique(vec)) > 50) - warning("Categorical variable selection `", v, - "` has more than 100 unique values.", - immediate. = TRUE) + cli::cli_alert_warning( + "Categorical variable selection `{v}` has more than 50 unique values." + ) return(FALSE) } }) + passing <- unlist(passing) if (!all(passing)) { - notPassed <- variables[!passing] - stop("The following selected variables are not considered as ", - "categorical. Please use something else or try converting ", - "them to factor class to force passing checks.\n", - paste(notPassed, collapse = ", ")) + cli::cli_abort( + "The following selected variables are not considered as + categorical. Please use something else or try converting + them to factor class to force passing checks. + {.val {variables[!passing]}}" + ) } } for (v in colnames(df)) { @@ -150,7 +162,9 @@ if (any(!idx %in% getNames(object))) { notFound <- paste(idx[!idx %in% getNames(object)], collapse = ", ") - stop(paramName, " not found in object: ", notFound) + cli::cli_abort( + "{paramName} not found in object: {notFound}" + ) } name <- seq(getNumber(object)) names(name) <- getNames(object) @@ -158,47 +172,47 @@ names(idx) <- NULL } else if (is.logical(idx)) { if (length(idx) != getNumber(object)) { - stop("Length of logical ", paramName, " does not match to ", - "number of ", orient, "s in `object`.") + cli::cli_abort( + "Length of logical {paramName} does not match to number of {orient}s in `object`." + ) } idx <- which(idx) } else if (is.numeric(idx)) { if (max(idx) > getNumber(object) || min(idx) < 1) { - stop("Numeric ", paramName, " subscription out of bound.") + cli::cli_abort( + "Numeric {paramName} subscription out of bound." + ) } } else if (is.null(idx)) { idx <- seq(getNumber(object)) } else { - stop("Please use character, logical or numeric subscription ", - "for ", paramName, ".") + cli::cli_abort( + "Please use character, logical or numeric subscription for {paramName}." + ) } return(idx) } .checkLDSlot <- function(object, slot) { - if (!inherits(object, "ligerDataset")) - stop("Please use a ligerDataset object.") avail <- c("rawData", "normData", "scaleData") if (is.null(slot)) { slot <- avail } else { if (any(!slot %in% avail)) { notFound <- slot[!slot %in% avail] - stop("Specified slot not availalble: ", - paste(notFound, collapse = ", "), ". Use one or more from ", - '"rawData", "normData" or "scaleData"') + cli::cli_abort( + "Specified slot not availalble: {.val {notFound}}. + Use one or more from \"rawData\", \"normData\" or \"scaleData\"" + ) } - if ("rawData" %in% slot && - is.null(rawData(object))) { - stop("`rawData` is not available for use.") + if ("rawData" %in% slot && is.null(rawData(object))) { + cli::cli_abort("`rawData` is not available for use.") } - if ("normData" %in% slot && - is.null(normData(object))) { - stop("`normData` is not available for use.") + if ("normData" %in% slot && is.null(normData(object))) { + cli::cli_abort("`normData` is not available for use.") } - if ("scaleData" %in% slot && - is.null(scaleData(object))) { - stop("`scaleData` is not available for use.") + if ("scaleData" %in% slot && is.null(scaleData(object))) { + cli::cli_abort("`scaleData` is not available for use.") } } slot @@ -215,26 +229,27 @@ if (type == "V") checklist <- c(1, 1, 1, 0, 0) if (checklist[1]) { if (!is.list(m)) - stop("`", type, ".init` should be a list of matrices") + cli::cli_abort( + "{.var {type}Init} should be a list of {.cls matrix}." + ) if (length(m) != length(nCells)) - stop("Number of matrices in `", type, ".init` should match number", - " of datasets in `object`") + cli::cli_abort( + "Number of matrices in {.var {type}Init} should match number of datasets in {.var object}." + ) isMat <- sapply(m, is.matrix) if (!all(isMat)) { - stop(sum(!isMat), " elements in `", type, ".init` is not a matrix.") + cli::cli_abort("{sum(!isMat)} elements in {.var {type}Init} is not {.cls matrix}.") } isValid <- sapply(seq_along(m), function(i) .checkInit.mat(m[[i]], nCells[i], nGenes, k, checklist)) if (!all(isValid)) - stop("Not all matrices in `", type, - ".init` has valid dimensionality.") + cli::cli_abort("Not all matrices in {.var {type}Init} has valid dimensionality.") } else { if (!is.matrix(m)) - stop("`", type, ".init` should be a matrix") + cli::cli_abort("{.var {type}Init} should be a {.cls matrix}.") if (!.checkInit.mat(m, sum(nCells), nGenes, k, checklist)) - stop("`", type, ".init` does not have valid dimensionality.") + cli::cli_abort("{.var {type}Init} does not have valid dimensionality.") } - m } .checkInit.mat <- function(m, nCells, nGenes, k, checklist) { @@ -249,7 +264,7 @@ checkV = TRUE) { result <- TRUE useDatasets <- .checkUseDatasets(object, useDatasets) - if (is.null(object@W)) stop("W matrix does not exist.") + if (is.null(object@W)) cli::cli_abort("W matrix does not exist.") k <- ncol(object@W) for (d in useDatasets) { @@ -257,32 +272,38 @@ nCells <- ncol(ld) if (isTRUE(checkV)) { if (is.null(ld@V)) { - warning("V matrix does not exist for dataset '", d, "'.") + cli::cli_alert_danger("V matrix does not exist for dataset {.val {d}}.") result <- FALSE } else { if (!identical(dim(ld@V), dim(object@W))) { - warning("Dimensionality of V matrix for dataset '", d, - "' does not match with W matrix.") + cli::cli_alert_danger( + "Dimensionality of V matrix for dataset {.val {d}} does not match with W matrix." + ) result <- FALSE } } } if (is.null(ld@H)) { - warning("H matrix does not exist for dataset '", d, "'.") + cli::cli_alert_danger("H matrix does not exist for dataset {.val {d}}.") result <- FALSE } else { if (!identical(dim(ld@H), c(k, nCells))) { - warning("Dimensionality of H matrix for dataset '", d, - "' is not valid") + cli::cli_alert_danger( + "Dimensionality of H matrix for dataset {.val {d}} is not valid." + ) result <- FALSE } } } if (k != object@uns$factorization$k) - warning("Number of factors does not match with object `k` slot. ") + cli::cli_alert_danger( + "Number of factors does not match with recorded parameter." + ) if (isFALSE(result)) - stop("Cannot detect valid existing factorization result. ", - "Please run factorization first. Check warnings.") + cli::cli_abort( + c(x = "Cannot detect valid existing factorization result. ", + i = "Please run factorization first. Check warnings.") + ) } # !!!MaintainerDeveloperNOTE: @@ -337,82 +358,107 @@ .checkRaster <- function(n, raster = NULL) { pkgAvail <- requireNamespace("scattermore", quietly = TRUE) if (!is.null(raster) && !is.logical(raster)) { - stop("Please use `NULL` or logical value for `raster`.") + cli::cli_abort("Please use `NULL` or logical value for `raster`.") } if (is.null(raster)) { # Automatically decide whether to rasterize depending on number of cells if (n > 1e5) { if (pkgAvail) { raster <- TRUE - .log("NOTE: Points are rasterized as number of cells/nuclei ", - "plotted exceeds 100,000.\n", - "Use `raster = FALSE` or `raster = TRUE` to force plot ", - "in vector form or not.") + cli::cli_alert_info( + "Points are rasterized since number of cells/nuclei exceeds + 100,000. + Use {.code raster = FALSE} or {.code raster = TRUE} to force + plot in vectorized form or not." + ) } else { raster <- FALSE warning("Number of cells/nuclei plotted exceeds 100,000. ", "Rasterizing the scatter plot is recommended but ", - "package \"scattermore\" is not available. ") + "package {.pkg scattermore} is not available. ") } } else { raster <- FALSE } } else if (isTRUE(raster)) { if (!pkgAvail) { - stop("Package \"scattermore\" needed for rasterizing the scatter ", - "plot. Please install it by command:\n", - "BiocManager::install('scattermore')", - call. = FALSE) + cli::cli_abort( + "Package {.pkg scattermore} is needed for rasterizing the scatter + plot. Please install it by command: + {.code BiocManager::install('scattermore')}" + ) } } return(raster) } .checkArgLen <- function(arg, n, repN = TRUE, class = NULL, .stop = TRUE) { + if (is.null(arg)) return(arg) argname <- deparse(substitute(arg)) - if (!is.null(arg)) { - if (length(arg) == 1 && isTRUE(repN)) { - arg <- rep(arg, n) - } - if (length(arg) != n) { - if (isTRUE(.stop)) - stop("`", argname, "` has to be a vector of length ", n) - else { - warning("`", argname, "` has to be a vector of length ", n) + if (length(arg) == 1 && isTRUE(repN)) { + arg <- rep(arg, n) + } + if (length(arg) != n) { + classTxt <- ifelse(is.null(class), "", " ") + if (isTRUE(.stop)) + if (!is.null(class)) { + cli::cli_abort( + c("{.var {argname}} has to be a length {ifelse(repN, paste0('1 or ', n), n)} object of class {.cls {class}}.", + "i" = "length: {length(arg)}; class: {.cls {class(arg)}}") + ) + } else { + cli::cli_abort( + c("{.var {argname}} has to be a length {ifelse(repN, paste0('1 or ', n), n)} object.", + "i" = "length: {length(arg)}; class: {.cls {class(arg)}}") + ) + } + else { + if (!is.null(class)) { + cli::cli_alert_warning( + c("{.var {argname}} has to be a length {ifelse(repN, paste0('1 or ', n), n)} object of class {.cls {class}}.", + i = "Using it anyway.") + ) + } else { + cli::cli_alert_warning( + c("{.var {argname}} has to be a length {ifelse(repN, paste0('1 or ', n), n)} object.", + i = "Using it anyway.") + ) } } } if (!is.null(class)) { allClassCheck <- sapply(class, function(x) methods::is(arg, x)) if (!any(allClassCheck)) { + class <- cli::cli_vec(class, list("vec-quote" )) if (isTRUE(.stop)) - stop("`", argname, "` has to be of class '", - paste(class, collapse = "', '"), "'") + cli::cli_abort( + c("{.var {argname}} has to be of class {.cls {class}}", + "i" = "Given class is {.cls {class(arg)}}") + ) else { - warning("`", argname, "` has to be of class '", - paste(class, collapse = "', '"), "'") + cli::cli_alert_warning( + c("{.var {argname}} has to be of class {.cls {class}}. Using it anyway.") + ) } } } return(arg) } -# Format "not found" string. When we need `need` elements from some source -# `from` format the string of ", " separeted list of not found elements. -.nfstr <- function(need, from) { - nf <- need[!need %in% from] - paste(nf, collapse = ", ") -} - - - - .getSeuratData <- function(object, layer, slot, assay = NULL) { if (!requireNamespace("Seurat", quietly = TRUE)) { - stop("Seurat package has to be installed in advance.") + cli::cli_abort( + "Package {.pkg Seurat} is needed for this function to work. + Please install it by command: + {.code install.packages('Seurat')}" + ) } if (!requireNamespace("SeuratObject", quietly = TRUE)) { - stop("SeuratObject package has to be installed in advance.") + cli::cli_abort( + "Package {.pkg SeuratObject} is needed for this function to work. + Please install it by command: + {.code install.packages('SeuratObject')}" + ) } assayObj <- Seurat::GetAssay(object, assay = assay) if (!"layers" %in% methods::slotNames(assayObj)) { @@ -422,7 +468,7 @@ if (utils::packageVersion("SeuratObject") >= package_version("4.9.9")) { layers <- SeuratObject::Layers(object, assay = assay, search = layer) if (length(layers) == 0) { - stop("Layer '", layer, "' not found in object.") + cli::cli_abort("Layer {.val {layer}} not found in object.") } else if (length(layers) == 1) { data <- SeuratObject::LayerData(object, assay = assay, layer = layers) } else { @@ -431,19 +477,28 @@ }) names(data) <- layers } - } else { + } else { # nocov start + cli::cli_alert_info("Using old Seurat package. Upgrade is recommended.") data <- SeuratObject::GetAssayData(object, assay = assay, slot = layer) - } + } # nocov end return(data) } .setSeuratData <- function(object, layer, save, slot, value, assay = NULL, denseIfNeeded = FALSE) { if (!requireNamespace("Seurat", quietly = TRUE)) { - stop("Seurat package has to be installed in advance.") + cli::cli_abort( + "Package {.pkg Seurat} is needed for this function to work. + Please install it by command: + {.emph install.packages('Seurat')}" + ) } if (!requireNamespace("SeuratObject", quietly = TRUE)) { - stop("SeuratObject package has to be installed in advance.") + cli::cli_abort( + "Package {.pkg SeuratObject} is needed for this function to work. + Please install it by command: + {.emph install.packages('SeuratObject')}" + ) } assayObj <- Seurat::GetAssay(object, assay = assay) if (!"layers" %in% methods::slotNames(assayObj)) { @@ -466,42 +521,14 @@ SeuratObject::LayerData(object, assay = assay, layer = save[i]) <- value[[layer[i]]] } } - } else { + } else { # nocov start + cli::cli_alert_info("Using old {.pkg Seurat} package. Upgrade is recommended.") object <- SeuratObject::SetAssayData(object, assay = assay, slot = save, new.data = value) - } + } # nocov end return(object) } -# plyr::mapvalues -mapvalues <- function(x, from, to, warn_missing = TRUE) { - if (length(from) != length(to)) { - stop("`from` and `to` vectors are not the same length.") - } - if (!is.atomic(x) && !is.null(x)) { - stop("`x` must be an atomic vector or NULL.") - } - - if (is.factor(x)) { - # If x is a factor, call self but operate on the levels - levels(x) <- mapvalues(levels(x), from, to, warn_missing) - return(x) - } - - mapidx <- match(x, from) - mapidxNA <- is.na(mapidx) - - # index of items in `from` that were found in `x` - from_found <- sort(unique(mapidx)) - if (warn_missing && length(from_found) != length(from)) { - message("The following `from` values were not present in `x`: ", - paste(from[!(1:length(from) %in% from_found) ], collapse = ", ")) - } - - x[!mapidxNA] <- to[mapidx[!mapidxNA]] - x -} - .DataFrame.as.data.frame <- function(x) { # Copied from Bioconductor package S4Vectors:::.as.data.frame.DataFrame @@ -554,8 +581,10 @@ searchH <- function(object, useRaw = NULL) { # If not found, look for raw H Ht <- Reduce(cbind, getMatrix(object, "H")) if (is.null(Ht)) { - stop("No cell factor loading available. ", - "Please run `runIntegration()` and `quantileNorm()` first.") + cli::cli_abort( + "No cell factor loading available. + Please run {.fn runIntegration} and {.fn quantileNorm} first." + ) } else { useRaw <- TRUE H <- t(Ht) @@ -567,17 +596,21 @@ searchH <- function(object, useRaw = NULL) { if (isTRUE(useRaw)) { Ht <- Reduce(cbind, getMatrix(object, "H")) if (is.null(Ht)) { - stop("Raw cell factor loading requested but not found. ", - "Please run `runIntegration()`.") + cli::cli_abort( + "Raw cell factor loading requested but not found. + Please run {.fn runIntegration}." + ) } else { H <- t(Ht) } } else { H <- getMatrix(object, "H.norm") if (is.null(H)) { - stop("Quantile-normalized cell factor loading requested but ", - "not found. Please run `quantileNorm()` after ", - "`runIntegration()`.") + cli::cli_abort( + "Quantile-normalized cell factor loading requested but + not found. Please run {.fn quantileNorm} after + {.fn runIntegration}." + ) } useRaw <- FALSE } diff --git a/R/visualization.R b/R/visualization.R index ecd8cdc8..37eb8e2c 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -50,7 +50,10 @@ plotClusterDimRed <- function( ...) { useDimRed <- useDimRed %||% object@uns$defaultDimRed if (is.null(useDimRed)) { - stop("No `useDimRed` given or default dimRed not set.") + cli::cli_abort( + c(x = "No {.var useDimRed} given or default dimRed not set.", + i = "Run {.fn runUMAP} or {.fn runTSNE} to create one. Or see {.fn dimRed} and {.fn defaultDimRed}.") + ) } useCluster <- useCluster %||% object@uns$defaultCluster xVar <- paste0(useDimRed, ".1") @@ -68,7 +71,10 @@ plotDatasetDimRed <- function( ...) { useDimRed <- useDimRed %||% object@uns$defaultDimRed if (is.null(useDimRed)) { - stop("No `useDimRed` given or default dimRed not set.") + cli::cli_abort( + c(x = "No {.var useDimRed} given or default dimRed not set.", + i = "Run {.fn runUMAP} or {.fn runTSNE} to create one. Or see {.fn dimRed} and {.fn defaultDimRed}.") + ) } xVar <- paste0(useDimRed, ".1") yVar <- paste0(useDimRed, ".2") @@ -433,8 +439,9 @@ plotProportionDot <- function( class1 <- class1 %||% object@uns$defaultCluster if (length(class1) != 1 || length(class2) != 1) - stop("`class1` and `class2` must be name of one categorical variable ", - "in `cellMeta` slot.") + cli::cli_abort( + "{.var class1} and {.var class2} must be name of one categorical variable in {.code cellMeta(object)}" + ) vars <- .fetchCellMetaVar(object, c(class1, class2), checkCategorical = TRUE) freq <- table(vars) @@ -471,8 +478,7 @@ plotProportionBar <- function( class1 <- class1 %||% object@uns$defaultCluster if (length(class1) != 1 || length(class2) != 1) - stop("`class1` and `class2` must be name of one categorical variable ", - "in `cellMeta` slot.") + cli::cli_abort("{.var class1} and {.var class2} must be name of one categorical variable in {.code cellMeta(object)}") method <- match.arg(method) vars <- .fetchCellMetaVar(object, c(class1, class2), checkCategorical = TRUE) @@ -622,6 +628,9 @@ plotProportionPie <- function( #' passed to \code{\link[EnhancedVolcano]{EnhancedVolcano}}. #' @return ggplot #' @export +#' @examples +#' result <- runMarkerDEG(pbmcPlot) +#' plotVolcano(result, 1) plotVolcano <- function( result, group, @@ -635,7 +644,10 @@ plotVolcano <- function( ... ) { if (!group %in% result$group) { - stop("Selected group does not exist in `result`.") + cli::cli_abort( + c("Selected group does not exist in {.code result$group}", + i = "Available ones: {.val {levels(droplevels(result$group))}}") + ) } result <- result[result$group == group, ] result <- result[order(abs(result$logFC), decreasing = TRUE), ] @@ -705,12 +717,12 @@ plotEnhancedVolcano <- function( group, ... ) { - if (!requireNamespace("EnhancedVolcano", quietly = TRUE)) { - stop("Package \"EnhancedVolcano\" needed for this function to work. ", - "Please install it by command:\n", - "BiocManager::install('EnhancedVolcano')", - call. = FALSE) - } + if (!requireNamespace("EnhancedVolcano", quietly = TRUE)) { # nocov start + cli::cli_abort( + "Package {.pkg EnhancedVolcano} is needed for this function to work. + Please install it by command: + {.code BiocManager::install('EnhancedVolcano')}") + } # nocov end result <- result[result$group == group, ] EnhancedVolcano::EnhancedVolcano( toptable = result, @@ -805,14 +817,12 @@ plotDensityDimRed <- function( } else { # Will return a single ggplot if (length(title) > 1) { - warning("`title` has length greater than 1 while only a single ", - "plot is generated. Using the first value only. ") + cli::cli_alert_warning("{.var title} has length greater than 1 while only a single plot is generated. Using the first value only.") title <- title[1] } drList <- list(dr) } plotList <- list() - #if (length(drList) == 0) stop("No plot could be generated") if (length(drList) == 1) { return(.ggDensity(drList[[1]], dotCoordDF = drList[[1]], title = title, minDensity = minDensity, @@ -1110,11 +1120,11 @@ plotSankey <- function( colorValues = scPalette, mar = c(2, 2, 4, 2) ) { - if (!requireNamespace("sankey", quietly = TRUE)) - stop("Package \"sankey\" needed for this function to work. ", - "Please install it by command:\n", - "install.packages('sankey')", - call. = FALSE) + if (!requireNamespace("sankey", quietly = TRUE)) # nocov start + cli::cli_abort( + "Package {.pkg sankey} is needed for this function to work. + Please install it by command: + {.code install.packages('sankey')}") # nocov end clusterConsensus <- clusterConsensus %||% object@uns$defaultCluster clusterDF <- .fetchCellMetaVar(object, @@ -1122,7 +1132,7 @@ plotSankey <- function( checkCategorical = TRUE, droplevels = TRUE) titles <- titles %||% c(cluster1, clusterConsensus, cluster2) - titles <- .checkArgLen(titles, 3, repN = FALSE) + titles <- .checkArgLen(titles, 3, repN = FALSE, class = "character") # Prepare for networkD3 input: Links, Nodes cluster1Fct <- droplevels(clusterDF[[1]]) clusterCFct <- droplevels(clusterDF[[2]]) @@ -1139,7 +1149,7 @@ plotSankey <- function( if (any(duplicated(c(nodes1, nodesC, nodes2)))) { prefixes <- prefixes %||% c(cluster1, clusterConsensus, cluster2) - prefixes <- .checkArgLen(prefixes, 3, repN = FALSE) + prefixes <- .checkArgLen(prefixes, 3, repN = FALSE, class = "character") nodes1 <- .addPrefix(prefixes[1], nodes1) nodesC <- .addPrefix(prefixes[2], nodesC) nodes2 <- .addPrefix(prefixes[3], nodes2) @@ -1240,7 +1250,7 @@ plotSpatial2D.liger <- function( ...) { dataset <- .checkUseDatasets(object, useDatasets = dataset, modal = "spatial") - .checkArgLen(dataset, 1) + .checkArgLen(dataset, 1, class = "character") ld <- dataset(object, dataset) useCluster <- useCluster %||% defaultCluster(object)[object$dataset == dataset] @@ -1248,7 +1258,7 @@ plotSpatial2D.liger <- function( legendColorTitle <- legendColorTitle %||% useCluster useCluster <- cellMeta(object, useCluster, useDatasets = dataset) } else { - useCluster <- .checkArgLen(useCluster, ncol(ld), repN = FALSE) + useCluster <- .checkArgLen(useCluster, ncol(ld), repN = FALSE, class = "factor") legendColorTitle <- legendColorTitle %||% "Annotation" } plotSpatial2D.ligerSpatialDataset( @@ -1275,11 +1285,11 @@ plotSpatial2D.ligerSpatialDataset <- function( labelText = FALSE, ...) { - .checkArgLen(useCluster, ncol(object)) + .checkArgLen(useCluster, ncol(object), repN = FALSE, class = "factor") legendColorTitle <- legendColorTitle %||% "Annotation" coord <- coordinate(object) - .checkArgLen(useDims, 2) + .checkArgLen(useDims, 2, repN = FALSE, class = "numeric") coord <- coord[, useDims] plotDF <- as.data.frame(coord) colnames(plotDF) <- c("x", "y") diff --git a/cleanup.ucrt b/cleanup.ucrt deleted file mode 100644 index 1b46a16b..00000000 --- a/cleanup.ucrt +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -rm -f src/*.o src/*.dll src/symbols.rds src/Makefile src/*.cmake src/CMakeCache.txt -rm -rf src/CMakefiles src/CPM_modules diff --git a/man/as.liger.Rd b/man/as.liger.Rd index 4a88ee72..2e0f991d 100644 --- a/man/as.liger.Rd +++ b/man/as.liger.Rd @@ -1,15 +1,13 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classConversion.R -\name{as.liger} -\alias{as.liger} +% Please edit documentation in R/classConversion.R, R/generics.R +\name{as.liger.dgCMatrix} \alias{as.liger.dgCMatrix} \alias{as.liger.SingleCellExperiment} \alias{as.liger.Seurat} \alias{seuratToLiger} +\alias{as.liger} \title{Converting other classes of data to a liger object} \usage{ -as.liger(object, ...) - \method{as.liger}{dgCMatrix}(object, datasetVar = NULL, modal = NULL, ...) \method{as.liger}{SingleCellExperiment}(object, datasetVar = NULL, modal = NULL, ...) @@ -17,12 +15,12 @@ as.liger(object, ...) \method{as.liger}{Seurat}(object, datasetVar = NULL, modal = NULL, assay = NULL, ...) seuratToLiger(object, datasetVar = NULL, modal = NULL, assay = NULL, ...) + +as.liger(object, ...) } \arguments{ \item{object}{Object.} -\item{...}{Additional arguments passed to \code{\link{createLiger}}} - \item{datasetVar}{Specify the dataset belonging by: 1. Select a variable from existing metadata in the object (e.g. colData column); 2. Specify a vector/factor that assign the dataset belonging. 3. Give a single character @@ -34,6 +32,8 @@ to find variable "sample" from SCE or "orig.ident" from Seurat.} \item{modal}{Modality setting for each dataset. See \code{\link{createLiger}}.} +\item{...}{Additional arguments passed to \code{\link{createLiger}}} + \item{assay}{Name of assay to use. Default \code{NULL} uses current active assay.} } @@ -64,6 +64,7 @@ multiSampleMatrix <- mergeSparseAll(matList) pbmc2 <- as.liger(multiSampleMatrix, datasetVar = pbmc$dataset) pbmc2 +\donttest{ sce <- SingleCellExperiment::SingleCellExperiment( assays = list(counts = multiSampleMatrix) ) @@ -86,3 +87,4 @@ seu5[["RNA"]] <- split(seu5[["RNA"]], pbmc$dataset) print(SeuratObject::Layers(seu5)) pbmc5 <- as.liger(seu5) } +} diff --git a/man/as.ligerDataset.Rd b/man/as.ligerDataset.Rd index 5115e4a5..001a1065 100644 --- a/man/as.ligerDataset.Rd +++ b/man/as.ligerDataset.Rd @@ -1,16 +1,14 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/classConversion.R -\name{as.ligerDataset} -\alias{as.ligerDataset} +% Please edit documentation in R/classConversion.R, R/generics.R +\name{as.ligerDataset.ligerDataset} \alias{as.ligerDataset.ligerDataset} \alias{as.ligerDataset.default} \alias{as.ligerDataset.matrix} \alias{as.ligerDataset.Seurat} \alias{as.ligerDataset.SingleCellExperiment} +\alias{as.ligerDataset} \title{Converting other classes of data to a as.ligerDataset object} \usage{ -as.ligerDataset(object, ...) - \method{as.ligerDataset}{ligerDataset}( object, modal = c("default", "rna", "atac", "spatial", "meth"), @@ -41,15 +39,17 @@ as.ligerDataset(object, ...) modal = c("default", "rna", "atac", "spatial", "meth"), ... ) + +as.ligerDataset(object, ...) } \arguments{ \item{object}{Object.} -\item{...}{Additional arguments passed to \code{\link{createLigerDataset}}} - \item{modal}{Modality setting for each dataset. Choose from \code{"default"}, \code{"rna"}, \code{"atac"}, \code{"spatial"}, \code{"meth"}.} +\item{...}{Additional arguments passed to \code{\link{createLigerDataset}}} + \item{assay}{Name of assay to use. Default \code{NULL} uses current active assay.} } diff --git a/man/commandDiff.Rd b/man/commandDiff.Rd index a600d46d..ce32172d 100644 --- a/man/commandDiff.Rd +++ b/man/commandDiff.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerCommand-class.R +% Please edit documentation in R/ligerCommand_relates.R \name{commandDiff} \alias{commandDiff} \title{Check difference of two liger command} diff --git a/man/coordinate.Rd b/man/coordinate.Rd index 764f8973..95b6f6dd 100644 --- a/man/coordinate.Rd +++ b/man/coordinate.Rd @@ -1,5 +1,6 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset_subclasses.R +% Please edit documentation in R/generics.R, R/liger-methods.R, +% R/ligerDataset_subclass-methods.R \name{coordinate} \alias{coordinate} \alias{coordinate<-} diff --git a/man/isH5Liger.Rd b/man/isH5Liger.Rd index 56250ca8..1d65bba1 100644 --- a/man/isH5Liger.Rd +++ b/man/isH5Liger.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset-class.R +% Please edit documentation in R/ligerDataset-methods.R \name{isH5Liger} \alias{isH5Liger} \title{Check if a liger or ligerDataset object is made of HDF5 file} diff --git a/man/liger-class.Rd b/man/liger-class.Rd index 8b9b72a6..bbe04b52 100644 --- a/man/liger-class.Rd +++ b/man/liger-class.Rd @@ -1,24 +1,34 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/liger-class.R, R/ligerCommand-class.R, -% R/ligerDataset-class.R +% Please edit documentation in R/classes.R, R/generics.R, R/liger-methods.R, +% R/ligerDataset-methods.R \docType{class} \name{liger-class} \alias{liger-class} \alias{liger} +\alias{datasets} +\alias{datasets<-} +\alias{dataset} +\alias{dataset<-} +\alias{cellMeta} +\alias{cellMeta<-} +\alias{defaultCluster} +\alias{defaultCluster<-} +\alias{dimRed} +\alias{dimRed<-} +\alias{defaultDimRed} +\alias{defaultDimRed<-} +\alias{varFeatures} +\alias{varFeatures<-} +\alias{varUnsharedFeatures} +\alias{varUnsharedFeatures<-} +\alias{commands} \alias{show,liger-method} \alias{dim,liger-method} \alias{dimnames,liger-method} \alias{dimnames<-,liger,list-method} -\alias{[,liger,character,missing,ANY-method} -\alias{[,liger,missing,index,ANY-method} -\alias{[,liger,character,index,ANY-method} -\alias{datasets} -\alias{datasets<-} \alias{datasets,liger-method} \alias{datasets<-,liger,logical-method} \alias{datasets<-,liger,missing-method} -\alias{dataset} -\alias{dataset<-} \alias{dataset,liger,character_OR_NULL-method} \alias{dataset,liger,missing-method} \alias{dataset,liger,numeric-method} @@ -28,81 +38,106 @@ \alias{names,liger-method} \alias{names<-,liger,character-method} \alias{length,liger-method} -\alias{cellMeta} -\alias{cellMeta<-} \alias{cellMeta,liger,NULL-method} \alias{cellMeta,liger,character-method} \alias{cellMeta,liger,missing-method} \alias{cellMeta<-,liger,missing-method} \alias{cellMeta<-,liger,character-method} +\alias{rawData,liger-method} +\alias{rawData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} +\alias{rawData<-,liger,ANY,ANY,H5D-method} +\alias{normData,liger-method} +\alias{normData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} +\alias{normData<-,liger,ANY,ANY,H5D-method} +\alias{scaleData,liger,ANY-method} +\alias{scaleData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} +\alias{scaleData<-,liger,ANY,ANY,H5D-method} +\alias{scaleData<-,liger,ANY,ANY,H5Group-method} +\alias{scaleUnsharedData,liger,character-method} +\alias{scaleUnsharedData,liger,numeric-method} +\alias{scaleUnsharedData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} +\alias{scaleUnsharedData<-,liger,ANY,ANY,H5D-method} +\alias{scaleUnsharedData<-,liger,ANY,ANY,H5Group-method} +\alias{getMatrix,liger,ANY,ANY,ANY-method} +\alias{getH5File,liger,ANY-method} \alias{$,liger-method} \alias{$<-,liger-method} -\alias{defaultCluster} -\alias{defaultCluster<-} \alias{defaultCluster,liger-method} \alias{defaultCluster<-,liger,ANY,ANY,character-method} \alias{defaultCluster<-,liger,ANY,ANY,factor-method} \alias{defaultCluster<-,liger,ANY,ANY,NULL-method} -\alias{dimRed} -\alias{dimRed<-} \alias{dimRed,liger,missing-method} \alias{dimRed,liger,character-method} \alias{dimRed<-,liger,character,ANY,matrixLike-method} -\alias{defaultDimRed} -\alias{defaultDimRed<-} \alias{defaultDimRed,liger-method} \alias{defaultDimRed<-,liger,missing,ANY,character-method} \alias{defaultDimRed<-,liger,character,ANY,matrixLike-method} -\alias{varFeatures} -\alias{varFeatures<-} \alias{varFeatures,liger-method} \alias{varFeatures<-,liger,ANY,character-method} -\alias{varUnsharedFeatures} -\alias{varUnsharedFeatures<-} \alias{varUnsharedFeatures,liger,ANY-method} -\alias{varUnsharedFeatures,ligerDataset,missing-method} \alias{varUnsharedFeatures<-,liger,ANY,ANY,character-method} -\alias{varUnsharedFeatures<-,ligerDataset,missing,ANY,character-method} \alias{fortify.liger} \alias{c.liger} -\alias{commands} \alias{commands,liger-method} -\alias{rawData,liger-method} -\alias{rawData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} -\alias{rawData<-,liger,ANY,ANY,H5D-method} -\alias{normData,liger-method} -\alias{normData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} -\alias{normData<-,liger,ANY,ANY,H5D-method} -\alias{scaleData,liger,ANY-method} -\alias{scaleData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} -\alias{scaleData<-,liger,ANY,ANY,H5D-method} -\alias{scaleData<-,liger,ANY,ANY,H5Group-method} -\alias{scaleUnsharedData,liger,character-method} -\alias{scaleUnsharedData,liger,numeric-method} -\alias{scaleUnsharedData<-,liger,ANY,ANY,matrixLike_OR_NULL-method} -\alias{scaleUnsharedData<-,liger,ANY,ANY,H5D-method} -\alias{scaleUnsharedData<-,liger,ANY,ANY,H5Group-method} -\alias{getMatrix,liger,ANY,ANY,ANY-method} -\alias{getH5File,liger,ANY-method} +\alias{varUnsharedFeatures,ligerDataset,missing-method} +\alias{varUnsharedFeatures<-,ligerDataset,missing,ANY,character-method} \title{liger class} \usage{ -\S4method{show}{liger}(object) +datasets(x, check = NULL) -\S4method{dim}{liger}(x) +datasets(x, check = TRUE) <- value -\S4method{dimnames}{liger}(x) +dataset(x, dataset = NULL) -\S4method{dimnames}{liger,list}(x) <- value +dataset(x, dataset, type = NULL, qc = TRUE) <- value + +cellMeta( + x, + columns = NULL, + useDatasets = NULL, + cellIdx = NULL, + as.data.frame = FALSE, + ... +) -\S4method{[}{liger,character,missing,ANY}(x, i, j, ..., drop = TRUE) +cellMeta( + x, + columns = NULL, + useDatasets = NULL, + cellIdx = NULL, + inplace = FALSE, + check = FALSE +) <- value -\S4method{[}{liger,missing,index,ANY}(x, i, j, ..., drop = TRUE) +defaultCluster(x, useDatasets = NULL, ...) -\S4method{[}{liger,character,index,ANY}(x, i, j, ..., drop = TRUE) +defaultCluster(x, name = NULL, useDatasets = NULL, ...) <- value -datasets(x, check = NULL) +dimRed(x, name = NULL, useDatasets = NULL, ...) -datasets(x, check = TRUE) <- value +dimRed(x, name = NULL, useDatasets = NULL, ...) <- value + +defaultDimRed(x, useDatasets = NULL) + +defaultDimRed(x, name, useDatasets = NULL) <- value + +varFeatures(x) + +varFeatures(x, check = TRUE) <- value + +varUnsharedFeatures(x, dataset = NULL) + +varUnsharedFeatures(x, dataset, check = TRUE) <- value + +commands(x, funcName = NULL, arg = NULL) + +\S4method{show}{liger}(object) + +\S4method{dim}{liger}(x) + +\S4method{dimnames}{liger}(x) + +\S4method{dimnames}{liger,list}(x) <- value \S4method{datasets}{liger}(x, check = NULL) @@ -110,10 +145,6 @@ datasets(x, check = TRUE) <- value \S4method{datasets}{liger,missing}(x, check = TRUE) <- value -dataset(x, dataset = NULL) - -dataset(x, dataset, type = NULL, qc = TRUE) <- value - \S4method{dataset}{liger,character_OR_NULL}(x, dataset = NULL) \S4method{dataset}{liger,missing}(x, dataset = NULL) @@ -132,17 +163,6 @@ dataset(x, dataset, type = NULL, qc = TRUE) <- value \S4method{length}{liger}(x) -cellMeta( - x, - columns = NULL, - useDatasets = NULL, - cellIdx = NULL, - as.data.frame = FALSE, - ... -) - -cellMeta(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, check = FALSE) <- value - \S4method{cellMeta}{liger,NULL}( x, columns = NULL, @@ -172,71 +192,14 @@ cellMeta(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, check = FALSE) < \S4method{cellMeta}{liger,missing}(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, check = FALSE) <- value -\S4method{cellMeta}{liger,character}(x, columns = NULL, useDatasets = NULL, cellIdx = NULL, check = FALSE) <- value - -\S4method{$}{liger}(x, name) - -\S4method{$}{liger}(x, name) <- value - -defaultCluster(x, useDatasets = NULL, ...) - -defaultCluster(x, name = NULL, useDatasets = NULL, ...) <- value - -\S4method{defaultCluster}{liger}(x, useDatasets = NULL, droplevels = FALSE, ...) - -\S4method{defaultCluster}{liger,ANY,ANY,character}(x, name = NULL, useDatasets = NULL, ...) <- value - -\S4method{defaultCluster}{liger,ANY,ANY,factor}(x, name = NULL, useDatasets = NULL, droplevels = TRUE, ...) <- value - -\S4method{defaultCluster}{liger,ANY,ANY,NULL}(x, name = NULL, useDatasets = NULL, ...) <- value - -dimRed(x, name = NULL, useDatasets = NULL, ...) - -dimRed(x, name = NULL, useDatasets = NULL, ...) <- value - -\S4method{dimRed}{liger,missing}(x, name = NULL, useDatasets = NULL, ...) - -\S4method{dimRed}{liger,character}(x, name = NULL, useDatasets = NULL, ...) - -\S4method{dimRed}{liger,character,ANY,matrixLike}(x, name = NULL, useDatasets = NULL, asDefault = NULL, ...) <- value - -defaultDimRed(x, useDatasets = NULL) - -defaultDimRed(x, name, useDatasets = NULL) <- value - -\S4method{defaultDimRed}{liger}(x, useDatasets = NULL) - -\S4method{defaultDimRed}{liger,missing,ANY,character}(x, name = NULL, useDatasets = NULL) <- value - -\S4method{defaultDimRed}{liger,character,ANY,matrixLike}(x, name, useDatasets = NULL) <- value - -varFeatures(x) - -varFeatures(x, check = TRUE) <- value - -\S4method{varFeatures}{liger}(x) - -\S4method{varFeatures}{liger,ANY,character}(x, check = TRUE) <- value - -varUnsharedFeatures(x, dataset = NULL) - -varUnsharedFeatures(x, dataset, check = TRUE) <- value - -\S4method{varUnsharedFeatures}{liger,ANY}(x, dataset = NULL) - -\S4method{varUnsharedFeatures}{ligerDataset,missing}(x, dataset = NULL) - -\S4method{varUnsharedFeatures}{liger,ANY,ANY,character}(x, dataset, check = TRUE) <- value - -\S4method{varUnsharedFeatures}{ligerDataset,missing,ANY,character}(x, dataset = NULL, check = TRUE) <- value - -\method{fortify}{liger}(model, data, ...) - -\method{c}{liger}(...) - -commands(x, funcName = NULL, arg = NULL) - -\S4method{commands}{liger}(x, funcName = NULL, arg = NULL) +\S4method{cellMeta}{liger,character}( + x, + columns = NULL, + useDatasets = NULL, + cellIdx = NULL, + inplace = TRUE, + check = FALSE +) <- value \S4method{rawData}{liger}(x, dataset = NULL) @@ -277,23 +240,57 @@ commands(x, funcName = NULL, arg = NULL) ) \S4method{getH5File}{liger,ANY}(x, dataset = NULL) -} -\arguments{ -\item{x, object, model}{A \linkS4class{liger} object} -\item{value}{Check detail sections for requirements.} +\S4method{$}{liger}(x, name) -\item{i, j}{Feature and cell index for \code{`[`} method. For \code{`[[`} -method, use a single variable name with \code{i} while \code{j} is not -applicable.} +\S4method{$}{liger}(x, name) <- value -\item{...}{See detailed sections for explanation.} +\S4method{defaultCluster}{liger}(x, useDatasets = NULL, droplevels = FALSE, ...) -\item{drop}{Not applicable.} +\S4method{defaultCluster}{liger,ANY,ANY,character}(x, name = NULL, useDatasets = NULL, ...) <- value + +\S4method{defaultCluster}{liger,ANY,ANY,factor}(x, name = NULL, useDatasets = NULL, droplevels = TRUE, ...) <- value + +\S4method{defaultCluster}{liger,ANY,ANY,NULL}(x, name = NULL, useDatasets = NULL, ...) <- value + +\S4method{dimRed}{liger,missing}(x, name = NULL, useDatasets = NULL, ...) + +\S4method{dimRed}{liger,character}(x, name = NULL, useDatasets = NULL, ...) + +\S4method{dimRed}{liger,character,ANY,matrixLike}(x, name = NULL, useDatasets = NULL, asDefault = NULL, ...) <- value + +\S4method{defaultDimRed}{liger}(x, useDatasets = NULL) + +\S4method{defaultDimRed}{liger,missing,ANY,character}(x, name = NULL, useDatasets = NULL) <- value + +\S4method{defaultDimRed}{liger,character,ANY,matrixLike}(x, name, useDatasets = NULL) <- value + +\S4method{varFeatures}{liger}(x) + +\S4method{varFeatures}{liger,ANY,character}(x, check = TRUE) <- value + +\S4method{varUnsharedFeatures}{liger,ANY}(x, dataset = NULL) + +\S4method{varUnsharedFeatures}{liger,ANY,ANY,character}(x, dataset, check = TRUE) <- value + +\method{fortify}{liger}(model, data, ...) + +\method{c}{liger}(...) + +\S4method{commands}{liger}(x, funcName = NULL, arg = NULL) + +\S4method{varUnsharedFeatures}{ligerDataset,missing}(x, dataset = NULL) + +\S4method{varUnsharedFeatures}{ligerDataset,missing,ANY,character}(x, dataset = NULL, check = TRUE) <- value +} +\arguments{ +\item{x, object, model}{A \linkS4class{liger} object} \item{check}{Logical, whether to perform object validity check on setting new value. Users are not supposed to set \code{FALSE} here.} +\item{value}{Check detail sections for requirements.} + \item{dataset}{Name or numeric index of a dataset} \item{type}{When using \code{dataset<-} with a matrix like \code{value}, @@ -315,9 +312,25 @@ acceptable. Default \code{NULL} works with all datasets.} \item{as.data.frame}{Logical, whether to apply \code{\link[base]{as.data.frame}} on the subscription. Default \code{FALSE}.} +\item{...}{See detailed sections for explanation.} + +\item{inplace}{For \code{cellMeta<-} method, when \code{columns} is for +existing variable and \code{useDatasets} or \code{cellIdx} indicate partial +insertion to the object, whether to by default (\code{TRUE}) in-place insert +\code{value} into the variable for selected cells or to replace the whole +variable with non-selected part left as NA.} + \item{name}{The name of available variables in \code{cellMeta} slot or the name of a new variable to store.} +\item{funcName, arg}{See Command records section.} + +\item{slot}{Name of slot to retrieve matrix from. Options shown in Usage.} + +\item{returnList}{Logical, whether to force return a list even when only one +dataset-specific matrix (i.e. expression matrices, H, V or U) is requested. +Default \code{FALSE}.} + \item{droplevels}{Whether to remove unused cluster levels from the factor object fetched by \code{defaultCluster()}. Default \code{FALSE}.} @@ -326,14 +339,6 @@ default for visualization methods. Default \code{NULL} sets it when no default has been set yet, otherwise does not change current default.} \item{data}{fortify method required argument. Not used.} - -\item{funcName, arg}{See Command records section.} - -\item{slot}{Name of slot to retrieve matrix from. Options shown in Usage.} - -\item{returnList}{Logical, whether to force return a list even when only one -dataset-specific matrix (i.e. expression matrices, H, V or U) is requested. -Default \code{FALSE}.} } \value{ See detailed sections for explanetion. @@ -386,36 +391,6 @@ accordingly.} \item{\code{version}}{Record of version of rliger2 package} }} -\section{Dimensionality}{ - -For a \code{liger} object, the column orientation is assigned for -cells. Due to the data structure, it is hard to define a row index for the -\code{liger} object, which might contain datasets that vary in number of -genes. - -Therefore, for \code{liger} objects, \code{dim} and \code{dimnames} returns -\code{NA}/\code{NULL} for rows and total cell counts/barcodes for the -columns. - -For direct call of \code{dimnames<-} method, \code{value} should be a list -with \code{NULL} as the first element and valid cell identifiers as the -second element. For \code{colnames<-} method, the character vector of cell -identifiers. \code{rownames<-} method is not applicable. -} - -\section{Subsetting}{ - -For more detail of subsetting a \code{liger} object or a -\linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} -and \code{\link{subsetLigerDataset}}. Here, we set the S4 method -"single-bracket" \code{[} as a quick wrapper to subset a \code{liger} object. -Note that \code{j} serves as cell subscriptor which can be any valid index -refering the collection of all cells (i.e. \code{rownames(cellMeta(obj))}). -While \code{i}, the feature subscriptor can only be character vector because -the features for each dataset can vary. \code{...} arugments are passed to -\code{subsetLiger} so that advanced options are allowed. -} - \section{Dataset access}{ \code{datasets()} method only accesses the \code{datasets} slot, the list of @@ -517,14 +492,6 @@ of inner \code{ligerDataset} objects as well as the \code{W} and \code{H.norm} slots of the input \code{liger} object. } -\section{Combining multiple liger object}{ - The list of \code{datasets} slot, -the rows of \code{cellMeta} slot and the list of \code{commands} slot will -be simply concatenated. Variable features in \code{varFeatures} slot will be -taken a union. The \eqn{W} and \eqn{H.norm} matrices are not taken into -account for now. -} - \section{Command records}{ rliger functions, that perform calculation and update the \code{liger} @@ -541,6 +508,44 @@ all louvain cluster attempts: \code{commands(ligerObj, "louvainCluster", "resolution")} } +\section{Dimensionality}{ + +For a \code{liger} object, the column orientation is assigned for +cells. Due to the data structure, it is hard to define a row index for the +\code{liger} object, which might contain datasets that vary in number of +genes. + +Therefore, for \code{liger} objects, \code{dim} and \code{dimnames} returns +\code{NA}/\code{NULL} for rows and total cell counts/barcodes for the +columns. + +For direct call of \code{dimnames<-} method, \code{value} should be a list +with \code{NULL} as the first element and valid cell identifiers as the +second element. For \code{colnames<-} method, the character vector of cell +identifiers. \code{rownames<-} method is not applicable. +} + +\section{Subsetting}{ + +For more detail of subsetting a \code{liger} object or a +\linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} +and \code{\link{subsetLigerDataset}}. Here, we set the S4 method +"single-bracket" \code{[} as a quick wrapper to subset a \code{liger} object. +Note that \code{j} serves as cell subscriptor which can be any valid index +refering the collection of all cells (i.e. \code{rownames(cellMeta(obj))}). +While \code{i}, the feature subscriptor can only be character vector because +the features for each dataset can vary. \code{...} arugments are passed to +\code{subsetLiger} so that advanced options are allowed. +} + +\section{Combining multiple liger object}{ + The list of \code{datasets} slot, +the rows of \code{cellMeta} slot and the list of \code{commands} slot will +be simply concatenated. Variable features in \code{varFeatures} slot will be +taken a union. The \eqn{W} and \eqn{H.norm} matrices are not taken into +account for now. +} + \examples{ # Methods for base generics pbmcPlot diff --git a/man/ligerATACDataset-class.Rd b/man/ligerATACDataset-class.Rd index a7d53039..07f07c03 100644 --- a/man/ligerATACDataset-class.Rd +++ b/man/ligerATACDataset-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset_subclasses.R +% Please edit documentation in R/classes.R \docType{class} \name{ligerATACDataset-class} \alias{ligerATACDataset-class} diff --git a/man/ligerCommand-class.Rd b/man/ligerCommand-class.Rd index f8e13d65..fdc46da7 100644 --- a/man/ligerCommand-class.Rd +++ b/man/ligerCommand-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerCommand-class.R +% Please edit documentation in R/classes.R, R/ligerCommand_relates.R \docType{class} \name{ligerCommand-class} \alias{ligerCommand-class} diff --git a/man/ligerDataset-class.Rd b/man/ligerDataset-class.Rd index e75aa15f..7c10002e 100644 --- a/man/ligerDataset-class.Rd +++ b/man/ligerDataset-class.Rd @@ -1,50 +1,81 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset-class.R +% Please edit documentation in R/classes.R, R/generics.R, +% R/ligerDataset-methods.R \docType{class} \name{ligerDataset-class} \alias{ligerDataset-class} \alias{ligerDataset} +\alias{rawData} +\alias{rawData<-} +\alias{normData} +\alias{normData<-} +\alias{scaleData} +\alias{scaleData<-} +\alias{scaleUnsharedData} +\alias{scaleUnsharedData<-} +\alias{getMatrix} +\alias{h5fileInfo} +\alias{h5fileInfo<-} +\alias{getH5File} +\alias{getH5File,ligerDataset,missing-method} +\alias{featureMeta} +\alias{featureMeta<-} \alias{show,ligerDataset-method} \alias{dim,ligerDataset-method} \alias{dimnames,ligerDataset-method} \alias{dimnames<-,ligerDataset,list-method} -\alias{rawData} -\alias{rawData<-} \alias{rawData,ligerDataset-method} \alias{rawData<-,ligerDataset,ANY,ANY,matrixLike_OR_NULL-method} \alias{rawData<-,ligerDataset,ANY,ANY,H5D-method} -\alias{normData} -\alias{normData<-} \alias{normData,ligerDataset-method} \alias{normData<-,ligerDataset,ANY,ANY,matrixLike_OR_NULL-method} \alias{normData<-,ligerDataset,ANY,ANY,H5D-method} -\alias{scaleData} -\alias{scaleData<-} \alias{scaleData,ligerDataset,missing-method} \alias{scaleData<-,ligerDataset,ANY,ANY,matrixLike_OR_NULL-method} \alias{scaleData<-,ligerDataset,ANY,ANY,H5D-method} \alias{scaleData<-,ligerDataset,ANY,ANY,H5Group-method} -\alias{scaleUnsharedData} -\alias{scaleUnsharedData<-} \alias{scaleUnsharedData,ligerDataset,missing-method} \alias{scaleUnsharedData<-,ligerDataset,missing,ANY,matrixLike_OR_NULL-method} \alias{scaleUnsharedData<-,ligerDataset,missing,ANY,H5D-method} \alias{scaleUnsharedData<-,ligerDataset,missing,ANY,H5Group-method} -\alias{getMatrix} \alias{getMatrix,ligerDataset,ANY,missing,missing-method} -\alias{h5fileInfo} -\alias{h5fileInfo<-} \alias{h5fileInfo,ligerDataset-method} \alias{h5fileInfo<-,ligerDataset-method} -\alias{getH5File} -\alias{getH5File,ligerDataset,missing-method} -\alias{featureMeta} -\alias{featureMeta<-} \alias{featureMeta,ligerDataset-method} \alias{featureMeta<-,ligerDataset-method} \alias{cbind.ligerDataset} \title{ligerDataset class} \usage{ +rawData(x, dataset = NULL) + +rawData(x, dataset = NULL, check = TRUE) <- value + +normData(x, dataset = NULL) + +normData(x, dataset = NULL, check = TRUE) <- value + +scaleData(x, dataset = NULL) + +scaleData(x, dataset = NULL, check = TRUE) <- value + +scaleUnsharedData(x, dataset = NULL) + +scaleUnsharedData(x, dataset = NULL, check = TRUE) <- value + +getMatrix(x, slot = "rawData", dataset = NULL, returnList = FALSE) + +h5fileInfo(x, info = NULL) + +h5fileInfo(x, info = NULL, check = TRUE) <- value + +getH5File(x, dataset = NULL) + +\S4method{getH5File}{ligerDataset,missing}(x, dataset = NULL) + +featureMeta(x, check = NULL) + +featureMeta(x, check = TRUE) <- value + \S4method{show}{ligerDataset}(object) \S4method{dim}{ligerDataset}(x) @@ -53,30 +84,18 @@ \S4method{dimnames}{ligerDataset,list}(x) <- value -rawData(x, dataset = NULL) - -rawData(x, dataset = NULL, check = TRUE) <- value - \S4method{rawData}{ligerDataset}(x, dataset = NULL) \S4method{rawData}{ligerDataset,ANY,ANY,matrixLike_OR_NULL}(x, dataset = NULL, check = TRUE) <- value \S4method{rawData}{ligerDataset,ANY,ANY,H5D}(x, dataset = NULL, check = TRUE) <- value -normData(x, dataset = NULL) - -normData(x, dataset = NULL, check = TRUE) <- value - \S4method{normData}{ligerDataset}(x, dataset = NULL) \S4method{normData}{ligerDataset,ANY,ANY,matrixLike_OR_NULL}(x, dataset = NULL, check = TRUE) <- value \S4method{normData}{ligerDataset,ANY,ANY,H5D}(x, dataset = NULL, check = TRUE) <- value -scaleData(x, dataset = NULL) - -scaleData(x, dataset = NULL, check = TRUE) <- value - \S4method{scaleData}{ligerDataset,missing}(x, dataset = NULL) \S4method{scaleData}{ligerDataset,ANY,ANY,matrixLike_OR_NULL}(x, dataset = NULL, check = TRUE) <- value @@ -85,10 +104,6 @@ scaleData(x, dataset = NULL, check = TRUE) <- value \S4method{scaleData}{ligerDataset,ANY,ANY,H5Group}(x, dataset = NULL, check = TRUE) <- value -scaleUnsharedData(x, dataset = NULL) - -scaleUnsharedData(x, dataset = NULL, check = TRUE) <- value - \S4method{scaleUnsharedData}{ligerDataset,missing}(x, dataset = NULL) \S4method{scaleUnsharedData}{ligerDataset,missing,ANY,matrixLike_OR_NULL}(x, check = TRUE) <- value @@ -97,8 +112,6 @@ scaleUnsharedData(x, dataset = NULL, check = TRUE) <- value \S4method{scaleUnsharedData}{ligerDataset,missing,ANY,H5Group}(x, check = TRUE) <- value -getMatrix(x, slot = "rawData", dataset = NULL, returnList = FALSE) - \S4method{getMatrix}{ligerDataset,ANY,missing,missing}( x, slot = c("rawData", "normData", "scaleData", "scaleUnsharedData", "H", "V", "U", "A", @@ -106,22 +119,10 @@ getMatrix(x, slot = "rawData", dataset = NULL, returnList = FALSE) dataset = NULL ) -h5fileInfo(x, info = NULL) - -h5fileInfo(x, info = NULL, check = TRUE) <- value - \S4method{h5fileInfo}{ligerDataset}(x, info = NULL) \S4method{h5fileInfo}{ligerDataset}(x, info = NULL, check = TRUE) <- value -getH5File(x, dataset = NULL) - -\S4method{getH5File}{ligerDataset,missing}(x, dataset = NULL) - -featureMeta(x, check = NULL) - -featureMeta(x, check = TRUE) <- value - \S4method{featureMeta}{ligerDataset}(x, check = NULL) \S4method{featureMeta}{ligerDataset}(x, check = TRUE) <- value @@ -131,12 +132,12 @@ featureMeta(x, check = TRUE) <- value \arguments{ \item{x, object}{A \code{ligerDataset} object.} -\item{value}{See detail sections for requirements} - \item{dataset}{Not applicable for \code{ligerDataset} methods.} \item{check}{Whether to perform object validity check on setting new value.} +\item{value}{See detail sections for requirements} + \item{slot}{The slot name when using \code{getMatrix}.} \item{returnList}{Not applicable for \code{ligerDataset} methods.} @@ -184,33 +185,6 @@ datasets} \item{\code{rownames}}{character} }} -\section{Dimensionality}{ - -For a \code{ligerDataset} object, the column orientation is assigned for -cells and rows are for features. Therefore, for \code{ligerDataset} objects, -\code{dim()} returns a numeric vector of two numbers which are number of -features and number of cells. \code{dimnames()} returns a list of two -character vectors, which are the feature names and the cell barcodes. - -For direct call of \code{dimnames<-} method, \code{value} should be a list -with a character vector of feature names as the first element and cell -identifiers as the second element. For \code{colnames<-} method, the -character vector of cell identifiers. For \code{rownames<-} method, the -character vector of feature names. -} - -\section{Subsetting}{ - -For more detail of subsetting a \code{liger} object or a -\linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} -and \code{\link{subsetLigerDataset}}. Here, we set the S3 method -"single-bracket" \code{[} as a quick wrapper to subset a \code{ligerDataset} -object. \code{i} and \code{j} serves as feature and cell subscriptor, -respectively, which can be any valid index refering the available features -and cells in a dataset. \code{...} arugments are passed to -\code{subsetLigerDataset} so that advanced options are allowed. -} - \section{Matrix access}{ For \code{ligerDataset} object, \code{rawData()}, \code{normData}, @@ -260,6 +234,33 @@ For example, \code{featureMeta(ligerD)$nCell} or \code{featureMeta(ligerD)[varFeatures(ligerObj), "gene_var"]}. } +\section{Dimensionality}{ + +For a \code{ligerDataset} object, the column orientation is assigned for +cells and rows are for features. Therefore, for \code{ligerDataset} objects, +\code{dim()} returns a numeric vector of two numbers which are number of +features and number of cells. \code{dimnames()} returns a list of two +character vectors, which are the feature names and the cell barcodes. + +For direct call of \code{dimnames<-} method, \code{value} should be a list +with a character vector of feature names as the first element and cell +identifiers as the second element. For \code{colnames<-} method, the +character vector of cell identifiers. For \code{rownames<-} method, the +character vector of feature names. +} + +\section{Subsetting}{ + +For more detail of subsetting a \code{liger} object or a +\linkS4class{ligerDataset} object, please check out \code{\link{subsetLiger}} +and \code{\link{subsetLigerDataset}}. Here, we set the S3 method +"single-bracket" \code{[} as a quick wrapper to subset a \code{ligerDataset} +object. \code{i} and \code{j} serves as feature and cell subscriptor, +respectively, which can be any valid index refering the available features +and cells in a dataset. \code{...} arugments are passed to +\code{subsetLigerDataset} so that advanced options are allowed. +} + \section{Concatenate ligerDataset}{ \code{cbind()} method is implemented for concatenating \code{ligerDataset} diff --git a/man/ligerMethDataset-class.Rd b/man/ligerMethDataset-class.Rd index af068570..8c7009ce 100644 --- a/man/ligerMethDataset-class.Rd +++ b/man/ligerMethDataset-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset_subclasses.R +% Please edit documentation in R/classes.R \docType{class} \name{ligerMethDataset-class} \alias{ligerMethDataset-class} diff --git a/man/ligerRNADataset-class.Rd b/man/ligerRNADataset-class.Rd index a57d6a85..ddb35a84 100644 --- a/man/ligerRNADataset-class.Rd +++ b/man/ligerRNADataset-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset_subclasses.R +% Please edit documentation in R/classes.R \docType{class} \name{ligerRNADataset-class} \alias{ligerRNADataset-class} diff --git a/man/ligerSpatialDataset-class.Rd b/man/ligerSpatialDataset-class.Rd index bd4b266f..e59146fd 100644 --- a/man/ligerSpatialDataset-class.Rd +++ b/man/ligerSpatialDataset-class.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset_subclasses.R +% Please edit documentation in R/classes.R \docType{class} \name{ligerSpatialDataset-class} \alias{ligerSpatialDataset-class} diff --git a/man/modalOf.Rd b/man/modalOf.Rd index fb1b5d4e..139301fa 100644 --- a/man/modalOf.Rd +++ b/man/modalOf.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset-class.R +% Please edit documentation in R/ligerDataset-methods.R \name{modalOf} \alias{modalOf} \title{Return preset modality of a ligerDataset object or that of all datasets in a diff --git a/man/normalize.Rd b/man/normalize.Rd index 2145f81a..8d1f48ba 100644 --- a/man/normalize.Rd +++ b/man/normalize.Rd @@ -13,12 +13,12 @@ normalize(object, ...) \method{normalize}{dgCMatrix}(object, log = FALSE, scaleFactor = NULL, ...) -\method{normalize}{ligerDataset}(object, chunk = 1000, verbose = getOption("ligerVerbose"), ...) +\method{normalize}{ligerDataset}(object, chunk = 1000, verbose = getOption("ligerVerbose", TRUE), ...) \method{normalize}{liger}( object, useDatasets = NULL, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), format.type = NULL, remove.missing = NULL, ... @@ -29,7 +29,7 @@ normalize(object, ...) normalizePeak( object, useDatasets = NULL, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) } @@ -51,7 +51,7 @@ factor before transformation. \code{NULL} for not scaling. Default working on HDF5 file based ligerDataset. Default \code{1000}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} \item{useDatasets}{A character vector of the names, a numeric or logical vector of the index of the datasets to be normalized. Should specify ATACseq diff --git a/man/peak.Rd b/man/peak.Rd index 4dfbd19a..851cbe05 100644 --- a/man/peak.Rd +++ b/man/peak.Rd @@ -1,5 +1,6 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset_subclasses.R +% Please edit documentation in R/generics.R, R/liger-methods.R, +% R/ligerDataset_subclass-methods.R \name{rawPeak} \alias{rawPeak} \alias{rawPeak<-} @@ -7,10 +8,10 @@ \alias{normPeak<-} \alias{rawPeak,liger,character-method} \alias{rawPeak<-,liger,character-method} -\alias{rawPeak,ligerATACDataset,missing-method} -\alias{rawPeak<-,ligerATACDataset,missing-method} \alias{normPeak,liger,character-method} \alias{normPeak<-,liger,character-method} +\alias{rawPeak,ligerATACDataset,missing-method} +\alias{rawPeak<-,ligerATACDataset,missing-method} \alias{normPeak,ligerATACDataset,missing-method} \alias{normPeak<-,ligerATACDataset,missing-method} \title{Access ligerATACDataset peak data} @@ -27,14 +28,14 @@ normPeak(x, dataset, check = TRUE) <- value \S4method{rawPeak}{liger,character}(x, dataset, check = TRUE) <- value -\S4method{rawPeak}{ligerATACDataset,missing}(x, dataset = NULL) - -\S4method{rawPeak}{ligerATACDataset,missing}(x, dataset = NULL, check = TRUE) <- value - \S4method{normPeak}{liger,character}(x, dataset) \S4method{normPeak}{liger,character}(x, dataset, check = TRUE) <- value +\S4method{rawPeak}{ligerATACDataset,missing}(x, dataset = NULL) + +\S4method{rawPeak}{ligerATACDataset,missing}(x, dataset = NULL, check = TRUE) <- value + \S4method{normPeak}{ligerATACDataset,missing}(x, dataset = NULL) \S4method{normPeak}{ligerATACDataset,missing}(x, dataset = NULL, check = TRUE) <- value diff --git a/man/plotVolcano.Rd b/man/plotVolcano.Rd index fdf586f0..f5becf37 100644 --- a/man/plotVolcano.Rd +++ b/man/plotVolcano.Rd @@ -59,3 +59,7 @@ most of arguments with other rliger plotting functions. substantial amount of arguments for graphical control. However, that requires the installation of package "EnhancedVolcano". } +\examples{ +result <- runMarkerDEG(pbmcPlot) +plotVolcano(result, 1) +} diff --git a/man/reexports.Rd b/man/reexports.Rd index 8cd827d2..68970992 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/zzz.R +% Please edit documentation in R/aaa.R \docType{import} \name{reexports} \alias{reexports} diff --git a/man/removeMissing.Rd b/man/removeMissing.Rd index 34fc12e3..7cd4404a 100644 --- a/man/removeMissing.Rd +++ b/man/removeMissing.Rd @@ -13,7 +13,7 @@ removeMissing( useDatasets = NULL, newH5 = TRUE, filenameSuffix = "removeMissing", - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) @@ -21,7 +21,7 @@ removeMissingObs( object, slot.use = NULL, use.cols = TRUE, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) } \arguments{ @@ -51,7 +51,7 @@ H5-based dataset on subset. Default \code{TRUE}} suffix will be added to all the filenames. Default \code{"removeMissing"}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} \item{...}{Arguments passed to \code{\link{subsetLigerDataset}}} diff --git a/man/reverseMethData.Rd b/man/reverseMethData.Rd index 2e5b729a..15f6c055 100644 --- a/man/reverseMethData.Rd +++ b/man/reverseMethData.Rd @@ -4,7 +4,7 @@ \alias{reverseMethData} \title{Create "scaled data" for DNA methylation datasets} \usage{ -reverseMethData(object, useDatasets, verbose = getOption("ligerVerbose")) +reverseMethData(object, useDatasets, verbose = getOption("ligerVerbose", TRUE)) } \arguments{ \item{object}{A \linkS4class{liger} object, with variable genes identified.} @@ -14,7 +14,7 @@ logical vector of the index of the datasets that should be identified as methylation data where the reversed data will be created.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} } \value{ The input \linkS4class{liger} object, where the \code{scaleData} slot diff --git a/man/runCluster.Rd b/man/runCluster.Rd index 5d3ec01b..d0230dd2 100644 --- a/man/runCluster.Rd +++ b/man/runCluster.Rd @@ -18,7 +18,7 @@ runCluster( groupSingletons = TRUE, clusterName = paste0(method, "_cluster"), seed = 1, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) } \arguments{ diff --git a/man/runGeneralQC.Rd b/man/runGeneralQC.Rd index cc5d6f4c..d95ef128 100644 --- a/man/runGeneralQC.Rd +++ b/man/runGeneralQC.Rd @@ -13,7 +13,7 @@ runGeneralQC( pattern = NULL, useDatasets = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) } \arguments{ @@ -40,7 +40,7 @@ vector of the index of the datasets to be included for QC. Default on HDF5 based dataset. Default \code{1000}} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} } \value{ Updated \code{object} with \code{nUMI}, \code{nGene} updated diff --git a/man/scaleNotCenter.Rd b/man/scaleNotCenter.Rd index 4eb2588e..f8a48cc2 100644 --- a/man/scaleNotCenter.Rd +++ b/man/scaleNotCenter.Rd @@ -17,14 +17,14 @@ scaleNotCenter(object, ...) object, features = NULL, chunk = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) \method{scaleNotCenter}{ligerMethDataset}( object, features = NULL, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) @@ -32,7 +32,7 @@ scaleNotCenter(object, ...) object, useDatasets = NULL, features = varFeatures(object), - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), remove.missing = NULL, ... ) @@ -65,7 +65,7 @@ features. "Seurat" method by default uses scaling is applied to any HDF5 based dataset. Default \code{1000}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} \item{useDatasets}{A character vector of the names, a numeric or logical vector of the index of the datasets to be scaled but not centered. Default diff --git a/man/selectGenes.Rd b/man/selectGenes.Rd index fa1ea7cb..46e389ce 100644 --- a/man/selectGenes.Rd +++ b/man/selectGenes.Rd @@ -18,7 +18,7 @@ selectGenes(object, thresh = 0.1, nGenes = NULL, alpha = 0.99, ...) unsharedThresh = 0.1, combine = c("union", "intersection"), chunk = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), var.thresh = thresh, alpha.thresh = alpha, num.genes = nGenes, @@ -42,7 +42,7 @@ selectGenes(object, thresh = 0.1, nGenes = NULL, alpha = 0.99, ...) assay = NULL, datasetVar = "orig.ident", combine = c("union", "intersection"), - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), ... ) } @@ -88,7 +88,7 @@ Choose from \code{"union"} or \code{"intersection"}. Default \code{"union"}.} gene selection is applied to any HDF5 based dataset. Default \code{1000}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} \item{var.thresh, alpha.thresh, num.genes, datasets.use, unshared.datasets, unshared.thresh}{\bold{Deprecated}. These arguments are renamed and will be removed in the future. Please see diff --git a/man/selectGenesVST.Rd b/man/selectGenesVST.Rd index 6b288a17..d969d2e5 100644 --- a/man/selectGenesVST.Rd +++ b/man/selectGenesVST.Rd @@ -11,7 +11,7 @@ selectGenesVST( loessSpan = 0.3, clipMax = "auto", useShared = TRUE, - verbose = getOption("ligerVerbose") + verbose = getOption("ligerVerbose", TRUE) ) } \arguments{ @@ -33,7 +33,7 @@ root of the number of cells.} dataset. Default \code{TRUE}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} } \description{ Seurat FindVariableFeatures VST method. This allows the selection of a fixed diff --git a/man/sub-liger.Rd b/man/sub-liger.Rd new file mode 100644 index 00000000..25dbf90c --- /dev/null +++ b/man/sub-liger.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/liger-methods.R +\name{sub-liger} +\alias{sub-liger} +\alias{[.liger} +\title{Subset liger with brackets} +\usage{ +\method{[}{liger}(x, i, j, ...) +} +\arguments{ +\item{x}{A \linkS4class{liger} object} + +\item{i}{Feature subscriptor, passed to \code{featureIdx} of +\code{\link{subsetLiger}}.} + +\item{j}{Cell subscriptor, passed to \code{cellIdx} of +\code{\link{subsetLiger}}.} + +\item{...}{Additional arguments passed to \code{\link{subsetLiger}}.} +} +\value{ +Subset of \code{x} with specified features and cells. +} +\description{ +Subset liger with brackets +} +\examples{ +pbmcPlot[varFeatures(pbmcPlot)[1:10], 1:10] +} +\seealso{ +\code{\link{subsetLiger}} +} diff --git a/man/sub-ligerDataset.Rd b/man/sub-ligerDataset.Rd index 7cc893d9..91f36773 100644 --- a/man/sub-ligerDataset.Rd +++ b/man/sub-ligerDataset.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ligerDataset-class.R +% Please edit documentation in R/ligerDataset-methods.R \name{sub-ligerDataset} \alias{sub-ligerDataset} \alias{[.ligerDataset} diff --git a/man/sub-sub-liger.Rd b/man/sub-sub-liger.Rd index 973236b3..7aeeea73 100644 --- a/man/sub-sub-liger.Rd +++ b/man/sub-sub-liger.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/liger-class.R +% Please edit documentation in R/liger-methods.R \name{sub-sub-liger} \alias{sub-sub-liger} \alias{[[.liger} diff --git a/man/sub-subset-.liger.Rd b/man/sub-subset-.liger.Rd index fceb8c11..bbe1f646 100644 --- a/man/sub-subset-.liger.Rd +++ b/man/sub-subset-.liger.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/liger-class.R +% Please edit documentation in R/liger-methods.R \name{[[<-,liger,ANY,missing,ANY-method} \alias{[[<-,liger,ANY,missing,ANY-method} \alias{[[<-,liger,ANY,missing-method} diff --git a/man/subsetLiger.Rd b/man/subsetLiger.Rd index e2b62939..311d4e65 100644 --- a/man/subsetLiger.Rd +++ b/man/subsetLiger.Rd @@ -10,7 +10,7 @@ subsetLiger( cellIdx = NULL, useSlot = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), newH5 = TRUE, returnObject = TRUE, ... @@ -33,7 +33,7 @@ Missing or \code{NULL} for all cells.} Default \code{1000}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} \item{newH5}{Whether to create new H5 files on disk for the subset datasets if involved datasets in the \code{object} is HDF5 based. \code{TRUE} writes a diff --git a/man/subsetLigerDataset.Rd b/man/subsetLigerDataset.Rd index 4fb6528d..6380f80a 100644 --- a/man/subsetLigerDataset.Rd +++ b/man/subsetLigerDataset.Rd @@ -15,7 +15,7 @@ subsetLigerDataset( filename = NULL, filenameSuffix = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), returnObject = TRUE, ... ) @@ -29,7 +29,7 @@ subsetH5LigerDataset( filename = NULL, filenameSuffix = NULL, chunkSize = 1000, - verbose = getOption("ligerVerbose"), + verbose = getOption("ligerVerbose", TRUE), returnObject = TRUE ) @@ -71,7 +71,7 @@ for the new files so the new filename looks like Default \code{1000}.} \item{verbose}{Logical. Whether to show information of the progress. Default -\code{getOption("ligerVerbose")} which is \code{TRUE} if users have not set.} +\code{getOption("ligerVerbose")} or \code{TRUE} if users have not set.} \item{returnObject}{Logical, whether to return a \linkS4class{ligerDataset} object for result. Default \code{TRUE}. \code{FALSE} returns a list diff --git a/tests/testthat/test_downstream.R b/tests/testthat/test_downstream.R index e6754e60..31d09697 100644 --- a/tests/testthat/test_downstream.R +++ b/tests/testthat/test_downstream.R @@ -156,10 +156,8 @@ test_that("wilcoxon", { expect_error(getFactorMarkers(pbmc, "ctrl", "stim", factorShareThresh = 0), "No factor passed the dataset specificity threshold") - expect_warning( - expect_message( - res3 <- getFactorMarkers(pbmc, "ctrl", "stim", printGenes = TRUE) - ) + expect_message( + res3 <- getFactorMarkers(pbmc, "ctrl", "stim", printGenes = TRUE) ) expect_is(res3, "list") expect_identical(names(res3), c("ctrl", "shared", "stim", "num_factors_V1", @@ -215,7 +213,7 @@ test_that("pseudo bulk", { groupCtrl = pbmc$dataset == "stim" & pbmc$leiden_cluster == 0, method = "pseudo", useReplicate = "dataset" ), - "Too few replicates label for condition" + "Too few replicates for condition" ) pbmc@datasets$ctrl@rawData <- NULL @@ -225,7 +223,7 @@ test_that("pseudo bulk", { variable1 = "leiden_cluster", method = "pseudo", useReplicate = "dataset" ), - "rawData not all available for involved datasets" + "not all available for involved datasets" ) }) diff --git a/tests/testthat/test_factorization.R b/tests/testthat/test_factorization.R index fd38b3c9..5908ae1f 100644 --- a/tests/testthat/test_factorization.R +++ b/tests/testthat/test_factorization.R @@ -249,14 +249,14 @@ test_that("Seurat wrapper", { expect_in("onlineINMF", SeuratObject::Reductions(seu)) expect_error(quantileNorm(seu, reduction = "orig.ident"), - "Specified `reduction` does not points to a DimReduc") + "Specified `reduction` does not points to a") seu <- quantileNorm(seu, reduction = "inmf") expect_in("inmfNorm", SeuratObject::Reductions(seu)) expect_error(quantileNorm(seu, reference = "hello"), "Should specify one existing dataset") expect_error(quantileNorm(seu, reference = 114514), - "Should specify one dataset within the range.") + "Should specify one existing dataset as reference") expect_error(quantileNorm(seu, reference = c(TRUE, FALSE, TRUE)), - "Should specify one dataset within the range.") + "Should specify one existing dataset as reference") }) diff --git a/tests/testthat/test_object.R b/tests/testthat/test_object.R index 99f31d75..ca31f83d 100644 --- a/tests/testthat/test_object.R +++ b/tests/testthat/test_object.R @@ -60,7 +60,7 @@ test_that("liger object creation - in memory", { expect_error(createLiger(rawData = "hi"), "`rawData` has to be a named list.") expect_error(createLiger(rawData = rawDataList, modal = letters[1:3]), - "`modal` has to be a vector of length 2") + "`modal` has to be a length 1 or 2 object of class") ldList <- datasets(pbmc) cellmeta <- cellMeta(pbmc) pbmc2 <- createLiger(rawData = ldList, cellMeta = cellmeta, @@ -83,7 +83,7 @@ test_that("liger object creation - on disk", { withNewH5Copy( function(rawList) { expect_error(createLiger(rawList, formatType = "Hello"), - "Specified `formatType` '") + "Specified `formatType`") # Customized paths barcodesName <- "matrix/barcodes" @@ -161,8 +161,8 @@ test_that("liger S3/S4 methods", { expect_is(meta, "DFrame") expect_null(cellMeta(pbmc, NULL)) expect_is(cellMeta(pbmc, "dataset"), "factor") - expect_warning(cellMeta(pbmc, "UMAP.1"), - "Specified variables from cellMeta not found: UMAP.1") + expect_message(cellMeta(pbmc, "UMAP.1"), + "Specified variables from cellMeta not found") expect_is(cellMeta(pbmc, "UMAP.1", cellIdx = 1:500, as.data.frame = TRUE), "numeric") expect_is(pbmc[["nUMI"]], "numeric") @@ -198,7 +198,7 @@ test_that("liger S3/S4 methods", { test_that("ligerDataset (in memory) object creation", { expect_error(createLigerDataset(), - "At least one type of") + "At least one of") ld <- createLigerDataset(rawData = rawDataList[[1]], modal = "atac") expect_is(ld, "ligerATACDataset") @@ -213,8 +213,10 @@ test_that("ligerDataset (in memory) object creation", { pbmc <- scaleNotCenter(pbmc) scaledMat <- scaleData(pbmc, dataset = "ctrl") featuremeta <- featureMeta(dataset(pbmc, "ctrl")) - ld <- createLigerDataset(scaleData = scaledMat, featureMeta = featuremeta) - expect_equal(length(varFeatures(pbmc)), nrow(ld)) + expect_error( + ld <- createLigerDataset(scaleData = scaledMat, featureMeta = featuremeta), + "At least one of " + ) }) #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -227,7 +229,7 @@ test_that("ligerDataset methods", { expect_false(isH5Liger(pbmc)) ctrl <- dataset(pbmc, "ctrl") expect_false(isH5Liger(ctrl)) - expect_warning(isH5Liger("hi"), "Given object is not of liger") + expect_message(isH5Liger("hi"), "Given object is not ") expect_identical(modalOf(ctrl), "default") expect_identical(modalOf(pbmc), c(ctrl = "default", stim = "default")) @@ -274,7 +276,7 @@ test_that("ligerDataset methods", { # ligerATACDataset related expect_error(rawPeak(pbmc, "stim"), - "Specified dataset is not of ligerATACDataset class.") + "Specified dataset is not of ") expect_error(rawPeak(pbmc, "stim") <- rawData(ctrl), "Specified dataset is not of") ctrl <- as.ligerDataset(ctrl, modal = "atac") @@ -282,7 +284,7 @@ test_that("ligerDataset methods", { rawPeak(pbmc, "ctrl") <- rawData(ctrl) expect_error(normPeak(pbmc, "stim"), - "Specified dataset is not of ligerATACDataset class.") + "Specified dataset is not of") expect_error(normPeak(pbmc, "stim") <- normData(stim), "Specified dataset is not of") normPeak(pbmc, "ctrl") <- normData(ctrl) @@ -290,7 +292,7 @@ test_that("ligerDataset methods", { expect_true(validObject(ctrl)) # ligerSpatialDataset related - expect_warning(ctrl <- as.ligerDataset(ctrl, modal = "spatial"), + expect_message(ctrl <- as.ligerDataset(ctrl, modal = "spatial"), "Will remove information in the following slots when ") pbmc@datasets$ctrl <- ctrl coords <- matrix(rnorm(300*2), 300, 2) @@ -305,7 +307,7 @@ test_that("ligerDataset methods", { expect_true(validObject(ctrl)) coords <- matrix(rnorm(300*3), 300, 3) - expect_warning(coordinate(ctrl) <- coords, + expect_message(coordinate(ctrl) <- coords, "No rownames with given spatial coordinate") coords <- matrix(rnorm(300*4), 300, 4) rownames(coords) <- colnames(ctrl) @@ -317,10 +319,10 @@ test_that("ligerDataset methods", { colnames(coords) <- c("x", "y") ctrl@coordinate <- coords expect_error(validObject(ctrl), "Inconsistant cell identifiers") - expect_warning(coordinate(ctrl) <- coords, + expect_message(coordinate(ctrl) <- coords, "NA generated for missing cells") # ligerMethDataset related - expect_warning(ctrl <- as.ligerDataset(ctrl, modal = "meth"), + expect_message(ctrl <- as.ligerDataset(ctrl, modal = "meth"), "Will remove information in the following slots when ") expect_no_error(validObject(ctrl)) }) @@ -361,17 +363,17 @@ test_that("H5 ligerDataset methods", { list(indicesName = "matrix/indices", indptrName = "matrix/indptr")) expect_error(h5fileInfo(ctrl, c("indicesName", "hello")), - "Specified h5file info not found: hello") + "Specified `info` not found") expect_error(h5fileInfo(ctrl, info = 1:2) <- "hey", "`info` has to be a single character.") expect_error(h5fileInfo(ctrl, "indicesName") <- "hey", - "Specified info is invalid,") + "Specified `info`") expect_no_error(h5fileInfo(ctrl, "barcodesName") <- "matrix/barcodes") ctrl.h5$close() - expect_warning(show(ctrl), "Link to HDF5 file fails.") + expect_message(show(ctrl), "Link to HDF5 file fails.") } ) }) @@ -399,11 +401,10 @@ test_that("as.liger methods", { colData = data.frame(dataset = factor(rep(c("a", "b"), each = 150))) ) sce$useless <- 1 - expect_warning(lig <- as.liger(sce), 'Variable name "dataset"') + expect_message(lig <- as.liger(sce)) expect_equal(names(lig), "SCE") - expect_warning(lig <- as.liger(sce, datasetVar = "dataset"), - 'Variable name "dataset"') + expect_message(lig <- as.liger(sce, datasetVar = "dataset")) expect_true(all.equal(sapply(datasets(lig), ncol), c(a = 150, b = 150))) } @@ -420,7 +421,7 @@ test_that("as.liger methods", { Seurat::FindVariableFeatures() %>% Seurat::ScaleData() %>% Seurat::RunPCA() - expect_warning(lig <- as.liger(seu)) + expect_message(lig <- as.liger(seu)) expect_true(all.equal(sapply(datasets(lig), ncol), c(a = 150, b = 150))) expect_in(paste0("pca.", 1:10), colnames(cellMeta(lig, as.data.frame = TRUE))) @@ -434,7 +435,7 @@ test_that("as.ligerDataset methods", { expect_is(ld, "ligerDataset") ld <- as.ligerDataset(ctrlLD, modal = "atac") expect_is(ld, "ligerATACDataset") - expect_warning(ld <- as.ligerDataset(ld, modal = "rna"), + expect_message(ld <- as.ligerDataset(ld, modal = "rna"), "Will remove information in the following slots when ") expect_is(ld, "ligerDataset") diff --git a/tests/testthat/test_preprocessing.R b/tests/testthat/test_preprocessing.R index 13d7bd0f..befa2bd1 100644 --- a/tests/testthat/test_preprocessing.R +++ b/tests/testthat/test_preprocessing.R @@ -150,7 +150,8 @@ test_that("Normalization - in-memory", { expect_equal(sum(normPeak(ld)[, i]), 1, tolerance = 1e-6) } - expect_warning(fakeNorm <- normalize(fakePeak, scaleFactor = -1)) + expect_message(fakeNorm <- normalize(fakePeak, scaleFactor = -1), + "Invalid `scaleFactor` given") expect_true(all.equal(colSums(fakeNorm), setNames(rep(1, ncol(fakeNorm)), colnames(fakeNorm)))) @@ -183,8 +184,6 @@ test_that("Normalize - on disk", { context("Select variable genes") test_that("selectGenes", { pbmc <- normalize(pbmc, useDatasets = 1) - expect_error(selectGenes(pbmc, thresh = 1:3), - "`thresh` has to be a vector of length 2") expect_error(selectGenes(pbmc, thresh = 0.1), "Normalized data not available") pbmc <- normalize(pbmc, useDatasets = 2) @@ -196,7 +195,7 @@ test_that("selectGenes", { pbmc <- selectGenes(pbmc, combine = "inters") expect_equal(length(varFeatures(pbmc)), 161) - expect_warning(selectGenes(pbmc, thresh = 3), + expect_message(selectGenes(pbmc, thresh = 3), "No genes were selected.") pbmc <- selectGenes(pbmc) @@ -216,7 +215,7 @@ test_that("selectGenes", { pbmc <- selectGenesVST(pbmc, useDataset = "ctrl", n = 50) expect_equal(length(varFeatures(pbmc)), 50) - expect_warning(pbmc <- selectGenesVST(pbmc, useDataset = "ctrl", n = 300, + expect_message(pbmc <- selectGenesVST(pbmc, useDataset = "ctrl", n = 300, useShared = FALSE), "Not all variable features passed are found in datasets") expect_equal(length(varFeatures(pbmc)), 266) diff --git a/tests/testthat/test_subset.R b/tests/testthat/test_subset.R index dd30b038..c1b1abf5 100644 --- a/tests/testthat/test_subset.R +++ b/tests/testthat/test_subset.R @@ -50,12 +50,12 @@ process <- function(object) { context("subset liger object") test_that("subsetLiger", { - expect_warning(a <- subsetLiger("a"), "`object` is not a liger obejct") + expect_message(a <- subsetLiger("a"), "`object` is not a ") expect_identical(a, "a") skip_if_not(has_RcppPlanc) pbmc <- process(pbmc) expect_error(subsetLiger(pbmc, featureIdx = 1:3), - "Feature subscription from liger object") + "Feature subscription from a") expect_error( expect_warning(subsetLiger(pbmc, featureIdx = c("fakeGene1", "fakeGene2")), @@ -79,7 +79,7 @@ test_that("subsetH5LigerDataset", { expect_false(isH5Liger(ctrlSmall)) path <- dirname(h5fileInfo(ctrl, "filename")) newName <- file.path(path, "ctrltest.h5.small.h5") - expect_warning( + expect_message( subsetLigerDataset(ctrl, featureIdx = 1:10, cellIdx = 1:10, newH5 = TRUE, filename = newName, @@ -88,11 +88,11 @@ test_that("subsetH5LigerDataset", { ) expect_true(file.exists(newName)) unlink(newName) - expect_warning( + expect_message( rliger2:::subsetH5LigerDatasetToMem(letters), - "`object` is not a ligerDataset obejct." + "`object` is not a " ) - expect_warning( + expect_message( rliger2:::subsetH5LigerDatasetToMem(dataset(pbmc, "ctrl")), "`object` is not HDF5 based." ) @@ -101,11 +101,11 @@ test_that("subsetH5LigerDataset", { ) expect_is(valueList, "list") - expect_warning( + expect_message( rliger2:::subsetH5LigerDatasetToH5(letters), - "`object` is not a ligerDataset obejct." + "`object` is not a" ) - expect_warning( + expect_message( rliger2:::subsetH5LigerDatasetToH5(dataset(pbmc, "ctrl")), "`object` is not HDF5 based." ) diff --git a/tests/testthat/test_visualization.R b/tests/testthat/test_visualization.R index 3a9301cc..24518c37 100644 --- a/tests/testthat/test_visualization.R +++ b/tests/testthat/test_visualization.R @@ -156,7 +156,7 @@ test_that("Density plot", { expect_gg( expect_no_warning(plotDensityDimRed(pbmcPlot, splitBy = "dataset", title = "one")), - expect_warning(plotDensityDimRed(pbmcPlot, title = letters[1:3], + expect_message(plotDensityDimRed(pbmcPlot, title = letters[1:3], dotRaster = TRUE), "`title` has length greater than") )