Nothing
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# From other things to liger class ####
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#' @rdname as.liger
#' @export
#' @method as.liger dgCMatrix
as.liger.dgCMatrix <- function(
object,
datasetVar = NULL,
modal = NULL,
...
) {
datasetVar <- datasetVar %||% "sample"
datasetVar <- .checkArgLen(datasetVar, ncol(object), repN = TRUE,
class = c("factor", "character"))
if (!is.factor(datasetVar)) datasetVar <- factor(datasetVar)
datasetVar <- droplevels(datasetVar)
rawDataList <- splitRmMiss(object, datasetVar)
modal <- .checkArgLen(modal, length(rawDataList), class = "character")
createLiger(rawData = rawDataList, modal = modal, ...)
}
#' @rdname as.liger
#' @export
#' @method as.liger SingleCellExperiment
as.liger.SingleCellExperiment <- function(
object,
datasetVar = NULL,
modal = NULL,
...
) {
if (!requireNamespace("SingleCellExperiment", quietly = TRUE)) # nocov start
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))
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)) {
if ("sample" %in% colnames(SummarizedExperiment::colData(object))) {
datasetVar <- SummarizedExperiment::colData(object)[["sample"]]
} else {
datasetVar <- "SCE"
}
} else if (length(datasetVar) == 1) {
if (datasetVar %in% colnames(SummarizedExperiment::colData(object))) {
datasetVar <- SummarizedExperiment::colData(object)[[datasetVar]]
}
}
datasetVar <- .checkArgLen(datasetVar, ncol(object), repN = TRUE,
class = c("factor", "character"))
if (!is.factor(datasetVar)) datasetVar <- factor(datasetVar)
datasetVar <- droplevels(datasetVar)
raw <- splitRmMiss(raw, datasetVar)
modal <- .checkArgLen(modal, length(raw), class = "character")
lig <- createLiger(raw, modal = modal, ...)
colDataCopy <- SummarizedExperiment::colData(object)
for (cdn in colnames(colDataCopy)) {
if (cdn %in% names(cellMeta(lig))) {
same <- identical(colDataCopy[[cdn]], cellMeta(lig, cdn))
if (same) next
cdnNew <- paste0("SCE_", cdn)
cli::cli_alert_warning(
"Variable name {.val {cdn}} in colData of SingleCellExperiment conflicts with liger default variables. Modified to {.val {cdnNew}}.")
} else {
cdnNew <- cdn
}
cellMeta(lig, cdnNew) <- colDataCopy[[cdn]]
}
for (rd in SingleCellExperiment::reducedDimNames(object)) {
cellMeta(lig, rd) <- SingleCellExperiment::reducedDim(object, rd)
}
return(lig)
}
#' @param assay Name of assay to use. Default \code{NULL} uses current active
#' assay.
#' @rdname as.liger
#' @method as.liger Seurat
#' @export
as.liger.Seurat <- function(
object,
datasetVar = NULL,
modal = NULL,
assay = NULL,
...
) {
raw <- .getSeuratData(object, layer = "counts", slot = "counts",
assay = assay)
if (!is.list(raw)) {
if (is.null(datasetVar)) {
if ("orig.ident" %in% colnames(object[[]])) {
datasetVar <- object[["orig.ident", drop = TRUE]]
} else {
datasetVar <- "Seurat"
}
} else if (length(datasetVar) == 1) {
if (datasetVar %in% colnames(object[[]])) {
datasetVar <- object[[datasetVar, drop = TRUE]]
}
}
datasetVar <- .checkArgLen(datasetVar, ncol(object), repN = TRUE,
class = c("factor", "character"))
if (!is.factor(datasetVar)) datasetVar <- factor(datasetVar)
datasetVar <- droplevels(datasetVar)
raw <- splitRmMiss(raw, datasetVar)
} else {
names(raw) <- gsub("counts.", "", names(raw))
}
datasetVar <- datasetVar %||% "Seurat"
modal <- .checkArgLen(modal, length(raw), class = "character")
lig <- createLiger(raw, modal = modal, ...)
colnames(object) <- colnames(lig)
for (cdn in colnames(object[[]])) {
if (cdn %in% names(cellMeta(lig))) {
same <- identical(object[[cdn, drop = TRUE]], cellMeta(lig, cdn))
if (same) next
cdnNew <- paste0("Seurat_", cdn)
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
}
cellMeta(lig, cdnNew) <- object[[cdn, drop = TRUE]]
}
for (rd in SeuratObject::Reductions(object)) {
mat <- object[[rd]][[]]
colnames(mat) <- seq_len(ncol(mat))
dimRed(lig, rd) <- mat
}
return(lig)
}
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# From other things to ligerDataset class ####
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#' @rdname as.ligerDataset
#' @export
#' @method as.ligerDataset ligerDataset
as.ligerDataset.ligerDataset <- function(
object,
modal = c("default", "rna", "atac", "spatial", "meth"),
...
) {
modal <- match.arg(modal)
newClass <- .modalClassDict[[modal]]
if (inherits(object, newClass)) return(object)
slotFromClass <- methods::slotNames(class(object))
slotToClass <- methods::slotNames(newClass)
if (any(!slotFromClass %in% slotToClass))
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)
newCallArgs[[s]] <- methods::slot(object, s)
}
do.call("new", newCallArgs)
}
#' @rdname as.ligerDataset
#' @export
#' @method as.ligerDataset default
as.ligerDataset.default <- function(
object,
modal = c("default", "rna", "atac", "spatial", "meth"),
...
) {
modal <- match.arg(modal)
createLigerDataset(object, modal, ...)
}
#' @rdname as.ligerDataset
#' @export
#' @method as.ligerDataset matrix
as.ligerDataset.matrix <- function(
object,
modal = c("default", "rna", "atac", "spatial", "meth"),
...
) {
modal <- match.arg(modal)
createLigerDataset(object, modal, ...)
}
#' @rdname as.ligerDataset
#' @export
#' @method as.ligerDataset Seurat
#' @param assay Name of assay to use. Default \code{NULL} uses current active
#' assay.
as.ligerDataset.Seurat <- function(
object,
modal = c("default", "rna", "atac", "spatial", "meth"),
assay = NULL,
...
) {
modal <- match.arg(modal)
mat <- .getSeuratData(object, "counts", "counts", assay = assay)
createLigerDataset(rawData = mat, modal = modal, ...)
}
#' @rdname as.ligerDataset
#' @export
#' @method as.ligerDataset SingleCellExperiment
as.ligerDataset.SingleCellExperiment <- function(
object,
modal = c("default", "rna", "atac", "spatial", "meth"),
...
) {
if (!requireNamespace("SingleCellExperiment", quietly = "TRUE")) # nocov start
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, ...)
}
###### AnnData object presented as H5AD file on disk is already supported with
###### H5-based ligerDataset object without having to create a AnnData object
###### in a running Python session first.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# From liger class to other things ####
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#' @title Convert between liger and Seurat object
#' @description
#' For converting a \linkS4class{liger} object to a Seurat object, the
#' \code{rawData}, \code{normData}, and \code{scaleData} from each dataset,
#' the \code{cellMeta}, \code{H.norm} and \code{varFeatures} slot will be
#' included. Compatible with V4 and V5. It is not recommended to use this
#' conversion if your \linkS4class{liger} object contains datasets from
#' various modalities.
#' @param object A \linkS4class{liger} object to be converted
#' @param assay Name of assay to store the data. Default \code{NULL} detects by
#' dataset modality. If the object contains various modality, default to
#' \code{"LIGER"}. Default dataset modality setting is understood as
#' \code{"RNA"}.
#' @param identByDataset Logical, whether to combine dataset variable and
#' default cluster labeling to set the Idents. Default \code{FALSE}.
#' @param merge Logical, whether to merge layers of different datasets into one.
#' Not recommended. Default \code{FALSE}.
#' @param by.dataset `r lifecycle::badge("superseded")`. Use
#' \code{identByDataset} instead.
#' @param nms `r lifecycle::badge("defunct")` Will be ignored because new object
#' structure does not have related problem.
#' @param renormalize `r lifecycle::badge("defunct")` Will be ignored because
#' since Seurat V5, layers of data can exist at the same time and it is better
#' to left it for users to do it by themselves.
#' @param use.liger.genes `r lifecycle::badge("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
#' layers for all datasets merged.
#' @examples
#' if (requireNamespace("SeuratObject", quietly = TRUE) &&
#' requireNamespace("Seurat", quietly = TRUE)) {
#' seu <- ligerToSeurat(pbmc)
#' }
ligerToSeurat <- function(
object,
assay = NULL,
identByDataset = FALSE,
merge = FALSE,
# Rename or defunct
nms = NULL,
renormalize = NULL,
use.liger.genes = NULL,
by.dataset = identByDataset
) {
.checkObjVersion(object)
.deprecateArgs(
list(by.dataset = "identByDataset"),
defunct = c("nms", "renormalize", "use.liger.genes"))
if (is.null(assay)) {
allModal <- toupper(modalOf(object))
if (all(allModal == allModal[1])) {
assay <- allModal[1]
} else {
assay <- "LIGER"
}
if (assay == "DEFAULT") assay <- "RNA"
}
rawDataList <- getMatrix(object, "rawData", returnList = TRUE)
rawDataList <- rawDataList[!sapply(rawDataList, is.null)]
if (isTRUE(merge)) rawDataList <- mergeSparseAll(rawDataList)
if (!length(rawDataList)) {
cli::cli_abort("rawData not found.")
}
Assay <- SeuratObject::CreateAssay5Object(rawDataList)
normDataList <- getMatrix(object, "normData", returnList = TRUE)
normDataList <- normDataList[!sapply(normDataList, is.null)]
if (isTRUE(merge)) {
normed <- mergeSparseAll(normDataList)
SeuratObject::LayerData(Assay, layer = "ligerNormData") <- normed
} else {
for (i in seq_along(normDataList)) {
layerName <- paste0("ligerNormData.", names(normDataList)[i])
SeuratObject::LayerData(Assay, layer = layerName) <- normDataList[[i]]
}
}
scaleDataList <- getMatrix(object, "scaleData", returnList = TRUE)
scaleDataList <- scaleDataList[!sapply(scaleDataList, is.null)]
if (isTRUE(merge)) {
scaled <- mergeSparseAll(scaleDataList)
SeuratObject::LayerData(Assay, layer = "ligerScaleData") <- scaled
} else {
for (i in seq_along(scaleDataList)) {
layerName <- paste0("ligerScaleData.", names(scaleDataList)[i])
SeuratObject::LayerData(Assay, layer = layerName) <- scaleDataList[[i]]
}
}
orig.ident <- object$dataset
idents <- defaultCluster(object)
if (is.null(idents)) {
idents <- orig.ident
} else {
if (isTRUE(identByDataset)) {
idents <- factor(paste0(as.character(orig.ident), "_",
as.character(idents)))
}
}
# Split normal data.frame compatible info and dimReds
metadata <- .DataFrame.as.data.frame(cellMeta(object))
dimReds <- dimReds(object)
srt <- Seurat::CreateSeuratObject(counts = Assay, assay = assay,
meta.data = metadata)
srt$orig.ident <- orig.ident
Seurat::Idents(srt) <- idents
# Attempt to get H.norm primarily. If it is NULL, then turn to H
h <- getMatrix(object, "H.norm") %||%
getMatrix(object, "H", returnList = TRUE)
if (is.list(h)) {
# Not from H.norm but H list. Only get merged when all datasets have H.
if (any(sapply(h, is.null))) h <- NULL
else h <- t(Reduce(cbind, h))
}
if (!is.null(h)) {
hDR <- SeuratObject::CreateDimReducObject(
embeddings = h,
loadings = getMatrix(object, "W", returnList = FALSE),
assay = assay,
misc = list(
H = getMatrix(object, "H", returnList = TRUE),
V = getMatrix(object, "V", returnList = TRUE)
),
key = "iNMF_"
)
srt[["inmf"]] <- hDR
}
for (var in names(dimReds)) {
dimred <- SeuratObject::CreateDimReducObject(
embeddings = dimReds[[var]],
assay = assay,
key = paste0(var, "_")
)
srt[[var]] <- dimred
}
Seurat::VariableFeatures(srt) <- varFeatures(object)
return(srt)
}
#' @rdname as.liger
#' @export
seuratToLiger <- as.liger.Seurat
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# From old version to new version ####
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#' Update old liger object to up-to-date structure
#' @description
#' Due to massive updates since rliger 2.0, old liger object structures are no
#' longer compatible with the current package. This function will update the
#' object to the latest structure.
#' @param object An object of any version of rliger
#' @param dimredName Name of the dimension reduction embedding to be stored.
#' Please see Details section.
#' @param clusterName Name of the clustering assignment variable to be stored.
#' Please see Details section.
#' @param h5FilePath Named character vector for all H5 file paths. Not required
#' for object run with in-memory analysis. For object containing H5-based
#' analysis (e.g. online iNMF), this must be supplied if the H5 file location is
#' different from that at creation time.
#' @details
#' Old liger object (<1.99.0) stores only one embedding at slot
#' \code{tsne.coords}. \code{dimredName} must be specified as a single
#' character. Pre-release version (1.99.0) stores multiple embeddings in
#' \code{cellMeta}. \code{dimredName} must be exact existing variable names in
#' \code{cellMeta} slot.
#'
#' Old liger object stores clustering assignment in slot \code{clusters}.
#' \code{clusterName} must be specified as a single character. Pre-release
#' version does not require this.
#' @return Updated liger object.
#' @export
#' @examples
#' \dontrun{
#' # Suppose you have a liger object of old version (<1.99.0)
#' newLig <- updateLigerObject(oldLig,
#' dimredName = "UMAP",
#' clusterName = "louvain")
#' }
updateLigerObject <- function(
object,
dimredName,
clusterName = "clusters",
h5FilePath = NULL
) {
objClass <- class(object)
if (objClass[1] != "liger") {
# DUE TO THE FACT THAT RLIGER PACKAGE HAS BEEN PREVIOUSLY NAMED BY
# 'liger' AND 'rliger2', `inherits()` WON'T WORK FOR OBJECTS CREATED
# WITH OLD NAMESPACES WHICH IS NOT INSTALLED.
cli::cli_abort("object is not of class {.cls liger}.")
}
attr(class(object), "package") <- "rliger"
# Instead of looking at the "version" number encoded in the object,
# More safe to check for slots
if (methods::.hasSlot(object, "raw.data") &&
methods::.hasSlot(object, "norm.data") &&
methods::.hasSlot(object, "scale.data")) {
# Old structure
cli::cli_alert_info("Detected {.cls liger} object with old-style structure.")
object <- updateOldLiger(object, dimredName = dimredName,
clusterName = clusterName,
h5FilePath = h5FilePath)
} else if (methods::.hasSlot(object, "datasets") &&
methods::.hasSlot(object, "cellMeta")) {
if (methods::.hasSlot(object, "dimReds")) {
# Current structure
cli::cli_alert_info("Detected {.cls liger} object with up-to-date structure.")
if (isH5Liger(object)) {
object <- restoreH5Liger(object, filePath = h5FilePath)
}
methods::slot(object, "version") <- package_version(version)
} else {
# 1.99.0 dev version structure
cli::cli_alert_info("Detected {.cls liger} object with pre-release structure.")
object <- updateRliger2NS(object, dimredName = dimredName)
}
}
return(object)
}
updateOldLiger <- function(
object,
dimredName,
clusterName = "clusters",
h5FilePath = NULL
) {
# Instead of having strict check points everywhere, this updating function
# focuses on constructing new structure with ligerDataset class, and then
# fill in as much information as possible. Old rliger package didn't have
# many checks so the given object might be somehow broken. We don't want to
# really refuse to update when users might really need it.
# First detect the set of datasets from all expression data slots and
# metadata slot, so that we don't miss any dataset partially removed for
# some reason.
if (missing(dimredName)) {
cli::cli_alert_warning("{.field dimredName} not specified. Setting default {.val embedding}.")
cli::cli_alert_info("It can be renamed with command: {.code names(dimReds(obj)) <- 'newName'}")
dimredName <- "embedding"
}
datasetNames <- Reduce(union, list(
names(methods::slot(object, "raw.data")),
names(methods::slot(object, "norm.data"))
))
if (!is.null(h5FilePath)) {
if (is.null(names(h5FilePath)) ||
!is.character(h5FilePath)) {
cli::cli_abort("{.field h5FilePath} must be a named character vector.")
}
}
ldList <- list()
ldmiscList <- list()
for (i in seq_along(datasetNames)) {
dn <- datasetNames[i]
cli::cli_alert_info("Constructing dataset {.val {dn}}")
components <- list(Class = "ligerDataset")
# Old liger structure does not have modality presets, so
# Class = "ligerDataset"
h5meta <- NULL
if (methods::.hasSlot(object, "h5file.info")) {
h5meta <- methods::slot(object, "h5file.info")[[dn]]
}
if (!is.null(h5meta)) {
# H5 mode
dsh5path <- NULL
if (!dn %in% names(h5FilePath)) {
dsh5path <- h5meta$file.path
} else {
dsh5path <- h5FilePath[[dn]]
}
if (!file.exists(dsh5path)) {
cli::cli_alert_danger("H5 file not found for {.val {dn}}: {.file {dsh5path}}. Skipped.")
next
}
components <- tryCatch({
h5file <- hdf5r::H5File$new(dsh5path, mode = "r+")
formatType <- h5meta$format.type
if (formatType == "10X") {
barcodesPath <- "matrix/barcodes"
genePath <- "matrix/features/name"
xPath <- "matrix/data"
iPath <- "matrix/indices"
pPath <- "matrix/indptr"
} else if (formatType == "AnnData") {
barcodesPath <- sprintf("obs/%s", hdf5r::h5attr(h5file[['obs']], "_index"))
genePath <- sprintf("raw/var/%s", hdf5r::h5attr(h5file[['raw/var']], "_index"))
xPath <- "raw/X/data"
iPath <- "raw/X/indices"
pPath <- "raw/X/indptr"
} else {
cli::cli_abort("Unsupported H5 format type {.val {formatType}}.")
}
rn <- h5file[[genePath]][]
cn <- h5file[[barcodesPath]][]
normPath <- if ("norm.data" %in% names(h5file)) "norm.data" else NULL
if ("scale.data" %in% names(h5file)) {
cli::cli_alert_warning("Old H5 based scaled data format is no longer supported.")
cli::cli_alert_info("Please rerun {.code scaleNotCenter()} after loading.")
}
h5metaNew <- list(
H5File = h5file,
filename = h5file$filename,
formatType = formatType,
indicesName = iPath,
indptrName = pPath,
barcodesName = barcodesPath,
genesName = genePath,
rawData = xPath,
normData = normPath,
scaleData = NULL
)
components$rawData <- h5file[[xPath]]
if (!is.null(normPath)) components$normData <- h5file[[normPath]]
components$h5fileInfo <- h5metaNew
components$colnames <- cn
components$rownames <- rn
components
}, error = function(e) {
cli::cli_alert_danger("Failed to extract necessary H5 information for {.val {dn}}. Skipped.")
cli::cli_alert_danger(e$message)
return(list())
})
if (length(components) == 0)
next
} else {
# in-memory mode
ldmiscList[[dn]] <- list()
components$rawData <- methods::slot(object, "raw.data")[[dn]]
components$normData <- methods::slot(object, "norm.data")[[dn]]
if (!is.null(components$rawData)) {
cn <- colnames(components$rawData)
rn <- rownames(components$rawData)
} else if (!is.null(components$normData)) {
cn <- colnames(components$normData)
rn <- rownames(components$normData)
} else {
cli::cli_alert_danger("Failed because raw and normalized matrices are both missing for cell and feature name detection. Skipped.")
next
}
components$colnames <- cn
components$rownames <- rn
# Transpose and sparsify scale data
ldScaleData <- methods::slot(object, "scale.data")[[dn]]
if (!.skip(ldScaleData, "scaled data")) {
ldScaleData <- t(methods::as(ldScaleData, "CsparseMatrix"))
if (!all(rownames(ldScaleData) %in% rn)) {
cli::cli_alert_warning("Scaled data contains features that do not exist in raw data. Including into unstructured misc data.")
ldmiscList[[dn]]$scaleData <- ldScaleData
}
}
if (methods::.hasSlot(object, "scale.unshared.data")) {
ldScaleUnsharedData <- methods::slot(object, "scale.unshared.data")[[dn]]
if (!.skip(ldScaleUnsharedData, "scaled data of unshared features")) {
ldScaleUnsharedData <- t(methods::as(ldScaleUnsharedData, "CsparseMatrix"))
components$scaleUnsharedData <- ldScaleUnsharedData
}
}
}
if (methods::.hasSlot(object, "var.unshared.features")) {
vuf <- methods::slot(object, "var.unshared.features")[[dn]]
if (!.skip(vuf, "unshared features")) {
components$varUnsharedFeatures <- vuf
}
}
ldH <- methods::slot(object, "H")[[dn]]
if (!.skip(ldH, "factorized H matrix")) {
components$H <- t(ldH)
}
ldV <- methods::slot(object, "V")[[dn]]
if (!.skip(ldV, "factorized V matrix")) {
ldV <- t(ldV)
if (is.null(rownames(ldV)) ||
!all(rownames(ldV) %in% rn)) {
cli::cli_alert_warning("Invalid feature names in factorized V matrix. Including into unstructured misc data.")
ldmiscList[[dn]]$V <- ldV
} else {
components$V <- ldV
}
}
if (methods::.hasSlot(object, "A")) {
ldA <- methods::slot(object, "A")[[dn]]
if (!.skip(ldA, "onlineINMF intermediate A matrix")) {
components$A <- t(ldA)
}
}
if (methods::.hasSlot(object, "B")) {
ldB <- methods::slot(object, "B")[[dn]]
if (!.skip(ldB, "onlineINMF intermediate B matrix")) {
components$B <- t(ldB)
}
}
if (methods::.hasSlot(object, "U")) {
ldU <- methods::slot(object, "U")[[dn]]
if (!.skip(ldU, "UINMF factorized U matrix")) {
components$U <- t(ldU)
}
}
components$featureMeta <- S4Vectors::DataFrame(row.names = rn)
ldList[[dn]] <- do.call("new", components)
cli::cli_alert_success("Constructed dataset {.val {dn}}")
}
ligerComp <- list(Class = "liger",
datasets = ldList,
uns = list(defaultCluster = clusterName,
defaultDimRed = dimredName,
datasetMist = ldmiscList),
version = utils::packageVersion("rliger"))
# All other all-cell individual elements
vg <- methods::slot(object, "var.genes")
if (!.skip(vg, "variable genes")) {
allisec <- Reduce(intersect, lapply(ldList, rownames))
if (any(!vg %in% allisec)) {
diff <- setdiff(vg, allisec)
cli::cli_alert_warning("{.val {length(diff)}} variable gene{?s} not found in all datasets ({.val {diff}}). Including all into unstructured misc data.")
ligerComp$uns$varFeatures <- diff
} else {
ligerComp$varFeatures <- vg
}
}
hnorm <- methods::slot(object, "H.norm")
if (!.skip(hnorm, "aligned H matrix")) {
ligerComp$H.norm <- hnorm
}
w <- methods::slot(object, "W")
if (!.skip(w, "factorized W matrix")) {
ligerComp$W <- t(w)
}
aggData <- methods::slot(object, "agg.data")
if (!.skip(aggData, "data aggregated within clusters")) {
cli::cli_alert_warning("Data aggregated within clusters is included into unstructured misc data.")
ligerComp$uns$agg.data <- aggData
}
param <- methods::slot(object, "parameters")
if (!.skip(param, "parameters")) {
cli::cli_alert_warning("Parameters are included into unstructured misc data.")
ligerComp$uns$parameters <- param
}
snf <- methods::slot(object, "snf")
if (!.skip(snf, "SNF intermediate information")) {
cli::cli_alert_warning("SNF info is included into unstructured misc data.")
ligerComp$uns$snf <- snf
}
dimredList <- list()
tsnecoords <- methods::slot(object, "tsne.coords")
if (!.skip(tsnecoords, "dimension reduction")) {
dimredList[[dimredName]] <- tsnecoords
ligerComp$dimReds <- dimredList
}
# Finally for metadata
metadata <- methods::slot(object, "cell.data")
metadata$dataset <- NULL
alignCluster <- methods::slot(object, "alignment.clusters")
if (!.skip(alignCluster, "alignment clusters")) {
metadata <- tryCatch({
metadata[names(alignCluster), "alignCluster"] <- alignCluster
metadata
}, error = function(e) {
cli::cli_alert_danger("Failed to add alignment clusters to cellMeta. Included into unstructured misc data.")
cli::cli_alert_danger(e$message)
ligerComp$uns$alignment_clusters <- alignCluster
return(metadata)
})
}
clusters <- methods::slot(object, "clusters")
if (!.skip(clusters, "cluster assignment")) {
metadata <- tryCatch({
metadata[names(clusters), clusterName] <- clusters
metadata
}, error = function(e) {
cli::cli_alert_danger("Failed to add cluster assignment to cellMeta. Included into unstructured misc data.")
cli::cli_alert_danger(e$message)
ligerComp$uns[[clusterName]] <- clusters
return(metadata)
})
}
# Make some effort to fix this part
bcOrignal <- rownames(metadata)
bcNew <- unlist(lapply(ldList, colnames), use.names = FALSE)
if (any(!bcNew %in% bcOrignal)) {
diff <- setdiff(bcNew, bcOrignal)
cli::cli_alert_warning("{.val {length(diff)}} cell{?s} from expression matrices not found in metadata. Filling in NA{?s}.")
fill <- data.frame(row.names = diff)
for (i in seq(ncol(metadata))) {
fill[[colnames(metadata)[i]]] <- NA
}
metadata <- rbind(metadata, fill)
}
if (any(!bcOrignal %in% bcNew)) {
diff <- setdiff(bcOrignal, bcNew)
cli::cli_alert_warning("{.val {length(diff)}} cell{?s} from metadata not found in expression matrices. Cutting out into unstructured misc data.")
ligerComp$uns$cell.data.missing <- metadata[diff, ]
}
metadata <- metadata[bcNew, ]
metadata <- cbind(
dataset = factor(
rep(names(ldList), sapply(ldList, ncol)),
levels = names(ldList)
),
metadata
)
ligerComp$cellMeta <- S4Vectors::DataFrame(metadata)
new <- tryCatch({
do.call("new", ligerComp)
},
error = function(e) {
cli::cli_alert_danger("Failed to create new liger object basing on existing information")
cli::cli_alert_danger(e$message)
cli::cli_alert_info("Returning list structure of all extracted information.")
return(ligerComp)
})
return(new)
}
.skip <- function(component, name) {
if (length(component) == 0) {
cli::cli_alert_warning("No {name} found. Skipped.")
return(TRUE)
} else {
return(FALSE)
}
}
updateRliger2NS <- function(
object,
dimredName,
clusterName = NULL,
h5FilePath = NULL
) {
# Slots that contain things of rliger2 namespace
datasetList <- list()
drList <- list()
commandList <- list()
cm <- methods::slot(object, "cellMeta")
if (missing(dimredName)) {
cli::cli_alert_warning("{.field dimredName} not specified. Keeping them all in {.field cellMeta} if they exist.")
multiDimVarIdx <- sapply(cm, function(v) !is.null(dim(v)))
guessArg <- paste0('"', names(cm)[multiDimVarIdx], '"', collapse = ", ")
cli::cli_alert_info("Guess you need: {.code dimredName = c({guessArg})}")
dimredName <- NULL
}
for (i in dimredName) {
if (i %in% colnames(cm)) {
drList[[i]] <- cm[[i]]
cm[[i]] <- NULL
} else {
cli::cli_alert_danger("Requested dimension reduction {.val {i}} does not exist. Skipped")
}
}
for (i in names(methods::slot(object, "datasets"))) {
ld <- methods::slot(object, "datasets")[[i]]
basics <- list(
rawData = methods::slot(ld, "rawData"),
normData = methods::slot(ld, "normData"),
scaleData = methods::slot(ld, "scaleData"),
H = methods::slot(ld, "H"),
V = methods::slot(ld, "V"),
A = methods::slot(ld, "A"),
B = methods::slot(ld, "B"),
varUnsharedFeatures = methods::slot(ld, "varUnsharedFeatures"),
scaleUnsharedData = methods::slot(ld, "scaleUnsharedData"),
U = methods::slot(ld, "U"),
h5fileInfo = methods::slot(ld, "h5fileInfo"),
featureMeta = methods::slot(ld, "featureMeta"),
colnames = methods::slot(ld, "colnames"),
rownames = methods::slot(ld, "rownames")
)
if (!is.null(basics$scaleData)) {
basics$scaleData <- methods::as(basics$scaleData, "CsparseMatrix")
}
if (!is.null(basics$scaleUnsharedData)) {
basics$scaleUnsharedData <- methods::as(basics$scaleUnsharedData,
"CsparseMatrix")
}
if (class(ld)[1] == "ligerATACDataset") {
basics <- c(basics, list(
Class = "ligerATACDataset",
rawPeak = methods::slot(ld, "rawPeak"),
normPeak = methods::slot(ld, "normPeak")
))
} else if (class(ld)[1] == "ligerRNADataset") {
basics <- c(basics, list(
Class = "ligerRNADataset"
))
} else if (class(ld)[1] == "ligerMethDataset") {
basics <- c(basics, list(
Class = "ligerMethDataset"
))
} else if (class(ld)[1] == "ligerSpatialDataset") {
basics <- c(basics, list(
Class = "ligerSpatialDataset",
coordinate = methods::slot(ld, "coordinate")
))
} else {
basics <- c(basics, list(
Class = "ligerDataset"
))
}
datasetList[[i]] <- do.call("new", basics)
}
for (i in names(methods::slot(object, "commands"))) {
cmd <- methods::slot(object, "commands")[[i]]
commandList[[i]] <- methods::new(
"ligerCommand",
funcName = methods::slot(cmd, "funcName"),
time = methods::slot(cmd, "time"),
call = methods::slot(cmd, "call"),
parameters = methods::slot(cmd, "parameters"),
objSummary = methods::slot(cmd, "objSummary"),
ligerVersion = methods::slot(cmd, "ligerVersion"),
dependencyVersion = methods::slot(cmd, "dependencyVersion")
)
}
new <- methods::new(
"liger",
datasets = datasetList,
cellMeta = cm,
varFeatures = methods::slot(object, "varFeatures"),
W = methods::slot(object, "W"),
H.norm = methods::slot(object, "H.norm"),
uns = methods::slot(object, "uns"),
commands = commandList,
version = utils::packageVersion("rliger")
)
for (i in names(drList)) {
dimRed(new, i) <- drList[[i]]
}
new
}
#' Convert old liger object to latest version
#' @param object \code{liger} object from rliger version <1.99.0
#' @param dimredName The name of variable in \code{cellMeta} slot to store the
#' dimensionality reduction matrix, which originally located in
#' \code{tsne.coords} slot. Default \code{"tsne.coords"}.
#' @param clusterName The name of variable in \code{cellMeta} slot to store the
#' clustering assignment, which originally located in \code{clusters} slot.
#' Default \code{"clusters"}.
#' @param h5FilePath Named list, to specify the path to the H5 file of each
#' dataset if location has been changed. Default \code{NULL} looks at the file
#' paths stored in object.
#' @export
#' @examples
#' \dontrun{
#' # Suppose you have a liger object of old version (<1.99.0)
#' newLig <- convertOldLiger(oldLig)
#' }
convertOldLiger <- updateLigerObject
# convertOldLiger <- function( # nocov start
# object,
# dimredName,
# clusterName = "clusters",
# h5FilePath = NULL
# ) {
# ver1990 <- package_version("1.99.0")
# if (object@version == ver1990) {
# return(rliger2_to_rliger_namespace(object, dimredName = dimredName))
# }
# if (object@version > ver1990) return(object)
# tryCatch(
# {
# if (inherits(object@raw.data[[1]], "H5File")) {
# ldList <- convertOldLiger.H5(object, h5FilePath = h5FilePath)
# } else {
# ldList <- convertOldLiger.mem(object)
# }
# },
# error = function(e) {
# print(e)
# cli::cli_alert_danger(
# "Conversion failed. Please check the error message above."
# )
# cli::cli_alert_info(
# "For 'inconsistent ID' error, please use an old version of {.pkg rliger} and manually fix the rownames/colnames matching."
# )
# cli::cli_alert("{.code dimnames()} of raw.data and norm.data must be identical for each dataset.")
# cli::cli_alert("{.code rownames()} of scale.data and H must be identical to the colnames of raw.data, for each dataset.")
# cli::cli_alert("{.code colnames()} of scale.data, V and U (if available) must be identical to the var.genes.")
# }
# )
#
# cellMeta <- object@cell.data
# varFeatures <- object@var.genes
# cellID <- unlist(lapply(ldList, colnames), use.names = FALSE)
# # 4. Wrap up liger object
# cellMeta <- S4Vectors::DataFrame(cellMeta)
# oldID <- rownames(cellMeta)
# # TODO: check default prototype of tsne.coords and clusters.
# dimred <- object@tsne.coords[oldID, , drop = FALSE]
# colnames(dimred) <- paste0(dimredName, "_", seq_len(ncol(dimred)))
# cellMeta$barcode <- oldID
# cellMeta[[clusterName]] <- object@clusters[rownames(cellMeta)]
# rownames(cellMeta) <- cellID
# hnorm <- object@H.norm
# rownames(hnorm) <- cellID
# newObj <- createLiger(ldList, W = t(object@W), H.norm = hnorm,
# varFeatures = varFeatures, cellMeta = cellMeta,
# addPrefix = FALSE, removeMissing = FALSE)
# dimRed(newObj, dimredName) <- dimred
# defaultCluster(newObj) <- clusterName
# defaultDimRed(newObj) <- dimredName
# return(newObj)
# }
#
# convertOldLiger.mem <- function(object) {
# dataLists <- list()
# if (.hasSlot(object, "raw.data")) dataLists$rawData <- object@raw.data
# if (.hasSlot(object, "norm.data")) dataLists$normData <- object@norm.data
# if (.hasSlot(object, "scale.data")) dataLists$scaleData <- object@scale.data
# if (.hasSlot(object, "H")) dataLists$H <- object@H
# if (.hasSlot(object, "V")) dataLists$V <- object@V
# if (.hasSlot(object, "U")) dataLists$U <- object@U
# # 1. Deal with cell metadata which establish a correct mapping of cell
# # barcodes and datasets belonging
# allDatasets <- Reduce(union, lapply(dataLists, names))
# cellMeta <- object@cell.data
# cellMetaDatasets <- unique(as.vector(cellMeta$dataset))
# if (!identical(sort(allDatasets), sort(cellMetaDatasets))) {
# # Datasets entry for matrices don't match with cell metadata
# # Only take the intersection
# allDatasets <- intersect(allDatasets, cellMetaDatasets)
# cellMeta <- cellMeta[cellMeta[["dataset"]] %in% allDatasets, ]
# }
#
# # Split `dataLists` by dataset
# datasetLists <- list()
# for (d in allDatasets) {
# for (slot in names(dataLists)) {
# datasetLists[[d]][[slot]] <- dataLists[[slot]][[d]]
# }
# }
#
# # For each existing dataset
# ldList <- list()
# for (d in allDatasets) {
# # "BC" for barcodes
# # 2. Check and clean up cell barcodes and feature idx issue
# cellMetaBC <- rownames(cellMeta)[cellMeta$dataset == d]
# features <- NULL
# varFeatures <- object@var.genes
# dataList <- datasetLists[[d]]
#
# # Check cell barcodes
# bcPassing <- .checkIDIdentical(
# ref = cellMetaBC,
# onCol = dataList[c("rawData", "normData")],
# onRow = dataList[c("scaleData", "H")]
# )
#
# # Check raw, norm data features
# if (!is.null(dataList$rawData)) features <- rownames(dataList$rawData)
# else features <- rownames(dataList$normData)
# if (is.null(features)) {
# cli::cli_abort(
# "Cannot detect feature names for dataset {.val {d}}."
# )
# }
# ftPassing <- .checkIDIdentical(
# ref = features,
# onRow = dataList[c("rawData", "normData")]
# )
#
# # Check var features
# if (!is.null(dataList$V) &&
# is.null(colnames(dataList$V)) &&
# !is.null(varFeatures)) {
# ## This should not happen but unfortunately, old `V`s might not
# ## have var features as their colnames
# colnames(dataList$V) <- varFeatures
# }
# hvgPassing <- .checkIDIdentical(
# ref = varFeatures,
# onCol = dataList[c("scaleData", "V", "U")]
# )
#
# # Remove data that has inconsistent information
# passing <- .combinePassingSignal(names(dataList),
# bcPassing, ftPassing, hvgPassing)
# dataList <- dataList[passing]
# for (s in c("scaleData", "H", "V", "U")) {
# if (!is.null(dataList[[s]])) {
# dataList[[s]] <- t(dataList[[s]])
# }
# }
# # 3. Construct ligerDataset objects for each dataset
# ldList[[d]] <- do.call(createLigerDataset, dataList)
# if (!all(startsWith(colnames(ldList[[d]]), d)))
# colnames(ldList[[d]]) <- paste0(d, "_", colnames(ldList[[d]]))
# }
# return(ldList)
# }
#
# convertOldLiger.H5 <- function(object, h5FilePath = NULL) {
# 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
# if (.hasSlot(object, "U")) dataLists$U <- object@U
#
# # 1. Deal with cell metadata which establish a correct mapping of cell
# # barcodes and datasets belonging
# allDatasets <- Reduce(union, lapply(dataLists, names))
# cellMeta <- object@cell.data
# cellMetaDatasets <- unique(as.vector(cellMeta$dataset))
# if (!identical(sort(allDatasets), sort(cellMetaDatasets))) {
# # Datasets entry for matrices don't match with cell metadata
# # Only take the intersection
# allDatasets <- intersect(allDatasets, cellMetaDatasets)
# cellMeta <- cellMeta[cellMeta[["dataset"]] %in% allDatasets, ]
# }
#
# # Split `dataLists` by dataset
# datasetLists <- list()
# for (d in allDatasets) {
# for (slot in names(dataLists)) {
# datasetLists[[d]][[slot]] <- dataLists[[slot]][[d]]
# }
# }
#
# # For each existing dataset
# ldList <- list()
# for (d in allDatasets) {
# # "BC" for barcodes
# # 2. Check and clean up cell barcodes and feature idx issue
# cellMetaBC <- rownames(cellMeta)[cellMeta$dataset == d]
# #features <- NULL
# varFeatures <- object@var.genes
# dataList <- datasetLists[[d]]
#
# # Check cell barcodes
# bcPassing <- .checkIDIdentical(
# ref = cellMetaBC,
# #onCol = dataList[c("rawData", "normData")],
# onRow = dataList[c("H")]
# )
#
# # Check raw, norm data features
# # 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.")
# # next
# # }
# # ftPassing <- .checkIDIdentical(
# # ref = features,
# # onRow = dataList[c("rawData", "normData")]
# # )
#
# # Check var features
# if (!is.null(dataList$V) &&
# is.null(colnames(dataList$V)) &&
# !is.null(varFeatures)) {
# ## This should not happen but unfortunately, old `V`s might not
# ## have var features as their colnames
# colnames(dataList$V) <- varFeatures
# }
# hvgPassing <- .checkIDIdentical(
# ref = varFeatures,
# onCol = dataList[c("V", "U")]
# )
#
# # Remove data that has inconsistent information
# passing <- .combinePassingSignal(names(dataList),
# bcPassing, hvgPassing)
# dataList <- dataList[passing]
# for (s in c("H", "V", "U")) {
# if (!is.null(dataList[[s]])) {
# dataList[[s]] <- t(dataList[[s]])
# }
# }
# # 3. Construct H5 ligerDataset objects for each dataset
# if (!is.null(h5FilePath[[d]])) h5Path <- h5FilePath[[d]]
# else h5Path <- object@h5file.info[[d]]$file.path
# if (!hdf5r::is_hdf5(name = 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(
# list(h5file = h5Path, formatType = h5Format),
# dataList
# ))
# colnames(ldList[[d]]) <- paste0(d, "_", colnames(ldList[[d]]))
#
# # 4. Check for potential existing processed result
# newSlotNameMap <- list(norm.data = "normData",
# "scale.data" = "scaleData",
# "scale.unshared.data" = "scaleUnsharedData")
# for (s in c("norm.data", "scale.data", "scale.unshared.data")) {
# h5file <- getH5File(ldList[[d]])
# if (h5file$link_exists(s)) {
# h5fileInfo(ldList[[d]], newSlotNameMap[[s]], check = FALSE) <- s
# }
# }
# }
#
# return(ldList)
# }
#
# .checkIDIdentical <- function(
# ref,
# onCol = NULL,
# onRow = NULL
# ) {
# # ref - a character vector as a reference
# # onCol - a list of matrix, where the colnames should match to reference
# # onRow - a list of matrix, where the rownames should match to reference
# colPassing <- rep(TRUE, length(onCol))
# names(colPassing) <- names(onCol)
# for (slot in names(onCol)) {
# if (is.na(slot)) next
# if (!identical(colnames(onCol[[slot]]), ref)) {
# cli::cli_alert_danger("Inconsistent column ID in slot {slot}.")
# colPassing[slot] <- FALSE
# }
# }
# rowPassing <- rep(TRUE, length(onRow))
# names(rowPassing) <- names(onRow)
# for (slot in names(onRow)) {
# if (is.na(slot)) next
# if (!identical(rownames(onRow[[slot]]), ref)) {
# cli::cli_alert_danger("Inconsistent row ID in slot {slot}.")
# rowPassing[slot] <- FALSE
# }
# }
# return(c(colPassing, rowPassing))
# }
#
# .combinePassingSignal <- function(slotNames, ...) {
# passings <- list(...)
# passings <- lapply(passings, function(x) {
# x <- x[slotNames]
# names(x) <- slotNames
# x[is.na(x)] <- TRUE
# x
# })
# Reduce("&", passings)
# }
# .hasSlot <- function(object, name) {
# tryCatch(
# expr = {
# methods::slot(object, name)
# return(TRUE)
# },
# error = function(e) {
# cli::cli_alert_info("Skipped slot {name} which is not available.")
# return(FALSE)
# },
# warining = function(w) {
# cli::cli_alert_warning(w)
# return(FALSE)
# }
# )
# } # nocov end
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.