From f7b834193193887a896804f0faea95464eb33d45 Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 11:45:58 +0200 Subject: [PATCH 01/19] add rworkflows --- .github/workflows/R-CMD-check.yaml | 51 ----------------------- .github/workflows/lint.yaml | 2 +- .github/workflows/pkgdown.yaml | 50 ---------------------- .github/workflows/rworkflows.yml | 58 ++++++++++++++++++++++++++ DESCRIPTION | 4 +- NAMESPACE | 1 + NEWS.md | 10 +++++ R/setup_conda.R | 29 +++++++++++++ _pkgdown.yml | 4 -- man/setup_conda.Rd | 67 ++++++++++++++++++++++++++++++ 10 files changed, 168 insertions(+), 108 deletions(-) delete mode 100644 .github/workflows/R-CMD-check.yaml delete mode 100644 .github/workflows/pkgdown.yaml create mode 100644 .github/workflows/rworkflows.yml create mode 100644 R/setup_conda.R delete mode 100644 _pkgdown.yml create mode 100644 man/setup_conda.Rd diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml deleted file mode 100644 index a804110c..00000000 --- a/.github/workflows/R-CMD-check.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help -on: push - -name: R-CMD-check - -jobs: - R-CMD-check: - runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - - strategy: - fail-fast: false - matrix: - config: - - {os: macos-latest, r: 'release'} - - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} - - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes - - steps: - - uses: actions/checkout@v3 - - - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - r-version: ${{ matrix.config.r }} - http-user-agent: ${{ matrix.config.http-user-agent }} - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::rcmdcheck - needs: check - - - name: Install Python dependencies for reticulate - run: | - reticulate::install_miniconda() - reticulate::py_install(c("anndata", "scanpy"), pip = TRUE) - shell: Rscript {0} - - - uses: r-lib/actions/check-r-package@v2 - with: - upload-snapshots: true diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 7b2ef39c..0d5636ea 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,7 +10,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml deleted file mode 100644 index 20246d9c..00000000 --- a/.github/workflows/pkgdown.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - release: - types: [published] - workflow_dispatch: - -name: pkgdown - -jobs: - pkgdown: - runs-on: ubuntu-latest - # Only restrict concurrency for non-PR jobs - concurrency: - group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - permissions: - contents: write - steps: - - uses: actions/checkout@v3 - - - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: local::. - needs: website - - - name: Build site - run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) - shell: Rscript {0} - - - name: Deploy to GitHub pages 🚀 - if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.4.1 - with: - clean: false - repository-name: data-intuitive/anndataR - branch: gh-pages - folder: docs - token: ${{ secrets.GTHB_PAT }} diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml new file mode 100644 index 00000000..fb42252d --- /dev/null +++ b/.github/workflows/rworkflows.yml @@ -0,0 +1,58 @@ +name: rworkflows +'on': + push: + branches: + - main + pull_request: + branches: + - main +jobs: + rworkflows: + permissions: + contents: write + runs-on: ${{ matrix.config.os }} + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + container: ${{ matrix.config.cont }} + strategy: + fail-fast: ${{ false }} + matrix: + config: + - os: ubuntu-latest + bioc: devel + r: auto + cont: bioconductor/bioconductor_docker:devel + rspm: https://packagemanager.rstudio.com/cran/__linux__/latest/release + - os: ubuntu-latest + bioc: release + r: auto + cont: bioconductor/bioconductor_docker:release + http-user-agent: 'release' + - os: macOS-latest + bioc: release + r: auto + cont: ~ + rspm: ~ + - os: windows-latest + bioc: release + r: auto + cont: ~ + rspm: ~ + steps: + - uses: neurogenomics/rworkflows@master + with: + run_bioccheck: ${{ true }} + run_rcmdcheck: ${{ true }} + as_cran: ${{ false }} + run_vignettes: ${{ true }} + has_testthat: ${{ true }} + run_covr: ${{ true }} + run_pkgdown: ${{ true }} + has_runit: ${{ false }} + has_latex: ${{ false }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run_docker: ${{ false }} + docker_user: username + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + runner_os: ${{ runner.os }} + cache_version: cache-v1 + enable_act: ${{ false }} diff --git a/DESCRIPTION b/DESCRIPTION index d8629c3d..08dc6bbc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: anndataR Title: AnnData interoperability in R -Version: 0.0.0.9000 +Version: 0.99.0 Authors@R: c( person("Robrecht", "Cannoodt", , "rcannood@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3641-729X", github = "rcannood")), @@ -46,4 +46,4 @@ Config/Needs/website: pkgdown, tibble, knitr, rprojroot, stringr, readr, Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE, r6 = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.2.3.9000 diff --git a/NAMESPACE b/NAMESPACE index 4f50a0ed..98fe2220 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ export(dummy_data) export(from_Seurat) export(from_SingleCellExperiment) export(read_h5ad) +export(setup_conda) export(to_HDF5AnnData) export(to_InMemory) export(to_Seurat) diff --git a/NEWS.md b/NEWS.md index 8bb1614c..0e7de359 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,15 @@ +# anndataR 0.99.0 + +## New features + +- New function `setup_conda` automatically installs miniconda + and sets up conda env: #97 +- Change version to Bioc-recommended devel version: 0.99.0 + # anndataR 0.1.0 +## New features + Initial release of anndataR, providing support for working with AnnData objects in R. Feature list: diff --git a/R/setup_conda.R b/R/setup_conda.R new file mode 100644 index 00000000..a8d0867f --- /dev/null +++ b/R/setup_conda.R @@ -0,0 +1,29 @@ +#' Setup conda +#' +#' Install miniconda and setup a conda env to use python dependencies. +#' @inheritParams reticulate::install_miniconda +#' @inheritParams reticulate::py_install +#' @inheritDotParams reticulate::py_install +#' @returns Null +#' +#' @export +#' @examples +#' \dontrun{ +#' setup_conda() +#' } +setup_conda <- function(path = reticulate::miniconda_path(), + update = TRUE, + force = FALSE, + packages = c("anndata", "scanpy"), + ...){ + + requireNamespace("reticulate") + if(!reticulate::py_module_available(module = "anndata")){ + reticulate::install_miniconda(path = path, + update = update, + force = force) + reticulate::py_install(packages = packages, + pip = TRUE, + ...) + } +} diff --git a/_pkgdown.yml b/_pkgdown.yml deleted file mode 100644 index 455f90d8..00000000 --- a/_pkgdown.yml +++ /dev/null @@ -1,4 +0,0 @@ -url: https://scverse.org/anndataR/ -template: - bootstrap: 5 - diff --git a/man/setup_conda.Rd b/man/setup_conda.Rd new file mode 100644 index 00000000..210b5e8b --- /dev/null +++ b/man/setup_conda.Rd @@ -0,0 +1,67 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup_conda.R +\name{setup_conda} +\alias{setup_conda} +\title{Setup conda} +\usage{ +setup_conda( + path = reticulate::miniconda_path(), + update = TRUE, + force = FALSE, + packages = c("anndata", "scanpy"), + ... +) +} +\arguments{ +\item{path}{The location where Miniconda is (or should be) installed. Note +that the Miniconda installer does not support paths containing spaces. See +\link[reticulate]{miniconda_path} for more details on the default path used by \code{reticulate}.} + +\item{update}{Boolean; update to the latest version of Miniconda after +installation?} + +\item{force}{Boolean; force re-installation if Miniconda is already installed +at the requested path?} + +\item{packages}{A vector of Python packages to install.} + +\item{...}{ + Arguments passed on to \code{\link[reticulate:py_install]{reticulate::py_install}} + \describe{ + \item{\code{envname}}{The name, or full path, of the environment in which Python +packages are to be installed. When \code{NULL} (the default), the active +environment as set by the \code{RETICULATE_PYTHON_ENV} variable will be used; +if that is unset, then the \code{r-reticulate} environment will be used.} + \item{\code{method}}{Installation method. By default, "auto" automatically finds a +method that will work in the local environment. Change the default to force +a specific installation method. Note that the "virtualenv" method is not +available on Windows.} + \item{\code{conda}}{The path to a \code{conda} executable. Use \code{"auto"} to allow +\code{reticulate} to automatically find an appropriate \code{conda} binary. +See \strong{Finding Conda} and \code{\link[reticulate:conda_binary]{conda_binary()}} for more details.} + \item{\code{python_version}}{The requested Python version. Ignored when attempting +to install with a Python virtual environment.} + \item{\code{pip}}{Boolean; use \code{pip} for package installation? This is only relevant +when Conda environments are used, as otherwise packages will be installed +from the Conda repositories.} + \item{\code{pip_ignore_installed,ignore_installed}}{Boolean; whether pip should +ignore previously installed versions of the requested packages. Setting +this to \code{TRUE} causes pip to install the latest versions of all +dependencies into the requested environment. This ensure that no +dependencies are satisfied by a package that exists either in the site +library or was previously installed from a different--potentially +incompatible--distribution channel. (\code{ignore_installed} is an alias for +\code{pip_ignore_installed}, \code{pip_ignore_installed} takes precedence).} + }} +} +\value{ +Null +} +\description{ +Install miniconda and setup a conda env to use python dependencies. +} +\examples{ +\dontrun{ +setup_conda() +} +} From 6b458f343b79b3704d4fad3f4f9a36d7e67d9439 Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 11:47:54 +0200 Subject: [PATCH 02/19] Add rworkflows to trigger branches --- .github/workflows/rworkflows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index fb42252d..6ac515a5 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -3,9 +3,11 @@ name: rworkflows push: branches: - main + - rworkflows pull_request: branches: - main + - rworkflows jobs: rworkflows: permissions: From ad024528ea3f4516d6bdb40f7fa07df418d75c76 Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 11:55:07 +0200 Subject: [PATCH 03/19] update rworkflows yaml --- .github/workflows/rworkflows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index 6ac515a5..7b77e519 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -27,7 +27,7 @@ jobs: - os: ubuntu-latest bioc: release r: auto - cont: bioconductor/bioconductor_docker:release + cont: bioconductor/bioconductor_docker:latest http-user-agent: 'release' - os: macOS-latest bioc: release From 7cd2edc5d724944ec640d1c6c8a638b1b644317a Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 11:59:29 +0200 Subject: [PATCH 04/19] update pkg number --- DESCRIPTION | 2 +- NEWS.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index d8629c3d..e5aeb2a9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: anndataR Title: AnnData interoperability in R -Version: 0.0.0.9000 +Version: 0.99.0 Authors@R: c( person("Robrecht", "Cannoodt", , "rcannood@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3641-729X", github = "rcannood")), diff --git a/NEWS.md b/NEWS.md index 8bb1614c..c8ec3ee8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,13 @@ +# anndataR 0.99.0 + +## New features + + + # anndataR 0.1.0 +## New features + Initial release of anndataR, providing support for working with AnnData objects in R. Feature list: From 11a403dfd1dd6895cffa19fb9430e72c98244302 Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 14:45:27 +0200 Subject: [PATCH 05/19] Add example_data --- DESCRIPTION | 14 +++--- NAMESPACE | 1 + NEWS.md | 6 +++ R/Seurat.R | 68 ++++++++++++++---------------- R/example_data.R | 64 ++++++++++++++++++++++++++++ README.qmd | 2 + man/Seurat.Rd | 28 ++++++------ man/example_data.Rd | 40 ++++++++++++++++++ tests/testthat/test-example_data.R | 8 ++++ 9 files changed, 176 insertions(+), 55 deletions(-) create mode 100644 R/example_data.R create mode 100644 man/example_data.Rd create mode 100644 tests/testthat/test-example_data.R diff --git a/DESCRIPTION b/DESCRIPTION index e5aeb2a9..f8067f60 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,15 +2,15 @@ Package: anndataR Title: AnnData interoperability in R Version: 0.99.0 Authors@R: c( - person("Robrecht", "Cannoodt", , "rcannood@gmail.com", role = c("aut", "cre"), + person("Robrecht", "Cannoodt", email="rcannood@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3641-729X", github = "rcannood")), - person("Luke", "Zappia", , "luke@lazappi.id.au", role = "aut", + person("Luke", "Zappia", email="luke@lazappi.id.au", role = "aut", comment = c(ORCID = "0000-0001-7744-8565", github = "lazappi")), - person("Martin", "Morgan", , "mtmorgan.bioc@gmail.com", role = "aut", + person("Martin", "Morgan", email="mtmorgan.bioc@gmail.com", role = "aut", comment = c(ORCID = "0000-0002-5874-8148", github = "mtmorgan")), - person("Louise", "Deconinck", , "louise.deconinck@gmail.com", role = "aut", + person("Louise", "Deconinck", email="louise.deconinck@gmail.com", role = "aut", comment = c(ORCID = "0000-0001-8100-6823", github = "LouiseDck")), - person("Danila", "Bredikhin", , "danila.bredikhin@embl.de", role = "aut", + person("Danila", "Bredikhin", email="danila.bredikhin@embl.de", role = "aut", comment = c(ORCID = "0000-0001-8089-6983", github = "gtca")) ) Description: Bring the power and flexibility of AnnData to the R @@ -21,6 +21,8 @@ Description: Bring the power and flexibility of AnnData to the R License: MIT + file LICENSE URL: https://scverse.org/anndataR, https://github.com/scverse/anndataR BugReports: https://github.com/scverse/anndataR/issues +BioViews: + Software Depends: R (>= 3.2.0) Imports: @@ -46,4 +48,4 @@ Config/Needs/website: pkgdown, tibble, knitr, rprojroot, stringr, readr, Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE, r6 = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.2.3.9000 diff --git a/NAMESPACE b/NAMESPACE index 4f50a0ed..d1dbb51a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ export(InMemoryAnnData) export(dummy_data) +export(example_data) export(from_Seurat) export(from_SingleCellExperiment) export(read_h5ad) diff --git a/NEWS.md b/NEWS.md index c8ec3ee8..3d366347 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,7 +2,13 @@ ## New features +* Add `example_data` function. +## Bug fixes + +* *DESCRIPTION*: + - Add "email" tag to `Authors`. + - Add `BiocViews` # anndataR 0.1.0 diff --git a/R/Seurat.R b/R/Seurat.R index dbf97f86..9599df3f 100644 --- a/R/Seurat.R +++ b/R/Seurat.R @@ -3,40 +3,33 @@ #' @title Convert Between AnnData and Seurat #' #' @description `to_Seurat()` converts an AnnData object to a Seurat object. -#' -#' @param obj An AnnData object +#' NOTE: Only 1 assay per object is currently. +#' @param obj An AnnData object. #' #' @importFrom Matrix t #' #' @export #' @examples -#' ad <- InMemoryAnnData$new( -#' X = matrix(1:5, 3L, 5L), -#' obs = data.frame(cell = 1:3), -#' obs_names = letters[1:3], -#' var = data.frame(gene = 1:5), -#' var_names = letters[1:5] -#' ) -#' to_Seurat(ad) -# TODO: Add parameters to choose which how X and layers are translated into counts, data and scaled.data +#' ad <- example_anndata() +#' to_Seurat(ad) to_Seurat <- function(obj) { # nolint requireNamespace("SeuratObject") - + stopifnot(inherits(obj, "AbstractAnnData")) - + # translate var_names # trackstatus: class=Seurat, feature=get_var_names, status=done var_names_ <- .toseurat_check_obsvar_names(obj$var_names, "var_names") - + # translate obs_names # trackstatus: class=Seurat, feature=get_obs_names, status=done obs_names_ <- .toseurat_check_obsvar_names(obj$obs_names, "obs_names") - + # translate var # trackstatus: class=Seurat, feature=get_var, status=done var_ <- obj$var rownames(var_) <- var_names_ - + # translate obs # trackstatus: class=Seurat, feature=get_obs, status=done obs_ <- @@ -47,7 +40,7 @@ to_Seurat <- function(obj) { # nolint } else { NULL } - + # translate X # trackstatus: class=Seurat, feature=get_X, status=wip # TODO: should x_ be passed to counts or to data? @@ -68,14 +61,14 @@ to_Seurat <- function(obj) { # nolint } dimnames(x_) <- list(var_names_, obs_names_) x_assay <- SeuratObject::CreateAssayObject(counts = x_) - + # create seurat object if (ncol(var_) > 0) { # don't add var metadata if the data frame does not contain any columns x_assay <- SeuratObject::AddMetaData(x_assay, metadata = var_) } seurat_obj <- SeuratObject::CreateSeuratObject(x_assay, meta.data = obs_) - + # add layers # trackstatus: class=Seurat, feature=get_layers, status=wip # TODO: should values be passed to counts or to data? @@ -84,7 +77,7 @@ to_Seurat <- function(obj) { # nolint dimnames(layer_) <- list(var_names_, obs_names_) seurat_obj[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) } - + seurat_obj } @@ -98,7 +91,7 @@ to_Seurat <- function(obj) { # nolint )) names <- gsub("_", "-", names) } - + names } @@ -108,37 +101,38 @@ to_Seurat <- function(obj) { # nolint #' #' @param seurat_obj An object inheriting from Seurat. #' -#' @param output_class Name of the AnnData class. Must be one of `"HDF5AnnData"` -#' or `"InMemoryAnnData"`. -#' -#' @param ... Additional arguments passed to the generator function. -#' See the "Details" section for more information on which parameters +#' @inheritParams example_data +#' +#' @returns \link[anndataR]{InMemoryAnnData} or \link[anndataR]{HDF5AnnData} #' #' @export # TODO: Add parameter to choose which how counts, data and scaled.data are translated into X and layers # TODO: add tests with Seurat objects not created by anndataR -from_Seurat <- function(seurat_obj, output_class = c("InMemoryAnnData", "HDF5AnnData"), ...) { # nolint - +from_Seurat <- function(seurat_obj, + output_class = c("InMemoryAnnData", + "HDF5AnnData"), + ...) { # nolint + stopifnot(inherits(seurat_obj, "Seurat")) - + # get obs_names # trackstatus: class=Seurat, feature=set_obs_names, status=done obs_names <- colnames(seurat_obj) - + # get obs # trackstatus: class=Seurat, feature=set_obs, status=done obs <- seurat_obj@meta.data rownames(obs) <- NULL - + # construct var_names # trackstatus: class=Seurat, feature=set_var_names, status=done var_names <- rownames(seurat_obj) - + # construct var # trackstatus: class=Seurat, feature=set_var, status=done var <- seurat_obj@assays[[seurat_obj@active.assay]]@meta.features rownames(var) <- NULL - + # use generator to create new AnnData object generator <- get_generator(output_class) ad <- generator$new( @@ -148,13 +142,13 @@ from_Seurat <- function(seurat_obj, output_class = c("InMemoryAnnData", "HDF5Ann var_names = var_names, ... ) - + # trackstatus: class=Seurat, feature=set_X, status=wip # trackstatus: class=Seurat, feature=set_layers, status=wip for (assay_name in names(seurat_obj@assays)) { # TODO: Maybe we shouldn't use counts but instead data assay_data <- SeuratObject::GetAssayData(seurat_obj, "counts", assay = assay_name) - + if (nrow(assay_data) != length(var_names) || !identical(rownames(assay_data), var_names)) { warning( "Skipping assay '", assay_name, "' because it has different feature names ", @@ -169,7 +163,7 @@ from_Seurat <- function(seurat_obj, output_class = c("InMemoryAnnData", "HDF5Ann ) next } - + # remove names dimnames(assay_data) <- list(NULL, NULL) if (assay_name == seurat_obj@active.assay) { @@ -178,6 +172,6 @@ from_Seurat <- function(seurat_obj, output_class = c("InMemoryAnnData", "HDF5Ann ad$layers[[assay_name]] <- Matrix::t(assay_data) } } - + return(ad) } diff --git a/R/example_data.R b/R/example_data.R new file mode 100644 index 00000000..91444e77 --- /dev/null +++ b/R/example_data.R @@ -0,0 +1,64 @@ +#' Example AnnData +#' +#' Create a small example AnnData object. +#' @param n_obs Number of observations. +#' @param n_var Number of variables. +#' @param output_class Name of the AnnData class. +#' Must be one of: +#' \itemize{ +#' \item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} +#' (default).} +#' \item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} +#' \item{"Seurat": }{Produces \link[SeuratObject]{Seurat}.} +#' } +#' @param ... Additional arguments passed to the generator function. +#' See the "Details" section for more information on which parameters. +#' @returns \link[anndataR]{InMemoryAnnData} or +#' \link[anndataR]{HDF5AnnData} or link[SeuratObject]{Seurat}. +#' @export +#' @examples +#' example_anndata() +example_data <- function(n_obs=3L, + n_var=5L, + output_class=c("InMemoryAnnData", + "HDF5AnnData", + "Seurat"), + file=tempfile(), + ...){ + + output_class <- output_class[1] + generator <- get_generator( + if(output_class=="Seurat") { + eval(formals(example_data)$output_class)[1] + } else { + output_class + } + ) + + if(output_class=="HDF5AnnData"){ + ad <- generator$new( + X = matrix(seq(n_var), n_obs, n_var), + obs = data.frame(cell = seq(n_obs)), + obs_names = letters[seq(n_obs)], + var = data.frame(gene = seq(n_var)), + var_names = letters[seq(n_var)], + file=file, + ... + ) + } else { + ad <- generator$new( + X = matrix(seq(n_var), n_obs, n_var), + obs = data.frame(cell = seq(n_obs)), + obs_names = letters[seq(n_obs)], + var = data.frame(gene = seq(n_var)), + var_names = letters[seq(n_var)], + ... + ) + } + #### Return #### + if(output_class=="Seurat"){ + return(to_Seurat(ad)) + } else { + return(ad) + } +} \ No newline at end of file diff --git a/README.qmd b/README.qmd index 6f45cd32..7cd6ad11 100644 --- a/README.qmd +++ b/README.qmd @@ -1,5 +1,7 @@ --- title: anndataR +author: "`r rworkflows::use_badges(branch='main', add_bioc_release=TRUE)`" +date: "

Most recent update: `r format( Sys.Date(), '%b-%d-%Y')`

" format: gfm --- diff --git a/man/Seurat.Rd b/man/Seurat.Rd index 7fbd41be..f969826a 100644 --- a/man/Seurat.Rd +++ b/man/Seurat.Rd @@ -14,28 +14,32 @@ from_Seurat( ) } \arguments{ -\item{obj}{An AnnData object} +\item{obj}{An AnnData object.} \item{seurat_obj}{An object inheriting from Seurat.} -\item{output_class}{Name of the AnnData class. Must be one of \code{"HDF5AnnData"} -or \code{"InMemoryAnnData"}.} +\item{output_class}{Name of the AnnData class. +Must be one of: +\itemize{ +\item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} +(default).} +\item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} +\item{"Seurat": }{Produces \link[SeuratObject]{Seurat}.} +}} \item{...}{Additional arguments passed to the generator function. -See the "Details" section for more information on which parameters} +See the "Details" section for more information on which parameters.} +} +\value{ +\link[anndataR]{InMemoryAnnData} or \link[anndataR]{HDF5AnnData} } \description{ \code{to_Seurat()} converts an AnnData object to a Seurat object. +NOTE: Only 1 assay per object is currently. \code{from_Seurat()} converts a Seurat object to an AnnData object. } \examples{ -ad <- InMemoryAnnData$new( - X = matrix(1:5, 3L, 5L), - obs = data.frame(cell = 1:3), - obs_names = letters[1:3], - var = data.frame(gene = 1:5), - var_names = letters[1:5] -) -to_Seurat(ad) +ad <- example_anndata() +to_Seurat(ad) } diff --git a/man/example_data.Rd b/man/example_data.Rd new file mode 100644 index 00000000..3337d947 --- /dev/null +++ b/man/example_data.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/example_data.R +\name{example_data} +\alias{example_data} +\title{Example AnnData} +\usage{ +example_data( + n_obs = 3L, + n_var = 5L, + output_class = c("InMemoryAnnData", "HDF5AnnData", "Seurat"), + ... +) +} +\arguments{ +\item{n_obs}{Number of observations.} + +\item{n_var}{Number of variables.} + +\item{output_class}{Name of the AnnData class. +Must be one of: +\itemize{ +\item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} +(default).} +\item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} +\item{"Seurat": }{Produces \link[SeuratObject]{Seurat}.} +}} + +\item{...}{Additional arguments passed to the generator function. +See the "Details" section for more information on which parameters.} +} +\value{ +\link[anndataR]{InMemoryAnnData} or +\link[anndataR]{HDF5AnnData} or link[SeuratObject]{Seurat}. +} +\description{ +Create a small example AnnData object. +} +\examples{ +example_anndata() +} diff --git a/tests/testthat/test-example_data.R b/tests/testthat/test-example_data.R new file mode 100644 index 00000000..fe5e48ca --- /dev/null +++ b/tests/testthat/test-example_data.R @@ -0,0 +1,8 @@ +test_that("example_data works", { + + classes <- c("InMemoryAnnData","HDF5AnnData","Seurat") + for(x in classes){ + obj <- example_data(output_class = x) + testthat::expect_true(methods::is(obj,x)) + } +}) From 3a22fe5dbde8f24cb3156e150427d537c904a79c Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 14:48:20 +0200 Subject: [PATCH 06/19] update NEWS [skip ci] --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 0e7de359..d6623647 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ ## New features +- Add `rworkflows` CI. +- Update *lint.yaml* to use `actions/checkout@v4` (which has less issues). - New function `setup_conda` automatically installs miniconda and sets up conda env: #97 - Change version to Bioc-recommended devel version: 0.99.0 From f5ed24d5e1c98fa194b183f1109b3d12270fac1e Mon Sep 17 00:00:00 2001 From: bschilder Date: Mon, 18 Sep 2023 15:16:34 +0200 Subject: [PATCH 07/19] Extend dummy_data to produce anndata classes --- DESCRIPTION | 10 ++--- NAMESPACE | 1 - R/Seurat.R | 2 +- R/dummy_data.R | 66 +++++++++++++++++++++++++++--- R/example_data.R | 64 ----------------------------- README.md | 15 +++++++ README.qmd | 2 +- man/Seurat.Rd | 4 +- man/dummy_anndata.Rd | 31 ++++++++++++++ man/dummy_data.Rd | 2 +- man/example_data.Rd | 40 ------------------ tests/testthat/test-Seurat.R | 8 +--- tests/testthat/test-dummy_data.R | 10 +++++ tests/testthat/test-example_data.R | 8 ---- 14 files changed, 127 insertions(+), 136 deletions(-) delete mode 100644 R/example_data.R create mode 100644 man/dummy_anndata.Rd delete mode 100644 man/example_data.Rd delete mode 100644 tests/testthat/test-example_data.R diff --git a/DESCRIPTION b/DESCRIPTION index f8067f60..ef0e00d6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,15 +2,15 @@ Package: anndataR Title: AnnData interoperability in R Version: 0.99.0 Authors@R: c( - person("Robrecht", "Cannoodt", email="rcannood@gmail.com", role = c("aut", "cre"), + person(given="Robrecht", family="Cannoodt", email="rcannood@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3641-729X", github = "rcannood")), - person("Luke", "Zappia", email="luke@lazappi.id.au", role = "aut", + person(given="Luke", family="Zappia", email="luke@lazappi.id.au", role = "aut", comment = c(ORCID = "0000-0001-7744-8565", github = "lazappi")), - person("Martin", "Morgan", email="mtmorgan.bioc@gmail.com", role = "aut", + person(given="Martin", family="Morgan", email="mtmorgan.bioc@gmail.com", role = "aut", comment = c(ORCID = "0000-0002-5874-8148", github = "mtmorgan")), - person("Louise", "Deconinck", email="louise.deconinck@gmail.com", role = "aut", + person(given="Louise", family="Deconinck", email="louise.deconinck@gmail.com", role = "aut", comment = c(ORCID = "0000-0001-8100-6823", github = "LouiseDck")), - person("Danila", "Bredikhin", email="danila.bredikhin@embl.de", role = "aut", + person(given="Danila", family="Bredikhin", email="danila.bredikhin@embl.de", role = "aut", comment = c(ORCID = "0000-0001-8089-6983", github = "gtca")) ) Description: Bring the power and flexibility of AnnData to the R diff --git a/NAMESPACE b/NAMESPACE index d1dbb51a..4f50a0ed 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,7 +2,6 @@ export(InMemoryAnnData) export(dummy_data) -export(example_data) export(from_Seurat) export(from_SingleCellExperiment) export(read_h5ad) diff --git a/R/Seurat.R b/R/Seurat.R index 9599df3f..04195caf 100644 --- a/R/Seurat.R +++ b/R/Seurat.R @@ -101,7 +101,7 @@ to_Seurat <- function(obj) { # nolint #' #' @param seurat_obj An object inheriting from Seurat. #' -#' @inheritParams example_data +#' @inheritParams dummy_anndata #' #' @returns \link[anndataR]{InMemoryAnnData} or \link[anndataR]{HDF5AnnData} #' diff --git a/R/dummy_data.R b/R/dummy_data.R index 9cbb7040..dbdf8df3 100644 --- a/R/dummy_data.R +++ b/R/dummy_data.R @@ -7,15 +7,18 @@ #' @param output Object type to output, one of "list", "SingleCellExperiment", #' or "Seurat" #' -#' @return Object containing the generated dataset as defined by `output` +#' @returns Object containing the generated dataset as defined by `output` #' @export #' #' @examples #' dummy <- dummy_data() dummy_data <- function(n_obs = 10L, n_vars = 20L, output = c( - "list", "SingleCellExperiment", - "Seurat" + "list", + "SingleCellExperiment", + "Seurat", + "InMemoryAnnData", + "HDF5AnnData" )) { output <- match.arg(output) @@ -24,7 +27,11 @@ dummy_data <- function(n_obs = 10L, n_vars = 20L, "SingleCellExperiment" = dummy_SingleCellExperiment( n_obs = n_obs, n_vars = n_vars ), - "Seurat" = dummy_Seurat(n_obs = n_obs, n_vars = n_vars) + "Seurat" = dummy_Seurat(n_obs = n_obs, n_vars = n_vars), + "InMemoryAnnData" = dummy_anndata(n_obs = n_obs, n_vars = n_vars, + output_class = "InMemoryAnnData"), + "HDF5AnnData" = dummy_anndata(n_obs = n_obs, n_vars = n_vars, + output_class = "HDF5AnnData") ) } @@ -113,7 +120,7 @@ dummy_SingleCellExperiment <- function(...) { #nolint #' #' @param ... Parameters passed to `dummy_list` #' -#' @return Seurat containing the generated data +#' @returns Seurat containing the generated data dummy_Seurat <- function(...) { #nolint if (!requireNamespace("SeuratObject", quietly = TRUE)) { stop( @@ -143,3 +150,52 @@ dummy_Seurat <- function(...) { #nolint return(seurat) } + + + +#' Dummy anndata +#' +#' Generate a dummy dataset as a \link[anndataR]{InMemoryAnnData} or +#' \link[anndataR]{HDF5AnnData} object +#' +#' @param output_class Name of the AnnData class. +#' Must be one of: +#' \itemize{ +#' \item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} +#' (default).} +#' \item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} +#' } +#' +#' @param ... Parameters passed to `dummy_list` +#' +#' @returns \link[anndataR]{InMemoryAnnData} or +#' \link[anndataR]{HDF5AnnData} containing the generated data. +dummy_anndata <- function(output_class=c("InMemoryAnnData", + "HDF5AnnData"), + file=tempfile(), + ...) { #nolint + + dummy <- dummy_data(...) + output_class <- output_class[1] + generator <- get_generator(output_class) + if(output_class=="HDF5AnnData"){ + ad <- generator$new( + X = dummy$X, + obs = dummy$obs, + obs_names = dummy$obs_names, + var = dummy$var, + var_names = dummy$var_names, + file=file + ) + } else { + ad <- generator$new( + X = dummy$X, + obs = dummy$obs, + obs_names = dummy$obs_names, + var = dummy$var, + var_names = dummy$var_names + ) + } + return(ad) +} + diff --git a/R/example_data.R b/R/example_data.R deleted file mode 100644 index 91444e77..00000000 --- a/R/example_data.R +++ /dev/null @@ -1,64 +0,0 @@ -#' Example AnnData -#' -#' Create a small example AnnData object. -#' @param n_obs Number of observations. -#' @param n_var Number of variables. -#' @param output_class Name of the AnnData class. -#' Must be one of: -#' \itemize{ -#' \item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} -#' (default).} -#' \item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} -#' \item{"Seurat": }{Produces \link[SeuratObject]{Seurat}.} -#' } -#' @param ... Additional arguments passed to the generator function. -#' See the "Details" section for more information on which parameters. -#' @returns \link[anndataR]{InMemoryAnnData} or -#' \link[anndataR]{HDF5AnnData} or link[SeuratObject]{Seurat}. -#' @export -#' @examples -#' example_anndata() -example_data <- function(n_obs=3L, - n_var=5L, - output_class=c("InMemoryAnnData", - "HDF5AnnData", - "Seurat"), - file=tempfile(), - ...){ - - output_class <- output_class[1] - generator <- get_generator( - if(output_class=="Seurat") { - eval(formals(example_data)$output_class)[1] - } else { - output_class - } - ) - - if(output_class=="HDF5AnnData"){ - ad <- generator$new( - X = matrix(seq(n_var), n_obs, n_var), - obs = data.frame(cell = seq(n_obs)), - obs_names = letters[seq(n_obs)], - var = data.frame(gene = seq(n_var)), - var_names = letters[seq(n_var)], - file=file, - ... - ) - } else { - ad <- generator$new( - X = matrix(seq(n_var), n_obs, n_var), - obs = data.frame(cell = seq(n_obs)), - obs_names = letters[seq(n_obs)], - var = data.frame(gene = seq(n_var)), - var_names = letters[seq(n_var)], - ... - ) - } - #### Return #### - if(output_class=="Seurat"){ - return(to_Seurat(ad)) - } else { - return(ad) - } -} \ No newline at end of file diff --git a/README.md b/README.md index ffee4144..fb4334fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,19 @@ # anndataR +NULL [![License: MIT + file +LICENSE](https://img.shields.io/badge/license-MIT%20+%20file%20LICENSE-blue.svg)](https://cran.r-project.org/web/licenses/MIT%20+%20file%20LICENSE) +[![](https://img.shields.io/badge/devel%20version-0.99.0-black.svg)](https://github.com/scverse/anndataR) +[![](https://img.shields.io/github/languages/code-size/scverse/anndataR.svg)](https://github.com/scverse/anndataR) +[![](https://img.shields.io/github/last-commit/scverse/anndataR.svg)](https://github.com/scverse/anndataR/commits/main) +
[![R build +status](https://github.com/scverse/anndataR/workflows/rworkflows/badge.svg)](https://github.com/scverse/anndataR/actions) +[![](https://codecov.io/gh/scverse/anndataR/branch/main/graph/badge.svg)](https://app.codecov.io/gh/scverse/anndataR) +
+ +

+Authors: Robrecht Cannoodt, Luke Zappia, Martin Morgan, Louise +Deconinck, Danila Bredikhin +

+Invalid Date diff --git a/README.qmd b/README.qmd index 7cd6ad11..874554b6 100644 --- a/README.qmd +++ b/README.qmd @@ -1,6 +1,6 @@ --- title: anndataR -author: "`r rworkflows::use_badges(branch='main', add_bioc_release=TRUE)`" +author: "`r rworkflows::use_badges(branch='main')`" date: "

Most recent update: `r format( Sys.Date(), '%b-%d-%Y')`

" format: gfm --- diff --git a/man/Seurat.Rd b/man/Seurat.Rd index f969826a..fc505714 100644 --- a/man/Seurat.Rd +++ b/man/Seurat.Rd @@ -24,11 +24,9 @@ Must be one of: \item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} (default).} \item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} -\item{"Seurat": }{Produces \link[SeuratObject]{Seurat}.} }} -\item{...}{Additional arguments passed to the generator function. -See the "Details" section for more information on which parameters.} +\item{...}{Parameters passed to \code{dummy_list}} } \value{ \link[anndataR]{InMemoryAnnData} or \link[anndataR]{HDF5AnnData} diff --git a/man/dummy_anndata.Rd b/man/dummy_anndata.Rd new file mode 100644 index 00000000..f6358fc0 --- /dev/null +++ b/man/dummy_anndata.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dummy_data.R +\name{dummy_anndata} +\alias{dummy_anndata} +\title{Dummy anndata} +\usage{ +dummy_anndata( + output_class = c("InMemoryAnnData", "HDF5AnnData"), + file = tempfile(), + ... +) +} +\arguments{ +\item{output_class}{Name of the AnnData class. +Must be one of: +\itemize{ +\item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} +(default).} +\item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} +}} + +\item{...}{Parameters passed to \code{dummy_list}} +} +\value{ +\link[anndataR]{InMemoryAnnData} or +\link[anndataR]{HDF5AnnData} containing the generated data. +} +\description{ +Generate a dummy dataset as a \link[anndataR]{InMemoryAnnData} or +\link[anndataR]{HDF5AnnData} object +} diff --git a/man/dummy_data.Rd b/man/dummy_data.Rd index 0ab86ae1..4c272c24 100644 --- a/man/dummy_data.Rd +++ b/man/dummy_data.Rd @@ -7,7 +7,7 @@ dummy_data( n_obs = 10L, n_vars = 20L, - output = c("list", "SingleCellExperiment", "Seurat") + output = c("list", "SingleCellExperiment", "Seurat", "InMemoryAnnData", "HDF5AnnData") ) } \arguments{ diff --git a/man/example_data.Rd b/man/example_data.Rd deleted file mode 100644 index 3337d947..00000000 --- a/man/example_data.Rd +++ /dev/null @@ -1,40 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/example_data.R -\name{example_data} -\alias{example_data} -\title{Example AnnData} -\usage{ -example_data( - n_obs = 3L, - n_var = 5L, - output_class = c("InMemoryAnnData", "HDF5AnnData", "Seurat"), - ... -) -} -\arguments{ -\item{n_obs}{Number of observations.} - -\item{n_var}{Number of variables.} - -\item{output_class}{Name of the AnnData class. -Must be one of: -\itemize{ -\item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} -(default).} -\item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} -\item{"Seurat": }{Produces \link[SeuratObject]{Seurat}.} -}} - -\item{...}{Additional arguments passed to the generator function. -See the "Details" section for more information on which parameters.} -} -\value{ -\link[anndataR]{InMemoryAnnData} or -\link[anndataR]{HDF5AnnData} or link[SeuratObject]{Seurat}. -} -\description{ -Create a small example AnnData object. -} -\examples{ -example_anndata() -} diff --git a/tests/testthat/test-Seurat.R b/tests/testthat/test-Seurat.R index fc65d673..e7374298 100644 --- a/tests/testthat/test-Seurat.R +++ b/tests/testthat/test-Seurat.R @@ -1,13 +1,7 @@ dummy <- dummy_data(10L, 20L) test_that("to_Seurat with inmemoryanndata", { - ad <- InMemoryAnnData$new( - X = dummy$X, - obs = dummy$obs, - var = dummy$var, - obs_names = dummy$obs_names, - var_names = dummy$var_names - ) + ad <- example_data(10L, 20L) # running to_seurat when ad0$X is null probably doesn't make any sense ad0 <- InMemoryAnnData$new( obs_names = letters[1:5], diff --git a/tests/testthat/test-dummy_data.R b/tests/testthat/test-dummy_data.R index c7ddb4c2..f5826514 100644 --- a/tests/testthat/test-dummy_data.R +++ b/tests/testthat/test-dummy_data.R @@ -17,3 +17,13 @@ test_that("generating dummy Seurat works", { dummy <- dummy_data(output = "Seurat") expect_s4_class(dummy, "Seurat") }) + +test_that("generating dummy InMemoryAnnData works", { + dummy <- dummy_data(output = "InMemoryAnnData") + expect_true(methods::is(dummy,"InMemoryAnnData")) +}) + +test_that("generating dummy HDF5AnnData works", { + dummy <- dummy_data(output = "HDF5AnnData") + expect_true(methods::is(dummy,"HDF5AnnData")) +}) diff --git a/tests/testthat/test-example_data.R b/tests/testthat/test-example_data.R deleted file mode 100644 index fe5e48ca..00000000 --- a/tests/testthat/test-example_data.R +++ /dev/null @@ -1,8 +0,0 @@ -test_that("example_data works", { - - classes <- c("InMemoryAnnData","HDF5AnnData","Seurat") - for(x in classes){ - obj <- example_data(output_class = x) - testthat::expect_true(methods::is(obj,x)) - } -}) From 4e5a4d7f97fd443d63e8bd5150e0d348e9766b68 Mon Sep 17 00:00:00 2001 From: bschilder Date: Tue, 19 Sep 2023 09:14:46 +0200 Subject: [PATCH 08/19] Add to_DimReduc --- R/Seurat.R | 83 +++++++++++++++++++++++++++++++++---------------- R/to_DimReduc.R | 33 ++++++++++++++++++++ man/Seurat.Rd | 30 +++++++++++++++--- 3 files changed, 115 insertions(+), 31 deletions(-) create mode 100644 R/to_DimReduc.R diff --git a/R/Seurat.R b/R/Seurat.R index 04195caf..f7fe24e5 100644 --- a/R/Seurat.R +++ b/R/Seurat.R @@ -5,18 +5,21 @@ #' @description `to_Seurat()` converts an AnnData object to a Seurat object. #' NOTE: Only 1 assay per object is currently. #' @param obj An AnnData object. +#' @inheritDotParams SeuratObject::CreateSeuratObject #' #' @importFrom Matrix t #' #' @export #' @examples -#' ad <- example_anndata() -#' to_Seurat(ad) -to_Seurat <- function(obj) { # nolint +#' obj <- dummy_data(output="InMemoryAnnData") +#' to_Seurat(obj) +to_Seurat <- function(obj, + use_layer=NULL, + ...) { # nolint requireNamespace("SeuratObject") stopifnot(inherits(obj, "AbstractAnnData")) - + use_layer <- use_layer[1] # translate var_names # trackstatus: class=Seurat, feature=get_var_names, status=done var_names_ <- .toseurat_check_obsvar_names(obj$var_names, "var_names") @@ -43,41 +46,69 @@ to_Seurat <- function(obj) { # nolint # translate X # trackstatus: class=Seurat, feature=get_X, status=wip - # TODO: should x_ be passed to counts or to data? - # TODO: creating a seurat object when th AnnData doesn't contain X or layers - # probably doesn't make any sense + seurat_slots <- c("counts","data","scale.data") x_ <- - if (!is.null(obj$X)) { + if(!is.null(use_layer) && + use_layer %in% obj$layers_keys()){ + seurat_slots <- seurat_slots[seurat_slots!=use_layer] + Matrix::t(obj$layers[[use_layer]]) + } else if (is.null(obj$X) && + !is.null(obj$layers_keys())){ + layer1 <- obj$layers_keys()[1] + seurat_slots <- seurat_slots[seurat_slots!=layer1] + message("No X detected. Using first layer as counts: ",layer1) + Matrix::t(obj$layers[[layer1]]) + } else if (!is.null(obj$X)) { Matrix::t(obj$X) } else { - mat <- Matrix::sparseMatrix( - i = integer(0), - p = c(0L), - x = integer(0), - dims = c(obj$n_vars(), obj$n_obs()) - ) - attr(mat, "is_X_null") <- TRUE # nolint - mat + stop("obj must contain at least one matrix in either `X` or `layers`.") } - dimnames(x_) <- list(var_names_, obs_names_) - x_assay <- SeuratObject::CreateAssayObject(counts = x_) + dimnames(x_) <- list(var_names_, obs_names_) + x_assay <- SeuratObject::CreateAssayObject(counts = x_) + # seurat <- SeuratObject::SetAssayData(seurat, "scale.data", X3) # create seurat object if (ncol(var_) > 0) { # don't add var metadata if the data frame does not contain any columns x_assay <- SeuratObject::AddMetaData(x_assay, metadata = var_) } - seurat_obj <- SeuratObject::CreateSeuratObject(x_assay, meta.data = obs_) + seurat_obj <- SeuratObject::CreateSeuratObject(x_assay, + meta.data = obs_, + ...) # add layers # trackstatus: class=Seurat, feature=get_layers, status=wip - # TODO: should values be passed to counts or to data? - for (key in obj$layers_keys()) { - layer_ <- t(obj$layers[[key]]) - dimnames(layer_) <- list(var_names_, obs_names_) - seurat_obj[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) - } - + #### Store as assay data matrices #### + if(all(seurat_slots %in% obj$layers_keys())){ + message("Setting layers as AssayData matrices.") + for(key in seurat_slots){ + seurat_obj <- SeuratObject::SetAssayData(object = seurat_obj, + slot = key, + new.data = t(obj$layers[[key]])) + } + #### Store as multiple assays #### + } else { + message("Setting layers as new assays.") + for (key in obj$layers_keys()) { + layer_ <- t(obj$layers[[key]]) + dimnames(layer_) <- list(var_names_, obs_names_) + seurat_obj[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) + } + } + #### Add DimReducObjects #### + if(all(c("obsm_keys","varm_keys") %in% names(obj)) && + !is.null(obj$obsm_keys()) && + !is.null(obj$varm_keys()) ){ + for(key in obj$obsm_keys()){ + assay <- SeuratObject::Assays(seurat_obj)[1] + seurat_obj@reductions[[key]] <- to_DimReduc(obsm=obj$obsm[[key]], + varm=obj$varm[[key]], + stdev=obj$stdev[[key]], + assay=assay, + key=key) + } + } + #### Return #### seurat_obj } diff --git a/R/to_DimReduc.R b/R/to_DimReduc.R new file mode 100644 index 00000000..396b04db --- /dev/null +++ b/R/to_DimReduc.R @@ -0,0 +1,33 @@ +#' Convert: \code{list} ==> \code{DimReducObject} +#' +#' Create a \pkg{Seurat} \code{DimReducObject} +#' from embeddings and loadings. +#' @param obsm Sample embeddings. +#' @param varm Feature loadings. +#' @param stdev Standard deviation (if applicable) for the +#' dimensional reduction. +#' @param assay Assay to use. +#' @param key Key to use (name of embedding). +#' @inheritDotParams SeuratObject::CreateDimReducObject +#' +#' @export +#' @importFrom SeuratObject CreateDimReducObject +to_DimReduc <- function(obsm, + varm, + stdev=NULL, + assay, + key, + ...) { + + Seurat::CreateDimReducObject( + embeddings = obsm, + loadings = varm, + stdev = if (is.null(stdev)) { + numeric() + } else { + as.numeric(stdev) + }, + assay = assay, + key = key, + ...) +} diff --git a/man/Seurat.Rd b/man/Seurat.Rd index fc505714..742f91e2 100644 --- a/man/Seurat.Rd +++ b/man/Seurat.Rd @@ -5,7 +5,7 @@ \alias{from_Seurat} \title{Convert Between AnnData and Seurat} \usage{ -to_Seurat(obj) +to_Seurat(obj, use_layer = NULL, ...) from_Seurat( seurat_obj, @@ -16,6 +16,28 @@ from_Seurat( \arguments{ \item{obj}{An AnnData object.} +\item{...}{ + Arguments passed on to \code{\link[SeuratObject:CreateSeuratObject]{SeuratObject::CreateSeuratObject}} + \describe{ + \item{\code{counts}}{Either a \code{\link[base]{matrix}}-like object with +unnormalized data with cells as columns and features as rows or an +\code{\link[SeuratObject]{Assay}}-derived object} + \item{\code{project}}{\link[SeuratObject]{Project} name for the \code{Seurat} object} + \item{\code{assay}}{Name of the initial assay} + \item{\code{names.field}}{For the initial identity class for each cell, choose this +field from the cell's name. E.g. If your cells are named as +BARCODE_CLUSTER_CELLTYPE in the input matrix, set \code{names.field} to 3 to +set the initial identities to CELLTYPE.} + \item{\code{names.delim}}{For the initial identity class for each cell, choose this +delimiter from the cell's column name. E.g. If your cells are named as +BARCODE-CLUSTER-CELLTYPE, set this to \dQuote{-} to separate the cell name +into its component parts for picking the relevant field.} + \item{\code{meta.data}}{Additional cell-level metadata to add to the Seurat object. +Should be a \code{\link[base]{data.frame}} where the rows are cell names and +the columns are additional metadata fields. Row names in the metadata need +to match the column names of the counts matrix.} + }} + \item{seurat_obj}{An object inheriting from Seurat.} \item{output_class}{Name of the AnnData class. @@ -25,8 +47,6 @@ Must be one of: (default).} \item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} }} - -\item{...}{Parameters passed to \code{dummy_list}} } \value{ \link[anndataR]{InMemoryAnnData} or \link[anndataR]{HDF5AnnData} @@ -38,6 +58,6 @@ NOTE: Only 1 assay per object is currently. \code{from_Seurat()} converts a Seurat object to an AnnData object. } \examples{ -ad <- example_anndata() -to_Seurat(ad) +obj <- dummy_data(output="InMemoryAnnData") +to_Seurat(obj) } From 3816df6a2d2ce5d76587be5acdf5d408f3eb684c Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:36:46 +0000 Subject: [PATCH 09/19] Get anndataR passing all BiocChecks --- .Rbuildignore | 2 +- DESCRIPTION | 5 +- NAMESPACE | 5 +- NEWS.md | 19 +- R/AbstractAnnData.R | 62 ++-- R/AnnData.R | 7 +- R/DimReduc.R | 192 +++++++++++ R/HDF5AnnData.R | 54 ++-- R/InMemoryAnnData.R | 30 +- R/Seurat.R | 262 ++++++++------- R/SingleCellExperiment.R | 10 +- R/dummy_data.R | 302 ++++++++++++++++++ R/get_generator.R | 3 + R/messager.R | 20 ++ R/read_h5ad.R | 1 + R/read_h5ad_helpers.R | 14 +- R/to_DimReduc.R | 33 -- R/write_h5ad.R | 55 +--- R/write_h5ad_helpers.R | 10 +- README.md | 2 +- anndataR.Rproj | 4 +- man/AbstractAnnData.Rd | 57 +++- man/AnnData.Rd | 9 +- man/HDF5AnnData.Rd | 34 +- man/InMemoryAnnData.Rd | 31 +- man/Seurat.Rd | 73 +++-- man/SingleCellExperiment.Rd | 5 +- man/anndataR-package.Rd | 3 + man/dummy_AnnData.Rd | 45 +++ man/dummy_Seurat.Rd | 27 ++ man/dummy_SingleCellExperiment.Rd | 27 ++ man/dummy_data.Rd | 43 +++ man/dummy_list.Rd | 33 ++ man/from_DimReduc.Rd | 31 ++ man/get_generator.Rd | 3 + man/list_generators.Rd | 4 + man/messager.Rd | 37 +++ man/to_DimReduc.Rd | 52 ++- man/to_HDF5AnnData.Rd | 15 +- man/write_h5ad.Rd | 54 +--- man/write_h5ad_dense_array.Rd | 3 + tests/testthat/helper-dummy_data.R | 200 ------------ tests/testthat/test-HDF5-read.R | 7 +- .../testthat/test-HDF5-reticulate-roundtrip.R | 2 +- tests/testthat/test-InMemoryAnnData.R | 2 +- tests/testthat/test-Seurat.R | 16 +- tests/testthat/test-SingleCellExperiment.R | 4 +- tests/testthat/test-dummy_data.R | 10 +- tests/testthat/test-roundtrip.R | 59 +--- vignettes/design.Rmd | 9 +- vignettes/getting-started.Rmd | 5 +- 51 files changed, 1306 insertions(+), 686 deletions(-) create mode 100644 R/DimReduc.R create mode 100644 R/dummy_data.R create mode 100644 R/messager.R delete mode 100644 R/to_DimReduc.R create mode 100644 man/dummy_AnnData.Rd create mode 100644 man/dummy_Seurat.Rd create mode 100644 man/dummy_SingleCellExperiment.Rd create mode 100644 man/dummy_data.Rd create mode 100644 man/dummy_list.Rd create mode 100644 man/from_DimReduc.Rd create mode 100644 man/messager.Rd delete mode 100644 tests/testthat/helper-dummy_data.R diff --git a/.Rbuildignore b/.Rbuildignore index e7831676..72dfffb9 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -11,4 +11,4 @@ ^pkgdown$ ^vignettes/diagrams/*\.mmd$ ^vignettes/diagrams/*\.svg$ -^vignettes/design\.Rmd$ \ No newline at end of file +^vignettes/design\.Rmd$ diff --git a/DESCRIPTION b/DESCRIPTION index 831ef409..b02f89a1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -60,10 +60,11 @@ BugReports: https://github.com/scverse/anndataR/issues BioViews: Software Depends: - R (>= 3.2.0) + R (>= 4.3.0) Imports: Matrix, methods, + stats, R6 Suggests: anndata, @@ -85,5 +86,5 @@ Config/Needs/website: pkgdown, tibble, knitr, rprojroot, stringr, readr, Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE, r6 = TRUE) -RoxygenNote: 7.2.3.9000 +RoxygenNote: 7.2.3 biocViews: SingleCell, DataImport, DataRepresentation diff --git a/NAMESPACE b/NAMESPACE index 9d9bdc56..14cf49ef 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,8 @@ export(AnnData) export(InMemoryAnnData) +export(dummy_data) +export(from_DimReduc) export(from_Seurat) export(from_SingleCellExperiment) export(read_h5ad) @@ -12,8 +14,9 @@ export(to_Seurat) export(to_SingleCellExperiment) export(write_h5ad) importFrom(Matrix,as.matrix) +importFrom(Matrix,rsparsematrix) importFrom(Matrix,sparseMatrix) importFrom(Matrix,t) importFrom(R6,R6Class) -importFrom(SeuratObject,CreateDimReducObject) importFrom(methods,as) +importFrom(stats,runif) diff --git a/NEWS.md b/NEWS.md index bb89238b..e8a48444 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,13 +4,30 @@ * Add `example_data` function. * Harmonise all references to `h5ad_file` (`file`,`h5ad_path`,`ad`) +* Harmonise all references to `ad` (`adata`) * Use `@returns` in Roxygen notes to allow multi-line notes. +* Export helper function `dummy_data` + - Makes examples much less verbose and more consistent. + - Include "obsm","varm","obsp","varp" and "layers" wherever possible. +* *test-Seurat.R* + - Add unit tests for `from_Seurat`. ## Bug fixes * *DESCRIPTION*: - Add "email" tag to `Authors`. - Add `BiocViews` +* Fix `BiocCheck` WARNINGS: + - `Empty or missing \value sections found in man pages.` +* Fix `BiocCheck` NOTES: + - Make all vignettes use `BiocStyle` + - `Update R version dependency from 3.2.0 to 4.3.0.` + - `'sessionInfo' not found in vignette(s)` + - `Avoid the use of 'paste' in condition signals` + - `Avoid 'cat' and 'print' outside of 'show' methods` + - `Consider adding runnable examples to man pages that document exported objects.` + - Added helper function `messager` to avoid notes about pasting inside messages. + Can also easily turn off verbosity by setting the env var "ANNDATAR_VERBOSE". # anndataR 0.1.0 @@ -34,4 +51,4 @@ Feature list: * Converters: - SingleCellExperiment - - Seurat \ No newline at end of file + - Seurat diff --git a/R/AbstractAnnData.R b/R/AbstractAnnData.R index 60938816..8c016d8d 100644 --- a/R/AbstractAnnData.R +++ b/R/AbstractAnnData.R @@ -9,6 +9,7 @@ #' Abstract [R6][R6::R6Class] class representing an AnnData #' object. Defines the interface. #' @importFrom R6 R6Class +#' @returns An [R6][R6::R6Class] object of class "AbstractAnnData". AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint active = list( #' @field X NULL or an observation x variable matrix (without @@ -77,8 +78,9 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint #' computationally expensive. #' @param ... Optional arguments to print method. print = function(...) { - cat("AnnData object with n_obs \u00D7 n_vars = ", self$n_obs(), " \u00D7 ", self$n_vars(), "\n", sep = "") - + messager("AnnData object with n_obs \u00D7 n_var =", + self$n_obs(), "\u00D7 ", + self$n_var(), "\n") for (attribute in c( "obs", "var", @@ -97,7 +99,7 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint NULL } if (length(keys) > 0) { - cat( + messager( " ", attribute, ":", paste("'", keys, "'", collapse = ", "), "\n", @@ -111,7 +113,7 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint shape = function() { c( self$n_obs(), - self$n_vars() + self$n_var() ) }, #' @description Number of observations in the AnnData object. @@ -119,7 +121,7 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint length(self$obs_names) }, #' @description Number of variables in the AnnData object. - n_vars = function() { + n_var = function() { length(self$var_names) }, #' @description Keys ('column names') of `obs`. @@ -180,20 +182,22 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint if (nrow(mat) != self$n_obs()) { stop("nrow(", label, ") should be the same as nrow(obs)") } - if (ncol(mat) != self$n_vars()) { + if (ncol(mat) != self$n_var()) { stop("ncol(", label, ") should be the same as nrow(var)") } if (!is.null(rownames(mat))) { warning(wrap_message( - "rownames(", label, ") should be NULL, removing them from the matrix" + "rownames(", label, + ") should be NULL, removing them from the matrix" )) rownames(mat) <- NULL } if (!is.null(colnames(mat))) { warning(wrap_message( - "colnames(", label, ") should be NULL, removing them from the matrix" + "colnames(", label, + ") should be NULL, removing them from the matrix" )) colnames(mat) <- NULL } @@ -211,13 +215,18 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint # @param shape Expected dimensions of matrix # @param expected_rownames # @param excepted_colnames - .validate_aligned_array = function(mat, label, shape, expected_rownames = NULL, expected_colnames = NULL) { + .validate_aligned_array = function(mat, + label, + shape, + expected_rownames = NULL, + expected_colnames = NULL) { mat_dims <- dim(mat) for (i in seq_along(shape)) { expected_dim <- shape[i] found_dim <- mat_dims[i] if (found_dim != expected_dim) { - stop("dim(", label, ")[", i, "] should have shape: ", expected_dim, ", found: ", found_dim, ".") + stop("dim(", label, ")[", i, "] should have shape: ", + expected_dim, ", found: ", found_dim, ".") } } if (!is.null(expected_rownames) & !is.null(rownames(mat))) { @@ -250,17 +259,25 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint # @param collection A named list of 0 or more matrix elements with # whose entries will be validated # @param label The label of the collection, used for error messages - # @param shape Expected dimensions of arrays. Arrays may have more dimensions than specified here + # @param shape Expected dimensions of arrays. Arrays may have more + # dimensions than specified here # @param expected_rownames # @param expected_colnames - .validate_aligned_mapping = function(collection, label, shape, expected_rownames = NULL, expected_colnames = NULL) { + .validate_aligned_mapping = function(collection, + label, + shape, + expected_rownames = NULL, + expected_colnames = NULL) { if (is.null(collection)) { return(collection) } collection_names <- names(collection) - if (!is.list(collection) || ((length(collection) != 0) && is.null(collection_names))) { - stop(paste0(label, " must be a named list, was ", class(collection))) + if (!is.list(collection) || + ((length(collection) != 0) && + is.null(collection_names))) { + stp <- paste0(label, " must be a named list, was ", class(collection)) + stop(stp) } for (mtx_name in collection_names) { @@ -286,7 +303,7 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint expected_nrow <- switch(label, obs = self$n_obs(), - var = self$n_vars() + var = self$n_var() ) if (is.null(df)) { @@ -308,7 +325,8 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint if (.row_names_info(df) > 0) { warning(wrap_message( - "'", label, "' should not have any rownames, removing them from the data frame." + "'", label, + "' should not have any rownames, removing them from the data frame." )) rownames(df) <- NULL } @@ -320,22 +338,26 @@ AbstractAnnData <- R6::R6Class("AbstractAnnData", # nolint # are NULL or consistent with the dimensions of `obs` or `var`. # @param names A vector to validate # @param label Must be `"obs"` or `"var"` - .validate_obsvar_names = function(names, label = c("obs", "var"), check_length = TRUE) { + .validate_obsvar_names = function(names, + label = c("obs", "var"), + check_length = TRUE) { label <- match.arg(label) if (is.null(names)) { stop(wrap_message(label, "_names should be defined.")) } - # only check whether sizes match if the obsvar names has already been defined + # only check whether sizes match if the obsvar names has + # already been defined. prev_names <- attr(self, paste0(label, "_names")) if (!is.null(prev_names)) { expected_len <- length(prev_names) if (length(names) != expected_len) { - size_check_label <- if (label == "obs") "n_obs" else "n_vars" + size_check_label <- if (label == "obs") "n_obs" else "n_var" stop(wrap_message( - "length(", label, "_names) should be the same as ad$", size_check_label, "()" + "length(", label, "_names) should be the same as ad$", + size_check_label, "()" )) } } diff --git a/R/AnnData.R b/R/AnnData.R index a09fa155..dee6edfd 100644 --- a/R/AnnData.R +++ b/R/AnnData.R @@ -25,20 +25,21 @@ #' information about observations. If `NULL`, an `n_obs`×0 data frame will #' automatically be generated. #' @param var Either `NULL` or a `data.frame` with columns containing -#' information about variables. If `NULL`, an `n_vars`×0 data frame will +#' information about variables. If `NULL`, an `n_var`×0 data frame will #' automatically be generated. #' @param obsm The obsm slot is used to store multi-dimensional annotation #' arrays. It must be either `NULL` or a named list, where each element is a #' matrix with `n_obs` rows and an arbitrary number of columns. #' @param varm The varm slot is used to store multi-dimensional annotation #' arrays. It must be either `NULL` or a named list, where each element is a -#' matrix with `n_vars` rows and an arbitrary number of columns. +#' matrix with `n_var` rows and an arbitrary number of columns. #' @param obsp The obsp slot is used to store sparse multi-dimensional #' annotation arrays. It must be either `NULL` or a named list, where each #' element is a sparse matrix where each dimension has length `n_obs`. #' @param varp The varp slot is used to store sparse multi-dimensional #' annotation arrays. It must be either `NULL` or a named list, where each -#' element is a sparse matrix where each dimension has length `n_vars`. +#' element is a sparse matrix where each dimension has length `n_var`. +#' @returns An \link[anndataR]{InMemoryAnnData} object. #' #' @export #' diff --git a/R/DimReduc.R b/R/DimReduc.R new file mode 100644 index 00000000..9572bb51 --- /dev/null +++ b/R/DimReduc.R @@ -0,0 +1,192 @@ +#' Convert between \code{AnnData} and \code{DimReducObject} +#' +#' Create a named list of \link[SeuratObject]{DimReduc} objects from an +#' \pkg{AnnData}. Alternatively, can supply arbitrary matrices or +#' lists of matrices to the \code{obsm}/\code{varm} manually. +#' @source Some internal code adapted from +#' \href{https://github.com/PMBio/MuDataSeurat/blob/main/R/ReadH5MU.R}{ +#' MuDataSeurat}. +#' @param obj An object of class \link[anndataR]{InMemoryAnnData} or +#' \link[anndataR]{HDF5AnnData}. +#' @param obsm A named list of observation (cell) embedding matrices. +#' @param varm A named list of variable (gene) loading matrices. +#' @param obs_names Names of all observations. +#' @param var_names Names of all variables. +#' @param key_map A key:value mapping indicating pairs of obsm/varm slots in +#' the \code{obj} object. For some common reductions, +#' there are conventional names for the loadings slots +#' @inheritDotParams SeuratObject::CreateDimReducObject +#' @returns A named list of \link[SeuratObject]{CreateDimReducObject} objects. +#' +#' @export +#' @examples +#' adata <- dummy_data("InMemoryAnnData") +#' drl <- to_DimReduc(obj=adata) +#' drl +to_DimReduc <- function(obj = NULL, + obsm = obj$obsm, + varm = obj$varm, + obs_names = obj$obs_names, + var_names = obj$var_names, + key_map= list("X_pca" = "PCs", + "X_umap" = NULL, + "X_mofa" = "LFs"), + ...) { + + requireNamespace("SeuratObject") + + ## Need to at least have obsm + if(is.null(obsm)) { + messager("obsm is NULL; returning NULL.") + return(NULL) + } + if(!is.list(obsm) || is.null(names(obsm))){ + messager("obsm name not provided; setting name to 'obsm'.") + obsm <- list(obsm=obsm) + } + if(!is.list(varm) || is.null(names(varm))){ + messager("varm name not provided; setting name to 'varm'.") + varm <- list(varm=varm) + } + + nms_msg <- function(X, + X_name, + X_type="loadings", + dim=2){ + dim_nm <- if(dim==1) "rownames" else "colnames" + messager("No",dim_nm,"present in",paste0(X_type,"; setting to"), + shQuote(paste0(X_name,"_[1:",ncol(X),"]")) + ) + } + # Add embeddings + lapply(stats::setNames(names(obsm), + names(obsm)), + function(key){ + messager("Preparing DimReduc for:",shQuote(key)) + ## Prepare obsm + obsm_nm2 <- gsub('X_', '', key) + if(!key %in% names(key_map)){ + key_map[[key]] <- key + } + embeddings <- obsm[[key]] + if(is.null(rownames(embeddings))){ + if(is.null(obs_names)) stop("Must provide `obs_names`.") + rownames(embeddings) <- obs_names + } + if(is.null(colnames(embeddings))){ + nms_msg(X = embeddings, + X_name = obsm_nm2, + X_type = "embeddings") + colnames(embeddings) <- paste(obsm_nm2,seq(ncol(embeddings)),sep="_") + } + embeddings <- as.matrix(embeddings) + ## Prepare matching varm + loadings <- matrix() + value <- key_map[[key]] + if (key %in% names(key_map) && + !is.null(value)) { + if (value %in% names(varm)) { + loadings <- varm[[value]] + if(is.null(rownames(loadings))){ + if(is.null(var_names)) stop("Must provide `var_names`.") + rownames(loadings) <- var_names + } + if(is.null(colnames(loadings))){ + nms_msg(X = embeddings, + X_name = obsm_nm2, + X_type = "embeddings") + colnames(loadings) <- paste(obsm_nm2,seq(ncol(loadings)),sep="_") + } + } + } + ## Prepare stdev + emb_stdev <- numeric() + if ("uns" %in% names(obj)) { + if (obsm_nm2 %in% names(obj[["uns"]])) { + if ("variance" %in% names(obj[["uns"]][[obsm_nm2]])) { + emb_stdev <- sqrt(obj[["uns"]][[obsm_nm2]][["variance"]]) + } + } + } + SeuratObject::CreateDimReducObject( + embeddings = as.matrix(embeddings), + loadings = as.matrix(loadings), + key = paste0(obsm_nm2, "_"), + stdev = emb_stdev, + ... + ) + }) +} + +#' Convert between \code{AnnData} and \code{DimReducObject} +#' +#' Create obsm and varm AnnData objects from a named list of +#' \link[SeuratObject]{DimReduc} object. +#' @param obj A named list of \link[SeuratObject]{DimReduc} objects. +#' Alternatively, can be a \link[SeuratObject]{SeuratObject} from which a +#' named list of \link[SeuratObject]{DimReduc} objects will be extracted. +#' @param rm_rownames Remove rownames from all matrices. +#' @param rm_colnames Remove colnames from all matrices. +#' @param drop_null Drop elements that are NULL. +#' @returns A nested named list of \code{obsm} and \code{varm} matrices. +#' @export +#' @examples +#' adata <- dummy_data("InMemoryAnnData") +#' obj <- to_DimReduc(obj=adata) +#' obsm_varm <- from_DimReduc(obj) +from_DimReduc <- function(obj, + rm_rownames=TRUE, + rm_colnames=TRUE, + drop_null=TRUE + ){ + + requireNamespace("SeuratObject") + + ## Handle multiple object types + if(methods::is(obj,"DimReduc")){ + stop("Must provide DimReduc objects as a named list.") + } else if(SeuratObject::IsNamedList(obj)){ + if(!all(mapply(methods::is,"DimReduc"),obj)){ + stop("All elements of named list must be of class 'DimReduc'.") + } + drl <- obj + } else if(methods::is(obj,"Seurat")){ + drl <- obj@reductions + } + ## Prepare obsm + obsm <- lapply(drl, function(dr){ + x <- SeuratObject::Embeddings(dr) + if(rm_rownames) rownames(x) <- NULL + if(rm_colnames) colnames(x) <- NULL + if(all(dim(x)==c(1,1)) && all(is.na(x))) return(NULL) + x + }) + if(isTRUE(drop_null)) obsm <- Filter(Negate(is.null), obsm) + ## Prepare varm + varm <- lapply(drl, function(dr){ + x <- SeuratObject::Loadings(dr) + if(rm_rownames) rownames(x) <- NULL + if(rm_colnames) colnames(x) <- NULL + if(all(dim(x)==c(1,1)) && all(is.na(x))) return(NULL) + x + }) + if(isTRUE(drop_null)) varm <- Filter(Negate(is.null), varm) + ## Return + return( + list( + obsm = obsm, + varm = varm + ) + ) +} + + +add_DimReduc <- function(obj, + drl){ + if(length(drl)>0){ + for(nm in names(drl)){ + obj[[nm]] <- drl[[nm]] + } + } + return(obj) +} \ No newline at end of file diff --git a/R/HDF5AnnData.R b/R/HDF5AnnData.R index 9703716d..3f269f38 100644 --- a/R/HDF5AnnData.R +++ b/R/HDF5AnnData.R @@ -2,12 +2,13 @@ #' #' @description #' Implementation of an in memory AnnData object. +#' @returns An \link[anndataR]{HDF5AnnData} object. HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint inherit = AbstractAnnData, private = list( .h5obj = NULL, .n_obs = NULL, - .n_vars = NULL, + .n_var = NULL, .obs_names = NULL, .var_names = NULL, .obsm = NULL, @@ -39,7 +40,7 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint value <- private$.validate_aligned_mapping( value, "layers", - c(self$n_obs(), self$n_vars()), + c(self$n_obs(), self$n_var()), expected_rownames = rownames(self), expected_colnames = colnames(self) ) @@ -74,7 +75,7 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint value <- private$.validate_aligned_mapping( value, "varm", - c(self$n_vars()), + c(self$n_var()), expected_rownames = colnames(self) ) write_h5ad_element(value, private$.h5obj, "/varm") @@ -109,7 +110,7 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint value <- private$.validate_aligned_mapping( value, "varp", - c(self$n_vars(), self$n_vars()), + c(self$n_var(), self$n_var()), expected_rownames = colnames(self), expected_colnames = colnames(self) ) @@ -174,7 +175,8 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint # var names are cached to avoid reading all of var whenever they are # accessed if (is.null(private$.var_names)) { - private$.var_names <- read_h5ad_data_frame_index(private$.h5obj, "var") + private$.var_names <- read_h5ad_data_frame_index(private$.h5obj, + "var") } private$.var_names } else { @@ -188,8 +190,9 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint public = list( #' @description HDF5AnnData constructor #' - #' @param file The filename (character) of the `.h5ad` file. If this - #' file does not exist yet, `obs_names` and `var_names` must be provided. + #' @param file The filename (character) of the `.h5ad` file. + #' If this file does not exist yet, `obs_names` and `var_names` + #' must be provided. #' @param obs_names A vector of unique identifiers #' used to identify each row of `obs` and to act as an index into the #' observation dimension of the AnnData object. The length of `obs_names` @@ -207,20 +210,20 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint #' information about observations. If `NULL`, an `n_obs`×0 data frame will #' automatically be generated. #' @param var Either `NULL` or a `data.frame` with columns containing - #' information about variables. If `NULL`, an `n_vars`×0 data frame will + #' information about variables. If `NULL`, an `n_var`×0 data frame will #' automatically be generated. #' @param obsm The obsm slot is used to store multi-dimensional annotation - #' arrays. It must be either `NULL` or a named list, where each element is a - #' matrix with `n_obs` rows and an arbitrary number of columns. + #' arrays. It must be either `NULL` or a named list, where each element + #' is a matrix with `n_obs` rows and an arbitrary number of columns. #' @param varm The varm slot is used to store multi-dimensional annotation - #' arrays. It must be either `NULL` or a named list, where each element is a - #' matrix with `n_vars` rows and an arbitrary number of columns. + #' arrays. It must be either `NULL` or a named list, where each element + #' is a matrix with `n_var` rows and an arbitrary number of columns. #' @param obsp The obsp slot is used to store sparse multi-dimensional #' annotation arrays. It must be either `NULL` or a named list, where each #' element is a sparse matrix where each dimension has length `n_obs`. #' @param varp The varp slot is used to store sparse multi-dimensional #' annotation arrays. It must be either `NULL` or a named list, where each - #' element is a sparse matrix where each dimension has length `n_vars`. + #' element is a sparse matrix where each dimension has length `n_var`. #' #' @details #' The constructor creates a new HDF5 AnnData interface object. This can @@ -259,7 +262,7 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint # Set private object slots private$.h5obj <- file private$.n_obs <- length(obs_names) - private$.n_vars <- length(var_names) + private$.n_var <- length(var_names) private$.obs_names <- obs_names private$.var_names <- var_names } else { @@ -329,11 +332,11 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint }, #' @description Number of variables in the AnnData object - n_vars = function() { - if (is.null(private$.n_vars)) { - private$.n_vars <- length(self$var_names) + n_var = function() { + if (is.null(private$.n_var)) { + private$.n_var <- length(self$var_names) } - private$.n_vars + private$.n_var } ) ) @@ -352,18 +355,9 @@ HDF5AnnData <- R6::R6Class("HDF5AnnData", # nolint #' @export #' #' @examples -#' ad <- AnnData( -#' X = matrix(1:5, 3L, 5L), -#' layers = list( -#' A = matrix(5:1, 3L, 5L), -#' B = matrix(letters[1:5], 3L, 5L) -#' ), -#' obs = data.frame(cell = 1:3), -#' var = data.frame(gene = 1:5), -#' obs_names = LETTERS[1:3], -#' var_names = letters[1:5] -#' ) -#' to_HDF5AnnData(ad, "test.h5ad") +#' adata <- dummy_data("InMemoryAnnData") +#' adata2 <- to_HDF5AnnData(adata, "test.h5ad") +#' adata2 #' # remove file #' file.remove("test.h5ad") to_HDF5AnnData <- function(adata, file) { # nolint diff --git a/R/InMemoryAnnData.R b/R/InMemoryAnnData.R index 47b098e1..5e550137 100644 --- a/R/InMemoryAnnData.R +++ b/R/InMemoryAnnData.R @@ -2,7 +2,7 @@ #' #' @description #' Implementation of an in memory AnnData object. -#' +#' @returns An \link[anndataR]{InMemoryAnnData} object. #' @importFrom Matrix as.matrix #' #' @examples @@ -66,7 +66,7 @@ InMemoryAnnData <- R6::R6Class("InMemoryAnnData", # nolint private$.layers <- private$.validate_aligned_mapping( value, "layers", - c(self$n_obs(), self$n_vars()), + c(self$n_obs(), self$n_var()), expected_rownames = rownames(self), expected_colnames = colnames(self) ) @@ -157,7 +157,7 @@ InMemoryAnnData <- R6::R6Class("InMemoryAnnData", # nolint private$.varm <- private$.validate_aligned_mapping( value, "varm", - c(self$n_vars()), + c(self$n_var()), expected_rownames = colnames(self) ) self @@ -192,7 +192,7 @@ InMemoryAnnData <- R6::R6Class("InMemoryAnnData", # nolint private$.varp <- private$.validate_aligned_mapping( value, "varp", - c(self$n_vars(), self$n_vars()), + c(self$n_var(), self$n_var()), expected_rownames = colnames(self), expected_colnames = colnames(self) ) @@ -218,24 +218,24 @@ InMemoryAnnData <- R6::R6Class("InMemoryAnnData", # nolint #' @param layers Either `NULL` or a named list, where each element #' is an observation × variable matrix with dimensions consistent #' with `obs` and `var`. - #' @param obs Either `NULL` or a `data.frame` with columns containing information - #' about observations. If `NULL`, an `n_obs`×0 data frame will automatically - #' be generated. - #' @param var Either `NULL` or a `data.frame` with columns containing information - #' about variables. If `NULL`, an `n_vars`×0 data frame will automatically - #' be generated. + #' @param obs Either `NULL` or a `data.frame` with columns containing + #' information about observations. If `NULL`, an `n_obs`×0 data frame will + #' automatically be generated. + #' @param var Either `NULL` or a `data.frame` with columns containing + #' information about variables. If `NULL`, an `n_var`×0 data frame will + #' automatically be generated. #' @param obsm The obsm slot is used to store multi-dimensional annotation - #' arrays. It must be either `NULL` or a named list, where each element is a - #' matrix with `n_obs` rows and an arbitrary number of columns. + #' arrays. It must be either `NULL` or a named list, where each element is + #' a matrix with `n_obs` rows and an arbitrary number of columns. #' @param varm The varm slot is used to store multi-dimensional annotation - #' arrays. It must be either `NULL` or a named list, where each element is a - #' matrix with `n_vars` rows and an arbitrary number of columns. + #' arrays. It must be either `NULL` or a named list, where each element is + #' a matrix with `n_var` rows and an arbitrary number of columns. #' @param obsp The obsp slot is used to store sparse multi-dimensional #' annotation arrays. It must be either `NULL` or a named list, where each #' element is a sparse matrix where each dimension has length `n_obs`. #' @param varp The varp slot is used to store sparse multi-dimensional #' annotation arrays. It must be either `NULL` or a named list, where each - #' element is a sparse matrix where each dimension has length `n_vars`. + #' element is a sparse matrix where each dimension has length `n_var`. initialize = function(obs_names, var_names, X = NULL, diff --git a/R/Seurat.R b/R/Seurat.R index 4fd975aa..124c70c7 100644 --- a/R/Seurat.R +++ b/R/Seurat.R @@ -1,40 +1,41 @@ #' @rdname Seurat #' -#' @title Convert Between AnnData and Seurat +#' @title Convert between AnnData and Seurat #' #' @description `to_Seurat()` converts an AnnData object to a Seurat object. -#' NOTE: Only 1 assay per object is currently. -#' @param obj An AnnData object. -#' @param counts Which layer to use A `layer` within `obj` to use -#' @inheritDotParams SeuratObject::CreateSeuratObject +#' +#' @param obj An AnnData object +#' @inheritParams to_DimReduc +#' @inheritDotParams SeuratObject::CreateAssayObject +#' @returns A \link[SeuratObject]{SeuratObject}. #' #' @importFrom Matrix t #' #' @export #' @examples -#' h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") -#' ad <- anndataR::read_h5ad(h5ad_file) -#' to_Seurat(obj) -to_Seurat <- function(obj, - counts = c("X",obj$layer), - ...) { # nolint +#' adata <- dummy_data("InMemoryAnnData") +#' to_Seurat(adata) +# TODO: Add parameters to choose which how X and layers are translated into +# counts, data and scaled.data +to_Seurat <- function(obj, + key_map = list("X_pca" = "PCs", + "X_umap" = NULL, + "X_mofa" = "LFs"), + ...) { # nolint requireNamespace("SeuratObject") stopifnot(inherits(obj, "AbstractAnnData")) - use_layer <- use_layer[1] + # translate var_names # trackstatus: class=Seurat, feature=get_var_names, status=done var_names_ <- .toseurat_check_obsvar_names(obj$var_names, "var_names") - # translate obs_names # trackstatus: class=Seurat, feature=get_obs_names, status=done obs_names_ <- .toseurat_check_obsvar_names(obj$obs_names, "obs_names") - # translate var # trackstatus: class=Seurat, feature=get_var, status=done var_ <- obj$var rownames(var_) <- var_names_ - # translate obs # trackstatus: class=Seurat, feature=get_obs, status=done obs_ <- @@ -45,113 +46,116 @@ to_Seurat <- function(obj, } else { NULL } - # translate X # trackstatus: class=Seurat, feature=get_X, status=wip - seurat_slots <- c("counts","data","scale.data") + # TODO: should x_ be passed to counts or to data? + # TODO: creating a seurat object when th AnnData doesn't contain X or layers + # probably doesn't make any sense x_ <- - if(!is.null(use_layer) && - use_layer %in% obj$layers_keys()){ - seurat_slots <- seurat_slots[seurat_slots!=use_layer] - Matrix::t(obj$layers[[use_layer]]) - } else if (is.null(obj$X) && - !is.null(obj$layers_keys())){ - layer1 <- obj$layers_keys()[1] - seurat_slots <- seurat_slots[seurat_slots!=layer1] - message("No X detected. Using first layer as counts: ",layer1) - Matrix::t(obj$layers[[layer1]]) - } else if (!is.null(obj$X)) { + if (!is.null(obj$X)) { Matrix::t(obj$X) } else { - stop("obj must contain at least one matrix in either `X` or `layers`.") + mat <- Matrix::sparseMatrix( + i = integer(0), + p = c(0L), + x = integer(0), + dims = c(obj$n_var(), obj$n_obs()) + ) + attr(mat, "is_X_null") <- TRUE # nolint + mat } - dimnames(x_) <- list(var_names_, obs_names_) - x_assay <- SeuratObject::CreateAssayObject(counts = x_) - # seurat <- SeuratObject::SetAssayData(seurat, "scale.data", X3) + dimnames(x_) <- list(var_names_, obs_names_) + x_assay <- SeuratObject::CreateAssayObject(counts = x_, + ...) # create seurat object if (ncol(var_) > 0) { # don't add var metadata if the data frame does not contain any columns - x_assay <- SeuratObject::AddMetaData(x_assay, metadata = var_) + x_assay <- SeuratObject::AddMetaData(x_assay, + metadata = var_) } - seurat_obj <- SeuratObject::CreateSeuratObject(x_assay, - meta.data = obs_, - ...) + obj2 <- SeuratObject::CreateSeuratObject(x_assay, + meta.data = obs_) - # add layers + ## add layers # trackstatus: class=Seurat, feature=get_layers, status=wip - ## Store as assay data matrices - if(all(seurat_slots %in% obj$layers_keys())){ - message("Setting layers as AssayData matrices.") - for(key in seurat_slots){ - seurat_obj <- SeuratObject::SetAssayData(object = seurat_obj, - slot = key, - new.data = t(obj$layers[[key]])) - } - ## Store as multiple assays - } else { - message("Setting layers as new assays.") - for (key in obj$layers_keys()) { - layer_ <- t(obj$layers[[key]]) - dimnames(layer_) <- list(var_names_, obs_names_) - seurat_obj[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) - } + # TODO: should values be passed to counts or to data? + for (key in obj$layers_keys()) { + layer_ <- Matrix::t(obj$layers[[key]]) + dimnames(layer_) <- list(var_names_, obs_names_) + obj2[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) } - ## Add DimReducObjects - if(all(c("obsm_keys","varm_keys") %in% names(obj)) && - !is.null(obj$obsm_keys()) && - !is.null(obj$varm_keys()) ){ - for(key in obj$obsm_keys()){ - assay <- SeuratObject::Assays(seurat_obj)[1] - seurat_obj@reductions[[key]] <- to_DimReduc(obsm=obj$obsm[[key]], - varm=obj$varm[[key]], - stdev=obj$stdev[[key]], - assay=assay, - key=key) - } - } + ## Add DimReduc objects + drl <- to_DimReduc(obj = obj) + obj2 <- add_DimReduc(obj = obj2, + drl = drl) ## Return - seurat_obj + return(obj2) } .toseurat_check_obsvar_names <- function(names, label) { if (any(grepl("_", names))) { - # mimic seurat behaviour - warning(wrap_message( - "'", label, "' ", - "cannot have underscores ('_') when converting to Seurat, ", - "replacing with dashes ('-')" - )) + ## mimic seurat behaviour + wrn <- wrap_message( + shQuote(label), + " cannot have underscores ('_') when converting to Seurat;", + " replacing with dashes ('-')." + ) + warning(wrn) names <- gsub("_", "-", names) } - names } #' @rdname Seurat #' +#' @title Convert between AnnData and Seurat +#' #' @description `from_Seurat()` converts a Seurat object to an AnnData object. #' Only one assay can be converted at a time. #' -#' @param seurat_obj An object inheriting from Seurat. +#' @param obj An object inheriting from \link[Seurat]{Seurat}. #' @param output_class Name of the AnnData class. -#' Must be one of `"HDF5AnnData"` or `"InMemoryAnnData"`. -#' @param assay Assay to be converted. If NULL, `DefaultAssay()` is used. +#' Must be one of: +#' \itemize{ +#' \item{"HDF5AnnData"}{For \link[anndataR]{HDF5AnnData} output.} +#' \item{"InMemoryAnnData"}{For \link[anndataR]{InMemoryAnnData} output.} +#' } +#' @param assay Assay to be converted. If \code{NULL}, +#' \link[SeuratObject]{DefaultAssay} is used. #' @param X Which of 'counts', 'data', or 'scale.data' will be used for X. #' By default, 'counts' will be used (if it is -#' not empty), followed by 'data', then 'scale.data'. -#' The remaining non-empty slots will be stored in different layers. -#' @param ... Additional arguments passed to the generator function. +#' not empty), followed by 'data', then 'scale.data'. +#' The remaining non-empty slots will be stored in different +#' layers. +#' @param update Run \link[SeuratObject]{UpdateSeuratObject} on the +#' input \code{obj} beforehand. +#' @param ... Additional arguments passed to the generator function, +#' which will be one of the following depending on the selected +#' \code{output_class}: +#' \itemize{ +#' \item{"HDF5AnnData"}{For \link[anndataR]{HDF5AnnData} output.} +#' \item{"InMemoryAnnData"}{For \link[anndataR]{InMemoryAnnData} output.} +#' } +#' @returns An \link[anndataR]{AnnData} object. #' #' @export -# TODO: add tests with Seurat objects not created by anndataR -from_Seurat <- function(seurat_obj, - output_class = c("InMemoryAnnData", - "HDF5AnnData"), - assay = NULL, - X = "counts", ...) { # nolint +#' @examples +#' obj <- dummy_data("Seurat") +#' adata <- from_Seurat(obj) +#' adata +from_Seurat <- function(obj, + output_class = c("InMemoryAnnData", "HDF5AnnData"), + assay = NULL, + X = "counts", + update = FALSE, + ...) { # nolint + # devoptera::args2vars(from_Seurat) + + stopifnot(inherits(obj, "Seurat")) - stopifnot(inherits(seurat_obj, "Seurat")) + if(isTRUE(update)) obj <- SeuratObject::UpdateSeuratObject(obj) + output_class <- output_class[1] if (!is.null(X)) { if (!X %in% c("counts", "data", "scale.data")) { @@ -161,42 +165,51 @@ from_Seurat <- function(seurat_obj, # If a specific assay is selected, use it if (!is.null(assay)) { - if (!assay %in% names(seurat_obj@assays)) { - stop("'assay' must be NULL or one of: ", - paste0("'", names(seurat_obj@assays), "'", collapse = ", ")) + if (!assay %in% names(obj@assays)) { + stp <- paste( + "'assay' must be NULL or one of:", + paste0("'", names(obj@assays), "'", collapse = ", ") + ) + stop(stp) } assay_name <- assay } else { - assay_name <- SeuratObject::DefaultAssay(seurat_obj) + assay_name <- SeuratObject::DefaultAssay(obj) - # If Seurat object contains multiple assays, + # If Seurat object contains multiple assays, # notify user the Default one is used. - if (length(names(seurat_obj@assays)) > 1) { - message( - "There are ", length(names(seurat_obj@assays)), - " assays in the Seurat object; using the default assay ('", - assay_name, - "'). You can use the `assay` parameter to select a specific assay." + if (length(names(obj@assays)) > 1) { + messager( + "There are ", length(names(obj@assays)), + "assays in the Seurat object; using the default assay ", + paste0("(",shQuote(assay_name),")."), + "You can use the `assay` parameter to select a specific assay." ) } } # get obs_names # trackstatus: class=Seurat, feature=set_obs_names, status=done - obs_names <- colnames(seurat_obj) + obs_names <- colnames(obj) # get obs # trackstatus: class=Seurat, feature=set_obs, status=done - obs <- seurat_obj@meta.data + obs <- obj@meta.data rownames(obs) <- NULL # construct var_names # trackstatus: class=Seurat, feature=set_var_names, status=done - var_names <- rownames(seurat_obj@assays[[assay_name]]) + var_names <- rownames(obj@assays[[assay_name]]) # construct var # trackstatus: class=Seurat, feature=set_var, status=done - var <- seurat_obj@assays[[assay_name]]@meta.features + assay_slots <- methods::slotNames(obj@assays[[assay_name]]) + slot_opts <- c("meta.features","meta.data") + slot_opts <- slot_opts[slot_opts %in% assay_slots] + if(length(assay_slots)>0){ + var <- methods::slot(obj@assays[[assay_name]], + name = slot_opts[1]) + } rownames(var) <- NULL # use generator to create new AnnData object @@ -211,17 +224,14 @@ from_Seurat <- function(seurat_obj, if (!is.null(X)) { # Check if the slot is not empty - if (all(dim(SeuratObject::GetAssayData(seurat_obj, - slot = X, - assay = assay_name) - ) == 0)) { - stop("The '", X, "' slot is empty.") - } - - assay_data <- SeuratObject::GetAssayData(seurat_obj, - slot = X, - assay = assay_name) - + if (all(dim(SeuratObject::GetAssayData(obj, + layer = X, + assay = assay_name)) == 0)) { + stop("The ",shQuote(X)," slot is empty.") + } + assay_data <- SeuratObject::GetAssayData(obj, + layer = X, + assay = assay_name) # Remove names dimnames(assay_data) <- list(NULL, NULL) ad$X <- Matrix::t(assay_data) @@ -230,19 +240,23 @@ from_Seurat <- function(seurat_obj, X <- "none" } - # Add the remaining non-empty slots as layers - slots <- c("counts", "data", "scale.data") - slots <- slots[slots != X] - - for (slot in slots) { - if (!all(dim(SeuratObject::GetAssayData(seurat_obj, slot = slot)) == 0)) { - assay_data <- SeuratObject::GetAssayData(seurat_obj, - slot = slot, + # Add the remaining non-empty "slots" (renamed "layers" in Seurat>=5.0). + layer_opts <- names(obj@assays[[assay_name]]@layers) + layers <- c("counts", "data", "scale.data") + layers <- layers[layers != X & layers %in% layer_opts] + for (layer in layers) { + if (!all(dim(SeuratObject::GetAssayData(obj, layer = layer)) == 0)) { + assay_data <- SeuratObject::GetAssayData(obj, + layer = layer, assay = assay_name) dimnames(assay_data) <- list(NULL, NULL) - ad$layers[[slot]] <- Matrix::t(assay_data) + ad$layers[[layer]] <- Matrix::t(assay_data) } } - + ## Add obsm and varm + obsm_varm <- from_DimReduc(obj) + ad$obsm <- obsm_varm$obsm + ad$varm <- obsm_varm$varm + ## Return return(ad) -} \ No newline at end of file +} diff --git a/R/SingleCellExperiment.R b/R/SingleCellExperiment.R index a5843119..3568d27d 100644 --- a/R/SingleCellExperiment.R +++ b/R/SingleCellExperiment.R @@ -15,7 +15,7 @@ #' ## useful when interacting with the SingleCellExperiment ! #' library(SingleCellExperiment) #' } -#' ad <- AnnData( +#' adata <- AnnData( #' X = matrix(1:15, 3L, 5L), #' layers = list( #' A = matrix(15:1, 3L, 5L), @@ -28,9 +28,8 @@ #' ) #' #' ## construct a SingleCellExperiment from an AnnData object -#' sce <- to_SingleCellExperiment(ad) +#' sce <- to_SingleCellExperiment(adata) #' sce -#' #' @export to_SingleCellExperiment <- function(object) { # nolint stopifnot( @@ -112,7 +111,10 @@ to_SingleCellExperiment <- function(object) { # nolint #' from_SingleCellExperiment(sce, "InMemory") #' #' @export -from_SingleCellExperiment <- function(sce, output_class = c("InMemory", "HDF5AnnData"), ...) { # nolint +from_SingleCellExperiment <- function(sce, + output_class = c("InMemory", + "HDF5AnnData"), + ...) { # nolint stopifnot( inherits(sce, "SingleCellExperiment") ) diff --git a/R/dummy_data.R b/R/dummy_data.R new file mode 100644 index 00000000..f6753ba2 --- /dev/null +++ b/R/dummy_data.R @@ -0,0 +1,302 @@ +#' Dummy data +#' +#' Generate a dummy dataset in a selected format. +#' +#' @param n_obs Number of observations to generate. +#' @param n_var Number of variables to generate. +#' @param n_obsm Number of embedding dimensions. +#' @param n_varm Number of loading dimensions. +#' @param output Object type to output, one of "list", "SingleCellExperiment", +#' or "Seurat". +#' @param density Of the matrices on a scale from 0-1. +#' Passed to \link[Matrix]{rsparsematrix}. +#' @param ... Additional arguments passed to subfunctions +#' (determined by \code{output}). +#' +#' +#' @returns Object containing the generated dataset as defined by `output` +#' +#' @export +#' @examples +#' dummy <- dummy_data("InMemoryAnnData") +dummy_data <- function(output = c( + "list", + "SingleCellExperiment", + "Seurat", + "InMemoryAnnData", + "HDF5AnnData" + ), + n_obs = 10L, + n_var = 20L, + n_obsm = n_obs %/% 2, + n_varm = n_obsm, + density=.1, + ...) { + output <- match.arg(output) + + switch(output, + "list" = dummy_list(n_obs = n_obs, + n_var = n_var, + n_obsm = n_obsm, + n_varm = n_varm, + density = density), + "SingleCellExperiment" = dummy_SingleCellExperiment( + n_obs = n_obs, + n_var = n_var, + n_obsm = n_obsm, + n_varm = n_varm, + density = density, + ... + ), + "Seurat" = dummy_Seurat(n_obs = n_obs, + n_var = n_var, + n_obsm = n_obsm, + n_varm = n_varm, + density = density, + ...), + "InMemoryAnnData" = dummy_AnnData(n_obs = n_obs, + n_var = n_var, + n_obsm = n_obsm, + n_varm = n_varm, + density = density, + output_class = "InMemoryAnnData", + ...), + "HDF5AnnData" = dummy_AnnData(n_obs = n_obs, + n_var = n_var, + n_obsm = n_obsm, + n_varm = n_varm, + density = density, + output_class = "HDF5AnnData", + ...) + ) +} + +#' Dummy data list +#' +#' Generate a dummy dataset as a list +#' +#' @inheritParams dummy_data +#' @return A list with the generated dataset. +#' +#' @keywords internal +#' @importFrom Matrix rsparsematrix +#' @importFrom stats runif +dummy_list <- function(n_obs = 10L, + n_var = 20L, + n_obsm = n_obs %/% 2, + n_varm = n_obsm, + density = .1) { + # generate X + X <- Matrix::rsparsematrix(nrow = n_obs, + ncol = n_var, + density = density) + # generate layers + layers <- list( + X2 = X * 2, + X3 = X * 3 + ) + # generate obs + obs <- data.frame( + cell_type = sample(c("tcell", "bcell"), n_obs, replace = TRUE), + cluster = sample.int(3, n_obs, replace = TRUE) + ) + # generate var + var <- data.frame( + geneinfo = sample(c("a", "b", "c"), n_var, replace = TRUE) + ) + # generate obsm + obsm <- list( + "X_pca"=matrix(runif(n_obs*n_obsm), n_obs, n_obsm), + "X_umap"=matrix(runif(n_obs*n_obsm), n_obs, n_obsm) + ) + # generate varm + varm <- list( + "PCs"=matrix(runif(n_var*n_varm), n_var, n_varm) + ) + # generate obs_names + obs_names <- paste0("cell", seq_len(n_obs)) + # generate var_names + var_names <- paste0("gene", seq_len(n_var)) + # generate obsp + obsp <- list( + obs_graph=Matrix::rsparsematrix(n_obs, n_obs, density=density) + ) + # generate varp + varp <- list( + var_graph=Matrix::rsparsematrix(n_var, n_var, density=density) + ) + ## Return + list( + X = X, + obs = obs, + obs_names = obs_names, + obsm = obsm, + obsp = obsp, + var = var, + var_names = var_names, + varm = varm, + varp = varp, + layers = layers + ) +} + +#' Dummy SingleCellExperiment +#' +#' Generate a dummy dataset as a SingleCellExperiment object +#' +#' @inheritDotParams dummy_list +#' +#' @returns SingleCellExperiment containing the generated data +#' +#' @keywords internal +dummy_SingleCellExperiment <- function(...) { # nolint + if (!requireNamespace("SingleCellExperiment", quietly = TRUE)) { + stop( + "Creating a SingleCellExperiment requires the 'SingleCellExperiment'", + "package to be installed" + ) + } + + dummy <- dummy_data(...) + + assays_list <- c( + list(X = dummy$X), + dummy$layers + ) + assays_list <- lapply(assays_list, t) + + sce <- SingleCellExperiment::SingleCellExperiment( + assays = assays_list, + rowData = dummy$var, + colData = dummy$obs, + reducedDims = dummy$obsm + ) + colnames(sce) <- dummy$obs_names + rownames(sce) <- dummy$var_names + + return(sce) +} + +#' Dummy Seurat +#' +#' Generate a dummy dataset as a Seurat object +#' +#' @inheritDotParams dummy_list +#' +#' @returns Seurat containing the generated data +#' +#' @keywords internal +#' @importFrom Matrix t +dummy_Seurat <- function(..., + assay="RNA") { #nolint + if (!requireNamespace("SeuratObject", quietly = TRUE)) { + stop( + "Creating a Seurat requires the 'SeuratObject' package to be installed" + ) + } + + dummy <- dummy_data(...) + + X <- Matrix::t(dummy$X) + colnames(X) <- dummy$obs_names + rownames(X) <- dummy$var_names + + seurat <- SeuratObject::CreateSeuratObject(X, + assay = assay) + + X2 <- t(dummy$layers$X2) + colnames(X2) <- dummy$obs_names + rownames(X2) <- dummy$var_names + seurat <- SeuratObject::SetAssayData(object = seurat, + layer = "data", + new.data = X2, + assay = assay) + + X3 <- as.matrix(Matrix::t(dummy$layers$X3)) + colnames(X3) <- dummy$obs_names + rownames(X3) <- dummy$var_names + seurat <- SeuratObject::SetAssayData(object = seurat, + layer = "scale.data", + new.data = X3, + assay = assay) + + seurat <- SeuratObject::AddMetaData(object = seurat, + metadata = dummy$obs) + + ## Add DimReduc objects + drl <- to_DimReduc(obj = dummy, assay=assay) + seurat <- add_DimReduc(obj = seurat, + drl = drl) + ## Add obsp/varp as graphs + seurat@graphs <- dummy[c("obsp","varp")] + return(seurat) +} + + + +#' Dummy anndata +#' +#' Generate a dummy dataset as a \link[anndataR]{InMemoryAnnData} or +#' \link[anndataR]{HDF5AnnData} object +#' +#' @param output_class Name of the AnnData class. +#' Must be one of: +#' \itemize{ +#' \item{"InMemoryAnnData": }{ +#' Produces \link[anndataR]{InMemoryAnnData} (default).} +#' \item{"HDF5AnnData": }{ +#' Produces \link[anndataR]{HDF5AnnData}.} +#' } +#' @param file Path to save object to +#' (only used when `output_class="HDF5AnnData"`). +#' +#' @inheritDotParams dummy_list +#' +#' @returns \link[anndataR]{InMemoryAnnData} or +#' \link[anndataR]{HDF5AnnData} containing the generated data. +#' +#' @keywords internal +dummy_AnnData <- function(output_class=c("InMemoryAnnData", + "HDF5AnnData"), + file=tempfile(fileext = ".h5ad"), + ...) { #nolint + + dummy <- dummy_data(...) + output_class <- output_class[1] + generator <- get_generator(output_class) + if(output_class=="HDF5AnnData"){ + ad <- generator$new( + X = dummy$X, + # layers = dummy$layers, + obs = dummy$obs, + obs_names = dummy$obs_names, + obsm = dummy$obsm, + obsp = dummy$obsp, + var = dummy$var, + var_names = dummy$var_names, + varm = dummy$varm, + varp = dummy$varp, + file=file + ) + } else { + ad <- generator$new( + X = dummy$X, + # layers = dummy$layers, + obs = dummy$obs, + obs_names = dummy$obs_names, + obsm = dummy$obsm, + obsp = dummy$obsp, + var = dummy$var, + var_names = dummy$var_names, + varm = dummy$varm, + varp = dummy$varp, + ) + } + ## Set layers + # Do this in a second step as it seems to cause errors when setting + # during initialisation + ad$layers <- dummy$layers + ## Return + return(ad) +} + diff --git a/R/get_generator.R b/R/get_generator.R index 5f73fe78..cd0d87a0 100644 --- a/R/get_generator.R +++ b/R/get_generator.R @@ -1,4 +1,6 @@ #' List the available AnnData generators. +#' @returns A named list of generators currently available via +#' \link[anndataR]{get_generator}. list_generators <- function() { list( "HDF5AnnData" = HDF5AnnData, @@ -10,6 +12,7 @@ list_generators <- function() { #' #' @param class Name of the AnnData class. Must be one of `"HDF5AnnData"` #' or `"InMemoryAnnData"`. +#' @returns A requested AnnData generator. get_generator <- function(class = c("HDF5AnnData", "InMemoryAnnData")) { # TODO: also support directly passing the correct class? class <- match.arg(class) diff --git a/R/messager.R b/R/messager.R new file mode 100644 index 00000000..63b4c7cb --- /dev/null +++ b/R/messager.R @@ -0,0 +1,20 @@ +#' Print messages +#' +#' Conditionally print messages. +#' Allows developers to easily control verbosity of functions, +#' and meet Bioconductor requirements that dictate the message +#' must first be stored to a variable before passing to \link[base]{message}. +#' +#' @param v Whether to print messages or not. +#' @param parallel Whether to enable message print when wrapped +#' in parallelised functions. +#' @inheritParams base::paste +#' @return Null +#' @keywords internal +messager <- function(..., + v = Sys.getenv("ANNDATAR_VERBOSE")!=FALSE, + sep=" ", + collapse=NULL) { + msg <- paste(..., sep=sep, collapse=collapse) + if (v) try({message(msg)}) +} diff --git a/R/read_h5ad.R b/R/read_h5ad.R index 1914f83c..7f235fcd 100644 --- a/R/read_h5ad.R +++ b/R/read_h5ad.R @@ -34,6 +34,7 @@ read_h5ad <- function( fun <- switch(to, "SingleCellExperiment" = to_SingleCellExperiment, "Seurat" = to_Seurat, + "DimReduc" = to_DimReduc, "InMemoryAnnData" = to_InMemoryAnnData, "HDF5AnnData" = NULL ) diff --git a/R/read_h5ad_helpers.R b/R/read_h5ad_helpers.R index d1151363..859acead 100644 --- a/R/read_h5ad_helpers.R +++ b/R/read_h5ad_helpers.R @@ -84,8 +84,8 @@ read_h5ad_element <- function(file, name, type = NULL, version = NULL, ...) { #' @noRd read_h5ad_dense_array <- function(file, name, version = "0.2.0") { version <- match.arg(version) - # TODO: ideally, native = TRUE should take care of the row order and column order, - # but it doesn't + # TODO: ideally, native = TRUE should take care of the row order and + # column order, but it doesn't darr <- t(rhdf5::h5read(file, name)) # If the dense array is a 1D matrix, convert to vector if (any(dim(darr) == 1)) { @@ -169,8 +169,10 @@ read_h5ad_sparse_array <- function(file, name, version = "0.1.0", #' @details #' A "record array" (recarray) is a Python NumPy array type that contains #' "fields" that can be indexed using attributes (similar to columns in a -#' spreadsheet). See https://numpy.org/doc/stable/reference/generated/numpy.recarray.html -#' for details. +#' spreadsheet). +#' See +#' \href{https://numpy.org/doc/stable/reference/generated/numpy.recarray.html}{ +#' here} for details. #' #' They are used by **scanpy** to score marker gene testing results. #' @@ -287,7 +289,9 @@ read_h5ad_categorical <- function(file, name, version = "0.2.0") { codes <- element[["codes"]] + 1 if (!length(dim(codes)) == 1) { - stop("There is currently no support for multidimensional categorical arrays") + stop( + "There is currently no support for multidimensional categorical arrays" + ) } # Set missing values diff --git a/R/to_DimReduc.R b/R/to_DimReduc.R deleted file mode 100644 index 396b04db..00000000 --- a/R/to_DimReduc.R +++ /dev/null @@ -1,33 +0,0 @@ -#' Convert: \code{list} ==> \code{DimReducObject} -#' -#' Create a \pkg{Seurat} \code{DimReducObject} -#' from embeddings and loadings. -#' @param obsm Sample embeddings. -#' @param varm Feature loadings. -#' @param stdev Standard deviation (if applicable) for the -#' dimensional reduction. -#' @param assay Assay to use. -#' @param key Key to use (name of embedding). -#' @inheritDotParams SeuratObject::CreateDimReducObject -#' -#' @export -#' @importFrom SeuratObject CreateDimReducObject -to_DimReduc <- function(obsm, - varm, - stdev=NULL, - assay, - key, - ...) { - - Seurat::CreateDimReducObject( - embeddings = obsm, - loadings = varm, - stdev = if (is.null(stdev)) { - numeric() - } else { - as.numeric(stdev) - }, - assay = assay, - key = key, - ...) -} diff --git a/R/write_h5ad.R b/R/write_h5ad.R index df3e2c98..e8e50f43 100644 --- a/R/write_h5ad.R +++ b/R/write_h5ad.R @@ -10,58 +10,23 @@ #' @export #' #' @examples -#' adata <- AnnData( -#' X = matrix(1:15, 3L, 5L), -#' layers = list( -#' A = matrix(15:1, 3L, 5L), -#' B = matrix(letters[1:15], 3L, 5L) -#' ), -#' obs = data.frame(cell = 1:3), -#' var = data.frame(gene = 1:5), -#' obs_names = LETTERS[1:3], -#' var_names = letters[1:5] -#' ) -#' h5ad_file <- tempfile(fileext = ".h5ad") -#' write_h5ad(adata, h5ad_file) +#' # Write a InMemoryAnnData as a H5AD +#' adata <- dummy_data("InMemoryAnnData") +#' write_h5ad(adata) #' #' # Write a SingleCellExperiment as an H5AD #' if (requireNamespace("SingleCellExperiment", quietly = TRUE)) { -#' h5ad_file <- tempfile(fileext = ".h5ad") -#' ncells <- 100 -#' counts <- matrix(rpois(20000, 5), ncol = ncells) -#' logcounts <- log2(counts + 1) -#' -#' pca <- matrix(runif(ncells * 5), ncells) -#' tsne <- matrix(rnorm(ncells * 2), ncells) -#' -#' sce <- SingleCellExperiment::SingleCellExperiment( -#' assays = list(counts = counts, logcounts = logcounts), -#' reducedDims = list(PCA = pca, tSNE = tsne) -#' ) +#' sce <- dummy_data("SingleCellExperiment") +#' write_h5ad(sce) #' } #' #' # Write a Seurat as a H5AD -#' if (requireNamespace("SeuratObject", quietly = TRUE)) { -#' h5ad_file <- tempfile(fileext = ".h5ad") -#' counts <- matrix(1:15, 3L, 5L) -#' dimnames(counts) <- list( -#' letters[1:3], -#' LETTERS[1:5] -#' ) -#' gene.metadata <- data.frame( -#' row.names = LETTERS[1:5], -#' gene = 1:5 -#' ) -#' obj <- SeuratObject::CreateSeuratObject(counts, meta.data = gene.metadata) -#' cell.metadata <- data.frame( -#' row.names = letters[1:3], -#' cell = 1:3 -#' ) -#' obj <- SeuratObject::AddMetaData(obj, cell.metadata) -#' -#' write_h5ad(obj, h5ad_file) +#' if (requireNamespace("SeuratObject", quietly = TRUE)) { +#' seur <- dummy_data("Seurat") +#' write_h5ad(seur) #' } -write_h5ad <- function(object, path) { +write_h5ad <- function(object, + path=tempfile(fileext = ".h5ad")) { if (inherits(object, "SingleCellExperiment")) { from_SingleCellExperiment( object, diff --git a/R/write_h5ad_helpers.R b/R/write_h5ad_helpers.R index c6b5b24b..6fecc47f 100644 --- a/R/write_h5ad_helpers.R +++ b/R/write_h5ad_helpers.R @@ -90,8 +90,13 @@ write_h5ad_encoding <- function(file, name, encoding, version) { on.exit(rhdf5::H5Dclose(h5obj), add = TRUE) } - rhdf5::h5writeAttribute(encoding, h5obj, "encoding-type", asScalar = TRUE) # nolint - rhdf5::h5writeAttribute(version, h5obj, "encoding-version", asScalar = TRUE) # nolint + rhdf5::h5writeAttribute(encoding, h5obj, + "encoding-type", + asScalar = TRUE) # nolint + rhdf5::h5writeAttribute(version, + h5obj, + "encoding-version", + asScalar = TRUE) # nolint } #' Write H5AD dense array @@ -102,6 +107,7 @@ write_h5ad_encoding <- function(file, name, encoding, version) { #' @param file Path to a H5AD file or an open H5AD handle #' @param name Name of the element within the H5AD file #' @param version Encoding version of the element to write +#' @returns Null. write_h5ad_dense_array <- function(value, file, name, version = "0.2.0") { version <- match.arg(version) diff --git a/README.md b/README.md index a24ec9dc..5bb18b3e 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") Read an h5ad file: ``` r -adata <- read_h5ad(h5ad_path, to = "InMemoryAnnData") +adata <- read_h5ad(h5ad_file, to = "InMemoryAnnData") ``` View structure: diff --git a/anndataR.Rproj b/anndataR.Rproj index eaa6b818..5d79cb75 100644 --- a/anndataR.Rproj +++ b/anndataR.Rproj @@ -1,7 +1,7 @@ Version: 1.0 -RestoreWorkspace: Default -SaveWorkspace: Default +RestoreWorkspace: No +SaveWorkspace: No AlwaysSaveHistory: Default EnableCodeIndexing: Yes diff --git a/man/AbstractAnnData.Rd b/man/AbstractAnnData.Rd index 8ac448b8..b4dead35 100644 --- a/man/AbstractAnnData.Rd +++ b/man/AbstractAnnData.Rd @@ -3,6 +3,9 @@ \name{AbstractAnnData} \alias{AbstractAnnData} \title{AbstractAnnData} +\value{ +An \link[R6:R6Class]{R6} object of class "AbstractAnnData". +} \description{ Abstract \link[R6:R6Class]{R6} class representing an AnnData object. Defines the interface. @@ -57,10 +60,14 @@ with all elements having the same number of rows and columns as \code{var}.} \item \href{#method-AbstractAnnData-print}{\code{AbstractAnnData$print()}} \item \href{#method-AbstractAnnData-shape}{\code{AbstractAnnData$shape()}} \item \href{#method-AbstractAnnData-n_obs}{\code{AbstractAnnData$n_obs()}} -\item \href{#method-AbstractAnnData-n_vars}{\code{AbstractAnnData$n_vars()}} +\item \href{#method-AbstractAnnData-n_var}{\code{AbstractAnnData$n_var()}} \item \href{#method-AbstractAnnData-obs_keys}{\code{AbstractAnnData$obs_keys()}} \item \href{#method-AbstractAnnData-var_keys}{\code{AbstractAnnData$var_keys()}} \item \href{#method-AbstractAnnData-layers_keys}{\code{AbstractAnnData$layers_keys()}} +\item \href{#method-AbstractAnnData-obsm_keys}{\code{AbstractAnnData$obsm_keys()}} +\item \href{#method-AbstractAnnData-varm_keys}{\code{AbstractAnnData$varm_keys()}} +\item \href{#method-AbstractAnnData-obsp_keys}{\code{AbstractAnnData$obsp_keys()}} +\item \href{#method-AbstractAnnData-varp_keys}{\code{AbstractAnnData$varp_keys()}} \item \href{#method-AbstractAnnData-to_SingleCellExperiment}{\code{AbstractAnnData$to_SingleCellExperiment()}} \item \href{#method-AbstractAnnData-to_Seurat}{\code{AbstractAnnData$to_Seurat()}} \item \href{#method-AbstractAnnData-to_InMemoryAnnData}{\code{AbstractAnnData$to_InMemoryAnnData()}} @@ -108,12 +115,12 @@ Number of observations in the AnnData object. } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-AbstractAnnData-n_vars}{}}} -\subsection{Method \code{n_vars()}}{ +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AbstractAnnData-n_var}{}}} +\subsection{Method \code{n_var()}}{ Number of variables in the AnnData object. \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{AbstractAnnData$n_vars()}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{AbstractAnnData$n_var()}\if{html}{\out{
}} } } @@ -146,6 +153,46 @@ Keys (element names) of \code{layers}. \if{html}{\out{
}}\preformatted{AbstractAnnData$layers_keys()}\if{html}{\out{
}} } +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AbstractAnnData-obsm_keys}{}}} +\subsection{Method \code{obsm_keys()}}{ +Keys (element names) of \code{obsm}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{AbstractAnnData$obsm_keys()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AbstractAnnData-varm_keys}{}}} +\subsection{Method \code{varm_keys()}}{ +Keys (element names) of \code{varm}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{AbstractAnnData$varm_keys()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AbstractAnnData-obsp_keys}{}}} +\subsection{Method \code{obsp_keys()}}{ +Keys (element names) of \code{obsp}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{AbstractAnnData$obsp_keys()}\if{html}{\out{
}} +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-AbstractAnnData-varp_keys}{}}} +\subsection{Method \code{varp_keys()}}{ +Keys (element names) of \code{varp}. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{AbstractAnnData$varp_keys()}\if{html}{\out{
}} +} + } \if{html}{\out{
}} \if{html}{\out{}} diff --git a/man/AnnData.Rd b/man/AnnData.Rd index 3d9ef5da..fc827564 100644 --- a/man/AnnData.Rd +++ b/man/AnnData.Rd @@ -36,7 +36,7 @@ information about observations. If \code{NULL}, an \code{n_obs}×0 data frame wi automatically be generated.} \item{var}{Either \code{NULL} or a \code{data.frame} with columns containing -information about variables. If \code{NULL}, an \code{n_vars}×0 data frame will +information about variables. If \code{NULL}, an \code{n_var}×0 data frame will automatically be generated.} \item{layers}{Either \code{NULL} or a named list, where each element is an @@ -49,7 +49,7 @@ matrix with \code{n_obs} rows and an arbitrary number of columns.} \item{varm}{The varm slot is used to store multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each element is a -matrix with \code{n_vars} rows and an arbitrary number of columns.} +matrix with \code{n_var} rows and an arbitrary number of columns.} \item{obsp}{The obsp slot is used to store sparse multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each @@ -57,7 +57,10 @@ element is a sparse matrix where each dimension has length \code{n_obs}.} \item{varp}{The varp slot is used to store sparse multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each -element is a sparse matrix where each dimension has length \code{n_vars}.} +element is a sparse matrix where each dimension has length \code{n_var}.} +} +\value{ +An \link[anndataR]{InMemoryAnnData} object. } \description{ This class is used to represent an AnnData object in memory. diff --git a/man/HDF5AnnData.Rd b/man/HDF5AnnData.Rd index 27c57934..4cabbfb4 100644 --- a/man/HDF5AnnData.Rd +++ b/man/HDF5AnnData.Rd @@ -3,6 +3,9 @@ \name{HDF5AnnData} \alias{HDF5AnnData} \title{HDF5AnnData} +\value{ +An \link[anndataR]{HDF5AnnData} object. +} \description{ Implementation of an in memory AnnData object. } @@ -45,7 +48,7 @@ with all elements having the same number of rows and columns as \code{var}.} \itemize{ \item \href{#method-HDF5AnnData-new}{\code{HDF5AnnData$new()}} \item \href{#method-HDF5AnnData-n_obs}{\code{HDF5AnnData$n_obs()}} -\item \href{#method-HDF5AnnData-n_vars}{\code{HDF5AnnData$n_vars()}} +\item \href{#method-HDF5AnnData-n_var}{\code{HDF5AnnData$n_var()}} \item \href{#method-HDF5AnnData-clone}{\code{HDF5AnnData$clone()}} } } @@ -54,6 +57,8 @@ with all elements having the same number of rows and columns as \code{var}.} }} @@ -88,8 +95,9 @@ HDF5AnnData constructor \subsection{Arguments}{ \if{html}{\out{
}} \describe{ -\item{\code{file}}{The filename (character) of the \code{.h5ad} file. If this -file does not exist yet, \code{obs_names} and \code{var_names} must be provided.} +\item{\code{file}}{The filename (character) of the \code{.h5ad} file. +If this file does not exist yet, \code{obs_names} and \code{var_names} +must be provided.} \item{\code{obs_names}}{A vector of unique identifiers used to identify each row of \code{obs} and to act as an index into the @@ -109,7 +117,7 @@ information about observations. If \code{NULL}, an \code{n_obs}×0 data frame wi automatically be generated.} \item{\code{var}}{Either \code{NULL} or a \code{data.frame} with columns containing -information about variables. If \code{NULL}, an \code{n_vars}×0 data frame will +information about variables. If \code{NULL}, an \code{n_var}×0 data frame will automatically be generated.} \item{\code{layers}}{Either \code{NULL} or a named list, where each element is an @@ -117,12 +125,12 @@ observation × variable matrix with dimensions consistent with \code{obs} and \code{var}.} \item{\code{obsm}}{The obsm slot is used to store multi-dimensional annotation -arrays. It must be either \code{NULL} or a named list, where each element is a -matrix with \code{n_obs} rows and an arbitrary number of columns.} +arrays. It must be either \code{NULL} or a named list, where each element +is a matrix with \code{n_obs} rows and an arbitrary number of columns.} \item{\code{varm}}{The varm slot is used to store multi-dimensional annotation -arrays. It must be either \code{NULL} or a named list, where each element is a -matrix with \code{n_vars} rows and an arbitrary number of columns.} +arrays. It must be either \code{NULL} or a named list, where each element +is a matrix with \code{n_var} rows and an arbitrary number of columns.} \item{\code{obsp}}{The obsp slot is used to store sparse multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each @@ -130,7 +138,7 @@ element is a sparse matrix where each dimension has length \code{n_obs}.} \item{\code{varp}}{The varp slot is used to store sparse multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each -element is a sparse matrix where each dimension has length \code{n_vars}.} +element is a sparse matrix where each dimension has length \code{n_var}.} } \if{html}{\out{
}} } @@ -155,12 +163,12 @@ Number of observations in the AnnData object } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-HDF5AnnData-n_vars}{}}} -\subsection{Method \code{n_vars()}}{ +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-HDF5AnnData-n_var}{}}} +\subsection{Method \code{n_var()}}{ Number of variables in the AnnData object \subsection{Usage}{ -\if{html}{\out{
}}\preformatted{HDF5AnnData$n_vars()}\if{html}{\out{
}} +\if{html}{\out{
}}\preformatted{HDF5AnnData$n_var()}\if{html}{\out{
}} } } diff --git a/man/InMemoryAnnData.Rd b/man/InMemoryAnnData.Rd index 1a82e68e..5e5e5792 100644 --- a/man/InMemoryAnnData.Rd +++ b/man/InMemoryAnnData.Rd @@ -3,6 +3,9 @@ \name{InMemoryAnnData} \alias{InMemoryAnnData} \title{InMemoryAnnData} +\value{ +An \link[anndataR]{InMemoryAnnData} object. +} \description{ Implementation of an in memory AnnData object. } @@ -87,8 +90,10 @@ with all elements having the same number of rows and columns as \code{var}.} }} @@ -138,25 +145,25 @@ object.} \item{\code{X}}{Either \code{NULL} or a observation × variable matrix with dimensions consistent with \code{obs} and \code{var}.} -\item{\code{obs}}{Either \code{NULL} or a \code{data.frame} with columns containing information -about observations. If \code{NULL}, an \code{n_obs}×0 data frame will automatically -be generated.} +\item{\code{obs}}{Either \code{NULL} or a \code{data.frame} with columns containing +information about observations. If \code{NULL}, an \code{n_obs}×0 data frame will +automatically be generated.} -\item{\code{var}}{Either \code{NULL} or a \code{data.frame} with columns containing information -about variables. If \code{NULL}, an \code{n_vars}×0 data frame will automatically -be generated.} +\item{\code{var}}{Either \code{NULL} or a \code{data.frame} with columns containing +information about variables. If \code{NULL}, an \code{n_var}×0 data frame will +automatically be generated.} \item{\code{layers}}{Either \code{NULL} or a named list, where each element is an observation × variable matrix with dimensions consistent with \code{obs} and \code{var}.} \item{\code{obsm}}{The obsm slot is used to store multi-dimensional annotation -arrays. It must be either \code{NULL} or a named list, where each element is a -matrix with \code{n_obs} rows and an arbitrary number of columns.} +arrays. It must be either \code{NULL} or a named list, where each element is +a matrix with \code{n_obs} rows and an arbitrary number of columns.} \item{\code{varm}}{The varm slot is used to store multi-dimensional annotation -arrays. It must be either \code{NULL} or a named list, where each element is a -matrix with \code{n_vars} rows and an arbitrary number of columns.} +arrays. It must be either \code{NULL} or a named list, where each element is +a matrix with \code{n_var} rows and an arbitrary number of columns.} \item{\code{obsp}}{The obsp slot is used to store sparse multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each @@ -164,7 +171,7 @@ element is a sparse matrix where each dimension has length \code{n_obs}.} \item{\code{varp}}{The varp slot is used to store sparse multi-dimensional annotation arrays. It must be either \code{NULL} or a named list, where each -element is a sparse matrix where each dimension has length \code{n_vars}.} +element is a sparse matrix where each dimension has length \code{n_var}.} } \if{html}{\out{}} } diff --git a/man/Seurat.Rd b/man/Seurat.Rd index f540b8c4..4c079d3b 100644 --- a/man/Seurat.Rd +++ b/man/Seurat.Rd @@ -3,62 +3,79 @@ \name{to_Seurat} \alias{to_Seurat} \alias{from_Seurat} -\title{Convert Between AnnData and Seurat} +\title{Convert between AnnData and Seurat} \usage{ -to_Seurat(obj, counts = c("X", obj$layer), ...) +to_Seurat( + obj, + key_map = list(X_pca = "PCs", X_umap = NULL, X_mofa = "LFs"), + ... +) from_Seurat( - seurat_obj, + obj, output_class = c("InMemoryAnnData", "HDF5AnnData"), assay = NULL, X = "counts", + update = FALSE, ... ) } \arguments{ -\item{obj}{An AnnData object.} +\item{obj}{An object inheriting from \link[Seurat]{Seurat}.} -\item{counts}{Which layer to use A \code{layer} within \code{obj} to use} +\item{key_map}{A key:value mapping indicating pairs of obsm/varm slots in +the \code{obj} object. For some common reductions, +there are conventional names for the loadings slots} \item{...}{ - Arguments passed on to \code{\link[SeuratObject:CreateSeuratObject]{SeuratObject::CreateSeuratObject}} + Arguments passed on to \code{\link[SeuratObject:CreateAssayObject]{SeuratObject::CreateAssayObject}} \describe{ - \item{\code{project}}{\link[SeuratObject]{Project} name for the \code{Seurat} object} - \item{\code{names.field}}{For the initial identity class for each cell, choose this -field from the cell's name. E.g. If your cells are named as -BARCODE_CLUSTER_CELLTYPE in the input matrix, set \code{names.field} to 3 to -set the initial identities to CELLTYPE.} - \item{\code{names.delim}}{For the initial identity class for each cell, choose this -delimiter from the cell's column name. E.g. If your cells are named as -BARCODE-CLUSTER-CELLTYPE, set this to \dQuote{-} to separate the cell name -into its component parts for picking the relevant field.} - \item{\code{meta.data}}{Additional cell-level metadata to add to the Seurat object. -Should be a \code{\link[base]{data.frame}} where the rows are cell names and -the columns are additional metadata fields. Row names in the metadata need -to match the column names of the counts matrix.} + \item{\code{counts}}{Unnormalized data such as raw counts or TPMs} + \item{\code{data}}{Prenormalized data; if provided, do not pass \code{counts}} + \item{\code{min.cells}}{Include features detected in at least this many cells. Will +subset the counts matrix as well. To reintroduce excluded features, create a +new object with a lower cutoff} + \item{\code{min.features}}{Include cells where at least this many features are +detected} + \item{\code{key}}{Optional key to initialize assay with} + \item{\code{check.matrix}}{Check counts matrix for NA, NaN, Inf, and +non-integer values} }} -\item{seurat_obj}{An object inheriting from Seurat.} - \item{output_class}{Name of the AnnData class. -Must be one of \code{"HDF5AnnData"} or \code{"InMemoryAnnData"}.} +Must be one of: +\itemize{ +\item{"HDF5AnnData"}{For \link[anndataR]{HDF5AnnData} output.} +\item{"InMemoryAnnData"}{For \link[anndataR]{InMemoryAnnData} output.} +}} -\item{assay}{Assay to be converted. If NULL, \code{DefaultAssay()} is used.} +\item{assay}{Assay to be converted. If \code{NULL}, +\link[SeuratObject]{DefaultAssay} is used.} \item{X}{Which of 'counts', 'data', or 'scale.data' will be used for X. By default, 'counts' will be used (if it is not empty), followed by 'data', then 'scale.data'. -The remaining non-empty slots will be stored in different layers.} +The remaining non-empty slots will be stored in different +layers.} + +\item{update}{Run \link[SeuratObject]{UpdateSeuratObject} on the +input \code{obj} beforehand.} +} +\value{ +A \link[SeuratObject]{SeuratObject}. + +An \link[anndataR]{AnnData} object. } \description{ \code{to_Seurat()} converts an AnnData object to a Seurat object. -NOTE: Only 1 assay per object is currently. \code{from_Seurat()} converts a Seurat object to an AnnData object. Only one assay can be converted at a time. } \examples{ -h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") -ad <- anndataR::read_h5ad(h5ad_file) -to_Seurat(obj) +adata <- dummy_data("InMemoryAnnData") +to_Seurat(adata) +obj <- dummy_data("Seurat") +adata <- from_Seurat(obj) +adata } diff --git a/man/SingleCellExperiment.Rd b/man/SingleCellExperiment.Rd index eb5ca171..d6979db9 100644 --- a/man/SingleCellExperiment.Rd +++ b/man/SingleCellExperiment.Rd @@ -43,7 +43,7 @@ if (interactive()) { ## useful when interacting with the SingleCellExperiment ! library(SingleCellExperiment) } -ad <- AnnData( +adata <- AnnData( X = matrix(1:15, 3L, 5L), layers = list( A = matrix(15:1, 3L, 5L), @@ -56,9 +56,8 @@ ad <- AnnData( ) ## construct a SingleCellExperiment from an AnnData object -sce <- to_SingleCellExperiment(ad) +sce <- to_SingleCellExperiment(adata) sce - ## construct an AnnData object from a SingleCellExperiment from_SingleCellExperiment(sce, "InMemory") diff --git a/man/anndataR-package.Rd b/man/anndataR-package.Rd index 75638374..5cc2b006 100644 --- a/man/anndataR-package.Rd +++ b/man/anndataR-package.Rd @@ -25,11 +25,14 @@ Authors: \item Luke Zappia \email{luke@lazappi.id.au} (\href{https://orcid.org/0000-0001-7744-8565}{ORCID}) (lazappi) \item Martin Morgan \email{mtmorgan.bioc@gmail.com} (\href{https://orcid.org/0000-0002-5874-8148}{ORCID}) (mtmorgan) \item Louise Deconinck \email{louise.deconinck@gmail.com} (\href{https://orcid.org/0000-0001-8100-6823}{ORCID}) (LouiseDck) + \item Brian Schilder \email{brian_schilder@alumni.brown.edu} (\href{https://orcid.org/0000-0001-5949-2191}{ORCID}) (bschilder) } Other contributors: \itemize{ \item Danila Bredikhin \email{danila.bredikhin@embl.de} (\href{https://orcid.org/0000-0001-8089-6983}{ORCID}) (gtca) [contributor] + \item Isaac Virshup (\href{https://orcid.org/0000-0002-1710-8945}{ORCID}) (ivirshup) [contributor] + \item Chananchida Sang-aram (\href{https://orcid.org/0000-0002-0922-0822}{ORCID}) (csangara) [contributor] } } diff --git a/man/dummy_AnnData.Rd b/man/dummy_AnnData.Rd new file mode 100644 index 00000000..834474aa --- /dev/null +++ b/man/dummy_AnnData.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dummy_data.R +\name{dummy_AnnData} +\alias{dummy_AnnData} +\title{Dummy anndata} +\usage{ +dummy_AnnData( + output_class = c("InMemoryAnnData", "HDF5AnnData"), + file = tempfile(fileext = ".h5ad"), + ... +) +} +\arguments{ +\item{output_class}{Name of the AnnData class. +Must be one of: +\itemize{ +\item{"InMemoryAnnData": }{ +Produces \link[anndataR]{InMemoryAnnData} (default).} +\item{"HDF5AnnData": }{ +Produces \link[anndataR]{HDF5AnnData}.} +}} + +\item{file}{Path to save object to +(only used when \code{output_class="HDF5AnnData"}).} + +\item{...}{ + Arguments passed on to \code{\link[=dummy_list]{dummy_list}} + \describe{ + \item{\code{n_obs}}{Number of observations to generate.} + \item{\code{n_var}}{Number of variables to generate.} + \item{\code{n_obsm}}{Number of embedding dimensions.} + \item{\code{n_varm}}{Number of loading dimensions.} + \item{\code{density}}{Of the matrices on a scale from 0-1. +Passed to \link[Matrix]{rsparsematrix}.} + }} +} +\value{ +\link[anndataR]{InMemoryAnnData} or +\link[anndataR]{HDF5AnnData} containing the generated data. +} +\description{ +Generate a dummy dataset as a \link[anndataR]{InMemoryAnnData} or +\link[anndataR]{HDF5AnnData} object +} +\keyword{internal} diff --git a/man/dummy_Seurat.Rd b/man/dummy_Seurat.Rd new file mode 100644 index 00000000..dbaa5a15 --- /dev/null +++ b/man/dummy_Seurat.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dummy_data.R +\name{dummy_Seurat} +\alias{dummy_Seurat} +\title{Dummy Seurat} +\usage{ +dummy_Seurat(..., assay = "RNA") +} +\arguments{ +\item{...}{ + Arguments passed on to \code{\link[=dummy_list]{dummy_list}} + \describe{ + \item{\code{n_obs}}{Number of observations to generate.} + \item{\code{n_var}}{Number of variables to generate.} + \item{\code{n_obsm}}{Number of embedding dimensions.} + \item{\code{n_varm}}{Number of loading dimensions.} + \item{\code{density}}{Of the matrices on a scale from 0-1. +Passed to \link[Matrix]{rsparsematrix}.} + }} +} +\value{ +Seurat containing the generated data +} +\description{ +Generate a dummy dataset as a Seurat object +} +\keyword{internal} diff --git a/man/dummy_SingleCellExperiment.Rd b/man/dummy_SingleCellExperiment.Rd new file mode 100644 index 00000000..1d54efed --- /dev/null +++ b/man/dummy_SingleCellExperiment.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dummy_data.R +\name{dummy_SingleCellExperiment} +\alias{dummy_SingleCellExperiment} +\title{Dummy SingleCellExperiment} +\usage{ +dummy_SingleCellExperiment(...) +} +\arguments{ +\item{...}{ + Arguments passed on to \code{\link[=dummy_list]{dummy_list}} + \describe{ + \item{\code{n_obs}}{Number of observations to generate.} + \item{\code{n_var}}{Number of variables to generate.} + \item{\code{n_obsm}}{Number of embedding dimensions.} + \item{\code{n_varm}}{Number of loading dimensions.} + \item{\code{density}}{Of the matrices on a scale from 0-1. +Passed to \link[Matrix]{rsparsematrix}.} + }} +} +\value{ +SingleCellExperiment containing the generated data +} +\description{ +Generate a dummy dataset as a SingleCellExperiment object +} +\keyword{internal} diff --git a/man/dummy_data.Rd b/man/dummy_data.Rd new file mode 100644 index 00000000..abe9d019 --- /dev/null +++ b/man/dummy_data.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dummy_data.R +\name{dummy_data} +\alias{dummy_data} +\title{Dummy data} +\usage{ +dummy_data( + output = c("list", "SingleCellExperiment", "Seurat", "InMemoryAnnData", "HDF5AnnData"), + n_obs = 10L, + n_var = 20L, + n_obsm = n_obs\%/\%2, + n_varm = n_obsm, + density = 0.1, + ... +) +} +\arguments{ +\item{output}{Object type to output, one of "list", "SingleCellExperiment", +or "Seurat".} + +\item{n_obs}{Number of observations to generate.} + +\item{n_var}{Number of variables to generate.} + +\item{n_obsm}{Number of embedding dimensions.} + +\item{n_varm}{Number of loading dimensions.} + +\item{density}{Of the matrices on a scale from 0-1. +Passed to \link[Matrix]{rsparsematrix}.} + +\item{...}{Additional arguments passed to subfunctions +(determined by \code{output}).} +} +\value{ +Object containing the generated dataset as defined by \code{output} +} +\description{ +Generate a dummy dataset in a selected format. +} +\examples{ +dummy <- dummy_data("InMemoryAnnData") +} diff --git a/man/dummy_list.Rd b/man/dummy_list.Rd new file mode 100644 index 00000000..c1adface --- /dev/null +++ b/man/dummy_list.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dummy_data.R +\name{dummy_list} +\alias{dummy_list} +\title{Dummy data list} +\usage{ +dummy_list( + n_obs = 10L, + n_var = 20L, + n_obsm = n_obs\%/\%2, + n_varm = n_obsm, + density = 0.1 +) +} +\arguments{ +\item{n_obs}{Number of observations to generate.} + +\item{n_var}{Number of variables to generate.} + +\item{n_obsm}{Number of embedding dimensions.} + +\item{n_varm}{Number of loading dimensions.} + +\item{density}{Of the matrices on a scale from 0-1. +Passed to \link[Matrix]{rsparsematrix}.} +} +\value{ +A list with the generated dataset. +} +\description{ +Generate a dummy dataset as a list +} +\keyword{internal} diff --git a/man/from_DimReduc.Rd b/man/from_DimReduc.Rd new file mode 100644 index 00000000..e5ed7dda --- /dev/null +++ b/man/from_DimReduc.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/DimReduc.R +\name{from_DimReduc} +\alias{from_DimReduc} +\title{Convert between \code{AnnData} and \code{DimReducObject}} +\usage{ +from_DimReduc(obj, rm_rownames = TRUE, rm_colnames = TRUE, drop_null = TRUE) +} +\arguments{ +\item{obj}{A named list of \link[SeuratObject]{DimReduc} objects. +Alternatively, can be a \link[SeuratObject]{SeuratObject} from which a +named list of \link[SeuratObject]{DimReduc} objects will be extracted.} + +\item{rm_rownames}{Remove rownames from all matrices.} + +\item{rm_colnames}{Remove colnames from all matrices.} + +\item{drop_null}{Drop elements that are NULL.} +} +\value{ +A nested named list of \code{obsm} and \code{varm} matrices. +} +\description{ +Create obsm and varm AnnData objects from a named list of +\link[SeuratObject]{DimReduc} object. +} +\examples{ +adata <- dummy_data("InMemoryAnnData") +obj <- to_DimReduc(obj=adata) +obsm_varm <- from_DimReduc(obj) +} diff --git a/man/get_generator.Rd b/man/get_generator.Rd index f2d4de5c..7d95723a 100644 --- a/man/get_generator.Rd +++ b/man/get_generator.Rd @@ -10,6 +10,9 @@ get_generator(class = c("HDF5AnnData", "InMemoryAnnData")) \item{class}{Name of the AnnData class. Must be one of \code{"HDF5AnnData"} or \code{"InMemoryAnnData"}.} } +\value{ +A requested AnnData generator. +} \description{ Fetch an AnnData generator. } diff --git a/man/list_generators.Rd b/man/list_generators.Rd index 637308c7..cb4e1879 100644 --- a/man/list_generators.Rd +++ b/man/list_generators.Rd @@ -6,6 +6,10 @@ \usage{ list_generators() } +\value{ +A named list of generators currently available via +\link[anndataR]{get_generator}. +} \description{ List the available AnnData generators. } diff --git a/man/messager.Rd b/man/messager.Rd new file mode 100644 index 00000000..7e33d918 --- /dev/null +++ b/man/messager.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/messager.R +\name{messager} +\alias{messager} +\title{Print messages} +\usage{ +messager( + ..., + v = Sys.getenv("ANNDATAR_VERBOSE") != FALSE, + sep = " ", + collapse = NULL +) +} +\arguments{ +\item{...}{one or more \R objects, to be converted to character vectors.} + +\item{v}{Whether to print messages or not.} + +\item{sep}{a character string to separate the terms. Not + \code{\link[base]{NA_character_}}.} + +\item{collapse}{an optional character string to separate the results. Not + \code{\link[base]{NA_character_}}.} + +\item{parallel}{Whether to enable message print when wrapped +in parallelised functions.} +} +\value{ +Null +} +\description{ +Conditionally print messages. +Allows developers to easily control verbosity of functions, +and meet Bioconductor requirements that dictate the message +must first be stored to a variable before passing to \link[base]{message}. +} +\keyword{internal} diff --git a/man/to_DimReduc.Rd b/man/to_DimReduc.Rd index e9ddea70..aaef758a 100644 --- a/man/to_DimReduc.Rd +++ b/man/to_DimReduc.Rd @@ -1,22 +1,39 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/to_DimReduc.R +% Please edit documentation in R/DimReduc.R \name{to_DimReduc} \alias{to_DimReduc} -\title{Convert: \code{list} ==> \code{DimReducObject}} +\title{Convert between \code{AnnData} and \code{DimReducObject}} +\source{ +Some internal code adapted from +\href{https://github.com/PMBio/MuDataSeurat/blob/main/R/ReadH5MU.R}{ +MuDataSeurat}. +} \usage{ -to_DimReduc(obsm, varm, stdev = NULL, assay, key, ...) +to_DimReduc( + obj = NULL, + obsm = obj$obsm, + varm = obj$varm, + obs_names = obj$obs_names, + var_names = obj$var_names, + key_map = list(X_pca = "PCs", X_umap = NULL, X_mofa = "LFs"), + ... +) } \arguments{ -\item{obsm}{Sample embeddings.} +\item{obj}{An object of class \link[anndataR]{InMemoryAnnData} or +\link[anndataR]{HDF5AnnData}.} + +\item{obsm}{A named list of observation (cell) embedding matrices.} -\item{varm}{Feature loadings.} +\item{varm}{A named list of variable (gene) loading matrices.} -\item{stdev}{Standard deviation (if applicable) for the -dimensional reduction.} +\item{obs_names}{Names of all observations.} -\item{assay}{Assay to use.} +\item{var_names}{Names of all variables.} -\item{key}{Key to use (name of embedding).} +\item{key_map}{A key:value mapping indicating pairs of obsm/varm slots in +the \code{obj} object. For some common reductions, +there are conventional names for the loadings slots} \item{...}{ Arguments passed on to \code{\link[SeuratObject:CreateDimReducObject]{SeuratObject::CreateDimReducObject}} @@ -24,13 +41,26 @@ dimensional reduction.} \item{\code{embeddings}}{A matrix with the cell embeddings} \item{\code{loadings}}{A matrix with the feature loadings} \item{\code{projected}}{A matrix with the projected feature loadings} + \item{\code{assay}}{Assay used to calculate this dimensional reduction} + \item{\code{stdev}}{Standard deviation (if applicable) for the dimensional reduction} + \item{\code{key}}{A character string to facilitate looking up features from a +specific DimReduc} \item{\code{global}}{Specify this as a global reduction (useful for visualizations)} \item{\code{jackstraw}}{Results from the JackStraw function} \item{\code{misc}}{list for the user to store any additional information associated with the dimensional reduction} }} } +\value{ +A named list of \link[SeuratObject]{CreateDimReducObject} objects. +} \description{ -Create a \pkg{Seurat} \code{DimReducObject} -from embeddings and loadings. +Create a named list of \link[SeuratObject]{DimReduc} objects from an +\pkg{AnnData}. Alternatively, can supply arbitrary matrices or +lists of matrices to the \code{obsm}/\code{varm} manually. +} +\examples{ +adata <- dummy_data("InMemoryAnnData") +drl <- to_DimReduc(obj=adata) +drl } diff --git a/man/to_HDF5AnnData.Rd b/man/to_HDF5AnnData.Rd index a371ae35..40078772 100644 --- a/man/to_HDF5AnnData.Rd +++ b/man/to_HDF5AnnData.Rd @@ -20,18 +20,9 @@ This function takes an AnnData object and converts it to an HDF5AnnData object, loading all fields into memory. } \examples{ -ad <- AnnData( - X = matrix(1:5, 3L, 5L), - layers = list( - A = matrix(5:1, 3L, 5L), - B = matrix(letters[1:5], 3L, 5L) - ), - obs = data.frame(cell = 1:3), - var = data.frame(gene = 1:5), - obs_names = LETTERS[1:3], - var_names = letters[1:5] -) -to_HDF5AnnData(ad, "test.h5ad") +adata <- dummy_data("InMemoryAnnData") +adata2 <- to_HDF5AnnData(adata, "test.h5ad") +adata2 # remove file file.remove("test.h5ad") } diff --git a/man/write_h5ad.Rd b/man/write_h5ad.Rd index 9247ca6b..dc715d60 100644 --- a/man/write_h5ad.Rd +++ b/man/write_h5ad.Rd @@ -4,7 +4,7 @@ \alias{write_h5ad} \title{Write H5AD} \usage{ -write_h5ad(object, path) +write_h5ad(object, path = tempfile(fileext = ".h5ad")) } \arguments{ \item{object}{The object to write, either a "SingleCellExperiment" or a @@ -19,55 +19,19 @@ write_h5ad(object, path) Write an H5AD file } \examples{ -adata <- AnnData( - X = matrix(1:15, 3L, 5L), - layers = list( - A = matrix(15:1, 3L, 5L), - B = matrix(letters[1:15], 3L, 5L) - ), - obs = data.frame(cell = 1:3), - var = data.frame(gene = 1:5), - obs_names = LETTERS[1:3], - var_names = letters[1:5] -) -h5ad_file <- tempfile(fileext = ".h5ad") -write_h5ad(adata, h5ad_file) +# Write a InMemoryAnnData as a H5AD +adata <- dummy_data("InMemoryAnnData") +write_h5ad(adata) # Write a SingleCellExperiment as an H5AD if (requireNamespace("SingleCellExperiment", quietly = TRUE)) { - h5ad_file <- tempfile(fileext = ".h5ad") - ncells <- 100 - counts <- matrix(rpois(20000, 5), ncol = ncells) - logcounts <- log2(counts + 1) - - pca <- matrix(runif(ncells * 5), ncells) - tsne <- matrix(rnorm(ncells * 2), ncells) - - sce <- SingleCellExperiment::SingleCellExperiment( - assays = list(counts = counts, logcounts = logcounts), - reducedDims = list(PCA = pca, tSNE = tsne) - ) + sce <- dummy_data("SingleCellExperiment") + write_h5ad(sce) } # Write a Seurat as a H5AD -if (requireNamespace("SeuratObject", quietly = TRUE)) { - h5ad_file <- tempfile(fileext = ".h5ad") - counts <- matrix(1:15, 3L, 5L) - dimnames(counts) <- list( - letters[1:3], - LETTERS[1:5] - ) - gene.metadata <- data.frame( - row.names = LETTERS[1:5], - gene = 1:5 - ) - obj <- SeuratObject::CreateSeuratObject(counts, meta.data = gene.metadata) - cell.metadata <- data.frame( - row.names = letters[1:3], - cell = 1:3 - ) - obj <- SeuratObject::AddMetaData(obj, cell.metadata) - - write_h5ad(obj, h5ad_file) +if (requireNamespace("SeuratObject", quietly = TRUE)) { + seur <- dummy_data("Seurat") + write_h5ad(seur) } } diff --git a/man/write_h5ad_dense_array.Rd b/man/write_h5ad_dense_array.Rd index b33b5ce6..a74ec444 100644 --- a/man/write_h5ad_dense_array.Rd +++ b/man/write_h5ad_dense_array.Rd @@ -15,6 +15,9 @@ write_h5ad_dense_array(value, file, name, version = "0.2.0") \item{version}{Encoding version of the element to write} } +\value{ +Null. +} \description{ Write a dense array to an H5AD file } diff --git a/tests/testthat/helper-dummy_data.R b/tests/testthat/helper-dummy_data.R deleted file mode 100644 index 8cb0fc61..00000000 --- a/tests/testthat/helper-dummy_data.R +++ /dev/null @@ -1,200 +0,0 @@ -#' Dummy data -#' -#' Generate a dummy dataset -#' -#' @param n_obs Number of observations to generate -#' @param n_vars Number of variables to generate -#' @param output Object type to output, one of "list", "SingleCellExperiment", -#' or "Seurat" -#' -#' @returns Object containing the generated dataset as defined by `output` -#' -#' @examples -#' dummy <- dummy_data() -dummy_data <- function(n_obs = 10L, n_vars = 20L, - output = c( - "list", - "SingleCellExperiment", - "Seurat", - "InMemoryAnnData", - "HDF5AnnData" - )) { - output <- match.arg(output) - - switch(output, - "list" = dummy_list(n_obs = n_obs, n_vars = n_vars), - "SingleCellExperiment" = dummy_SingleCellExperiment( - n_obs = n_obs, n_vars = n_vars - ), - "Seurat" = dummy_Seurat(n_obs = n_obs, n_vars = n_vars), - "InMemoryAnnData" = dummy_anndata(n_obs = n_obs, n_vars = n_vars, - output_class = "InMemoryAnnData"), - "HDF5AnnData" = dummy_anndata(n_obs = n_obs, n_vars = n_vars, - output_class = "HDF5AnnData") - ) -} - -#' Dummy data list -#' -#' Generate a dummy dataset as a list -#' -#' @param n_obs Number of observations to generate -#' @param n_vars Number of variables to generate -#' -#' @return A list with the generated dataset -dummy_list <- function(n_obs = 10L, n_vars = 20L) { - # generate X - X <- Matrix::rsparsematrix(nrow = n_obs, ncol = n_vars, density = .1) - - # generate layers - layers <- list( - X2 = X * 2, - X3 = X * 3 - ) - - # generate obs - obs <- data.frame( - cell_type = sample(c("tcell", "bcell"), n_obs, replace = TRUE), - cluster = sample.int(3, n_obs, replace = TRUE) - ) - - # generate var - var <- data.frame( - geneinfo = sample(c("a", "b", "c"), n_vars, replace = TRUE) - ) - - # generate obs_names - obs_names <- paste0("cell", seq_len(n_obs)) - - # generate var_names - var_names <- paste0("gene", seq_len(n_vars)) - - list( - X = X, - obs = obs, - obs_names = obs_names, - var = var, - var_names = var_names, - layers = layers - ) -} - -#' Dummy SingleCellExperiment -#' -#' Generate a dummy dataset as a SingleCellExperiment object -#' -#' @param ... Parameters passed to `dummy_list` -#' -#' @return SingleCellExperiment containing the generated data -dummy_SingleCellExperiment <- function(...) { # nolint - if (!requireNamespace("SingleCellExperiment", quietly = TRUE)) { - stop( - "Creating a SingleCellExperiment requires the 'SingleCellExperiment'", - "package to be installed" - ) - } - - dummy <- dummy_data(...) - - assays_list <- c( - list(X = dummy$X), - dummy$layers - ) - assays_list <- lapply(assays_list, t) - - sce <- SingleCellExperiment::SingleCellExperiment( - assays = assays_list, - rowData = dummy$var, - colData = dummy$obs - ) - colnames(sce) <- dummy$obs_names - rownames(sce) <- dummy$var_names - - return(sce) -} - -#' Dummy Seurat -#' -#' Generate a dummy dataset as a Seurat object -#' -#' @param ... Parameters passed to `dummy_list` -#' -#' @returns Seurat containing the generated data -dummy_Seurat <- function(...) { #nolint - if (!requireNamespace("SeuratObject", quietly = TRUE)) { - stop( - "Creating a Seurat requires the 'SeuratObject' package to be installed" - ) - } - - dummy <- dummy_data(...) - - X <- t(dummy$X) - colnames(X) <- dummy$obs_names - rownames(X) <- dummy$var_names - - seurat <- SeuratObject::CreateSeuratObject(X) - - X2 <- t(dummy$layers$X2) - colnames(X2) <- dummy$obs_names - rownames(X2) <- dummy$var_names - seurat <- SeuratObject::SetAssayData(seurat, "data", X2) - - X3 <- as.matrix(t(dummy$layers$X3)) - colnames(X3) <- dummy$obs_names - rownames(X3) <- dummy$var_names - seurat <- SeuratObject::SetAssayData(seurat, "scale.data", X3) - - seurat <- SeuratObject::AddMetaData(seurat, dummy$obs) - - return(seurat) -} - - - -#' Dummy anndata -#' -#' Generate a dummy dataset as a \link[anndataR]{InMemoryAnnData} or -#' \link[anndataR]{HDF5AnnData} object -#' -#' @param output_class Name of the AnnData class. -#' Must be one of: -#' \itemize{ -#' \item{"InMemoryAnnData": }{Produces \link[anndataR]{InMemoryAnnData} -#' (default).} -#' \item{"HDF5AnnData": }{Produces \link[anndataR]{HDF5AnnData}.} -#' } -#' -#' @param ... Parameters passed to `dummy_list` -#' -#' @returns \link[anndataR]{InMemoryAnnData} or -#' \link[anndataR]{HDF5AnnData} containing the generated data. -dummy_anndata <- function(output_class=c("InMemoryAnnData", - "HDF5AnnData"), - file=tempfile(), - ...) { #nolint - - dummy <- dummy_data(...) - output_class <- output_class[1] - generator <- get_generator(output_class) - if(output_class=="HDF5AnnData"){ - ad <- generator$new( - X = dummy$X, - obs = dummy$obs, - obs_names = dummy$obs_names, - var = dummy$var, - var_names = dummy$var_names, - file=file - ) - } else { - ad <- generator$new( - X = dummy$X, - obs = dummy$obs, - obs_names = dummy$obs_names, - var = dummy$var, - var_names = dummy$var_names - ) - } - return(ad) -} - diff --git a/tests/testthat/test-HDF5-read.R b/tests/testthat/test-HDF5-read.R index 5ab7b472..1b3a2f39 100644 --- a/tests/testthat/test-HDF5-read.R +++ b/tests/testthat/test-HDF5-read.R @@ -125,6 +125,11 @@ test_that("reading H5AD as Seurat works", { skip_if_not_installed("SeuratObject") # TODO: remove this suppression when the to_seurat, from_seurat functions are updated. - seurat <- suppressWarnings(read_h5ad(h5ad_file, to = "Seurat")) + suppressWarnings( + { + seurat <- read_h5ad(path = h5ad_file, + to = "Seurat") + } + ) expect_s4_class(seurat, "Seurat") }) diff --git a/tests/testthat/test-HDF5-reticulate-roundtrip.R b/tests/testthat/test-HDF5-reticulate-roundtrip.R index 7551aa26..5f408f8b 100644 --- a/tests/testthat/test-HDF5-reticulate-roundtrip.R +++ b/tests/testthat/test-HDF5-reticulate-roundtrip.R @@ -2,7 +2,7 @@ skip_if_no_anndata() skip_if_not_installed("rhdf5") # construct dummy objects -dummy <- dummy_data(10L, 20L) +dummy <- dummy_data() test_that("test Python -> R", { # create anndata in python diff --git a/tests/testthat/test-InMemoryAnnData.R b/tests/testthat/test-InMemoryAnnData.R index aad80304..3541585b 100644 --- a/tests/testthat/test-InMemoryAnnData.R +++ b/tests/testthat/test-InMemoryAnnData.R @@ -1,4 +1,4 @@ -dummy <- dummy_data(10L, 20L) +dummy <- dummy_data() h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") adata <- read_h5ad(h5ad_file, to = "InMemoryAnnData") diff --git a/tests/testthat/test-Seurat.R b/tests/testthat/test-Seurat.R index 4722f686..809340e1 100644 --- a/tests/testthat/test-Seurat.R +++ b/tests/testthat/test-Seurat.R @@ -1,4 +1,4 @@ -dummy <- dummy_data(10L, 20L) +dummy <- dummy_data() test_that("to_Seurat with inmemoryanndata", { @@ -7,7 +7,9 @@ test_that("to_Seurat with inmemoryanndata", { obs = dummy$obs, var = dummy$var, obs_names = dummy$obs_names, - var_names = dummy$var_names + var_names = dummy$var_names, + obsm = dummy$obsm, + varm = dummy$varm ) # running to_seurat when ad0$X is null probably doesn't make any sense ad0 <- AnnData( @@ -37,7 +39,7 @@ test_that("to_Seurat with inmemoryanndata", { expect_true(obs_key %in% colnames(seu@meta.data)) expect_equal(seu@meta.data[[obs_key]], dummy$obs[[obs_key]]) } - seu0@meta.data + # seu0@meta.data # check whether all var keys are found in the seu assay metadata # trackstatus: class=Seurat, feature=test_get_var, status=done @@ -46,11 +48,15 @@ test_that("to_Seurat with inmemoryanndata", { expect_true(var_key %in% colnames(active_assay@meta.features)) expect_equal(active_assay@meta.features[[var_key]], dummy$var[[var_key]]) } + #### Test from_Seurat #### + obj <- dummy_data("Seurat") + adata <- from_Seurat(obj) + expect_equal(nrow(obj),adata$n_var()) + expect_equal(ncol(obj),adata$n_obs()) }) test_that("to_Seurat() fails gracefully", { expect_error(to_Seurat(), regexp = "obj.*is missing") expect_error(to_Seurat("foo"), regexp = "AbstractAnnData.*not TRUE") }) - -# TODO: test from_Seurat + diff --git a/tests/testthat/test-SingleCellExperiment.R b/tests/testthat/test-SingleCellExperiment.R index 32ac2f6c..2f5cb4e4 100644 --- a/tests/testthat/test-SingleCellExperiment.R +++ b/tests/testthat/test-SingleCellExperiment.R @@ -12,8 +12,8 @@ test_that("to_SingleCellExperiment() works", { ) # conversion works - expect_no_error(sce <- to_SingleCellExperiment(ad)) - expect_no_error(sce0 <- to_SingleCellExperiment(ad0)) + expect_no_error({sce <- to_SingleCellExperiment(ad)}) + expect_no_error({sce0 <- to_SingleCellExperiment(ad0)}) expect_true(validObject(sce)) expect_true(validObject(sce0)) diff --git a/tests/testthat/test-dummy_data.R b/tests/testthat/test-dummy_data.R index 9fcc311b..693adc86 100644 --- a/tests/testthat/test-dummy_data.R +++ b/tests/testthat/test-dummy_data.R @@ -2,8 +2,9 @@ test_that("generating dummy data works", { dummy <- dummy_data() expect_type(dummy, "list") expect_identical( - names(dummy), - c("X", "obs", "obs_names", "var", "var_names", "layers") + sort(names(dummy)), + sort(c("X", "obs", "obs_names", "var", "var_names", "layers", + "obsm","varm","obsp","varp")) ) expect_identical(dim(dummy$X), c(10L, 20L)) }) @@ -16,7 +17,9 @@ test_that("generating dummy SingleCellExperiment works", { suppressPackageStartupMessages(library(SeuratObject)) test_that("generating dummy Seurat works", { - dummy <- dummy_data(output = "Seurat") + dummy <- suppressWarnings( + dummy_data(output = "Seurat") + ) expect_s4_class(dummy, "Seurat") }) @@ -29,3 +32,4 @@ test_that("generating dummy HDF5AnnData works", { dummy <- dummy_data(output = "HDF5AnnData") expect_true(methods::is(dummy,"HDF5AnnData")) }) + diff --git a/tests/testthat/test-roundtrip.R b/tests/testthat/test-roundtrip.R index 48fba1d6..b5dfa699 100644 --- a/tests/testthat/test-roundtrip.R +++ b/tests/testthat/test-roundtrip.R @@ -1,70 +1,31 @@ library(testthat) h5ad_file <- tempfile(pattern = "hdf5_write_", fileext = ".h5ad") - base_file <- system.file("extdata", "example.h5ad", package = "anndataR") - -gen_adata <- function(type) { - library(Matrix) - N_OBS <- 10 - N_VAR <- 15 - obs_names <- paste0("obs_", 1:N_OBS) - var_names <- paste0("var_", 1:N_VAR) - adata <- AnnData( - X = rsparsematrix(N_OBS, N_VAR, 0.1), - obs_names = obs_names, - var_names = var_names, - layers = list( - dense = matrix(1:15, N_OBS, N_VAR), - sparse = rsparsematrix(N_OBS, N_VAR, 0.1) - ), - obsm = list( - dense = matrix(1:15, N_OBS, 5), - sparse = rsparsematrix(N_OBS, 5, 0.1) - ), - varm = list( - dense = matrix(1:15, N_VAR, 5), - sparse = rsparsematrix(N_VAR, 5, 0.1) - ), - obsp = list( - dense = matrix(1:15, N_OBS, N_OBS), - sparse = rsparsematrix(N_OBS, N_OBS, 0.1) - ), - varp = list( - dense = matrix(1:15, N_VAR, N_VAR), - sparse = rsparsematrix(N_VAR, N_VAR, 0.1) - ) - ) - if (type == "HDF5AnnData") { - tempfile(pattern = "hdf5_write_", fileext = ".h5ad") - write_h5ad(adata, h5ad_file) - read_h5ad(h5ad_file, to = type) - } else if (type == "InMemoryAnnData") { - adata - } else { - stop(paste0("Unknown type: ", type)) - } -} - check_round_trip <- function(expected, type) { h5ad_file <- tempfile(pattern = "hdf5_write_", fileext = ".h5ad") write_h5ad(expected, h5ad_file) actual <- read_h5ad(h5ad_file, to = type) - expect_equal(actual, expected) } check_round_trip_example <- function(type) { - check_round_trip(read_h5ad(base_file, to = type), type) + check_round_trip(expected = read_h5ad(base_file, to = type), + type = type) } for (typ in c("HDF5AnnData", "InMemoryAnnData")) { + # test1 test_that(paste("round trip w/ example data for", typ), { - check_round_trip_example(typ) + suppressWarnings( + check_round_trip_example(type = typ) + ) }) + # test2 test_that(paste("round trip w/ generated data for", typ), { - adata <- gen_adata(typ) - check_round_trip(adata, typ) + adata <- dummy_data(output = typ) + check_round_trip(expected = adata, type = typ) }) } + diff --git a/vignettes/design.Rmd b/vignettes/design.Rmd index 348fc0ba..0561cb01 100644 --- a/vignettes/design.Rmd +++ b/vignettes/design.Rmd @@ -1,6 +1,6 @@ --- title: "Design" -output: rmarkdown::html_vignette +output: BiocStyle::html_document vignette: > %\VignetteIndexEntry{Design} %\VignetteEngine{knitr::rmarkdown} @@ -147,3 +147,10 @@ for (class in unique(status_formatted$class)) { cat("\n\n") } ``` + +# Session Info + +```{r} +utils::sessionInfo() +``` + diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index 17bf23c6..7d242200 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -79,7 +79,8 @@ var <- adata$var ## `InMemoryAnnData` backend -The following example details how to construct an InMemoryAnnData and access its contents. +The following example details how to construct an `InMemoryAnnData` +and access its contents. ```{r inmemory_construct} adata <- AnnData( @@ -114,6 +115,6 @@ seurat <- to_Seurat(adata) # Session info ```{r} -sessionInfo() +utils::sessionInfo() ``` From c7e96f9f9686261c5bb84213608d1c4cee73d288 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:47:19 +0000 Subject: [PATCH 10/19] update news [skip ci] --- NEWS.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index e8a48444..ee05bacc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,15 +2,17 @@ ## New features -* Add `example_data` function. * Harmonise all references to `h5ad_file` (`file`,`h5ad_path`,`ad`) * Harmonise all references to `ad` (`adata`) * Use `@returns` in Roxygen notes to allow multi-line notes. * Export helper function `dummy_data` - Makes examples much less verbose and more consistent. - Include "obsm","varm","obsp","varp" and "layers" wherever possible. -* *test-Seurat.R* +* *Seurat.R* + - Add/extract `DimRed` elements whenever available. - Add unit tests for `from_Seurat`. +* *DimReduc.R* + - New functions for converting Seurat's DimReduc objects. ## Bug fixes @@ -28,6 +30,7 @@ - `Consider adding runnable examples to man pages that document exported objects.` - Added helper function `messager` to avoid notes about pasting inside messages. Can also easily turn off verbosity by setting the env var "ANNDATAR_VERBOSE". +* Fix all unit tests given updates. # anndataR 0.1.0 From 24bf76a37dcf93787adac75e70478408c174380c Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:39:43 +0000 Subject: [PATCH 11/19] Add graphs_to_anndata and anndata_to_graphs funcs --- NEWS.md | 8 +++++ R/DimReduc.R | 2 +- R/Seurat.R | 64 ++++++++++++++++++++++++++++++++++++---- R/dummy_data.R | 3 +- man/anndata_to_graphs.Rd | 20 +++++++++++++ man/graphs_to_anndata.Rd | 20 +++++++++++++ 6 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 man/anndata_to_graphs.Rd create mode 100644 man/graphs_to_anndata.Rd diff --git a/NEWS.md b/NEWS.md index ee05bacc..4e341cf4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,8 +11,16 @@ * *Seurat.R* - Add/extract `DimRed` elements whenever available. - Add unit tests for `from_Seurat`. +* New functions + - `graphs_to_anndata`: When converting Seurat --> AnnData, + store `obsp` and `varp` anndata slots from `graphs`. Does some inference + about whether each matrix should go into the `obsp` vs. `varp` slot based + on whether they have the same dimensions as the number of rows. + - `anndata_to_graphs`: When converting AnnData --> Seurat, + store `obsp` and `varp` anndata slots in `graphs`. * *DimReduc.R* - New functions for converting Seurat's DimReduc objects. + ## Bug fixes diff --git a/R/DimReduc.R b/R/DimReduc.R index 9572bb51..5da50dcb 100644 --- a/R/DimReduc.R +++ b/R/DimReduc.R @@ -146,7 +146,7 @@ from_DimReduc <- function(obj, if(methods::is(obj,"DimReduc")){ stop("Must provide DimReduc objects as a named list.") } else if(SeuratObject::IsNamedList(obj)){ - if(!all(mapply(methods::is,"DimReduc"),obj)){ + if(!all( unlist(lapply(obj,methods::is,"DimReduc")) ) ){ stop("All elements of named list must be of class 'DimReduc'.") } drl <- obj diff --git a/R/Seurat.R b/R/Seurat.R index 124c70c7..30eb84a8 100644 --- a/R/Seurat.R +++ b/R/Seurat.R @@ -74,7 +74,7 @@ to_Seurat <- function(obj, x_assay <- SeuratObject::AddMetaData(x_assay, metadata = var_) } - obj2 <- SeuratObject::CreateSeuratObject(x_assay, + seur <- SeuratObject::CreateSeuratObject(x_assay, meta.data = obs_) ## add layers @@ -83,14 +83,17 @@ to_Seurat <- function(obj, for (key in obj$layers_keys()) { layer_ <- Matrix::t(obj$layers[[key]]) dimnames(layer_) <- list(var_names_, obs_names_) - obj2[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) + seur[[key]] <- SeuratObject::CreateAssayObject(counts = layer_) } ## Add DimReduc objects drl <- to_DimReduc(obj = obj) - obj2 <- add_DimReduc(obj = obj2, + seur <- add_DimReduc(obj = seur, drl = drl) + ## Add obsp/varp as graphs + seur <- anndata_to_graphs(ad = obj, + seur = seur) ## Return - return(obj2) + return(seur) } .toseurat_check_obsvar_names <- function(names, label) { @@ -256,7 +259,58 @@ from_Seurat <- function(obj, ## Add obsm and varm obsm_varm <- from_DimReduc(obj) ad$obsm <- obsm_varm$obsm - ad$varm <- obsm_varm$varm + ad$varm <- obsm_varm$varm + ## Add obsp/varp from graphs + ad <- graphs_to_anndata(seur = obj, + ad = ad) ## Return return(ad) } + + +#' Graphs to AnnData +#' +#' Transfer Seurat graphs to relevant AnnData slots. +#' @param ad An AnnData object. +#' @param seur A Seurat object. +#' @returns An AnnData object. +#' @keywords internal +graphs_to_anndata <- function(seur, + ad){ + graph_nms <- names(seur@graphs) + if("obsp" %in% graph_nms){ + ad$obsp <- seur@graphs$obsp + } + if("varp" %in% graph_nms){ + ad$varp <- seur@graphs$varp + } + extra_graphs <- graph_nms[!graph_nms %in% c("obsp","varp")] + for(gn in extra_graphs){ + g <- seur@graphs[[gn]] + if(all(dim(g)==nrow(seur))){ + messager("Inferring graph",shQuote(gn),"as a obs x obs matrix.", + "Adding to 'obsp' slot.") + ad$obsp[[gn]] <- g + } else if (all(dim(g)==ncol(seur))){ + messager("Inferring graph",shQuote(gn),"as a var x var matrix.", + "Adding to 'varp' slot.") + ad$varp[[gn]] <- g + } else { + messager("Unable to infer where graph",shQuote(gn),"should be placed.") + } + } + return(ad) +} + +#' Graphs to AnnData +#' +#' Transfer Seurat graphs to relevant AnnData slots. +#' @returns A \link[SeuratObject]{SeuratObject}. +#' @inheritParams graphs_to_anndata +#' @keywords internal +anndata_to_graphs <- function(ad, + seur){ + seur@graphs <- list(ad$obsp, + ad$varp) + return(seur) +} \ No newline at end of file diff --git a/R/dummy_data.R b/R/dummy_data.R index f6753ba2..78ed1ec9 100644 --- a/R/dummy_data.R +++ b/R/dummy_data.R @@ -228,7 +228,8 @@ dummy_Seurat <- function(..., seurat <- add_DimReduc(obj = seurat, drl = drl) ## Add obsp/varp as graphs - seurat@graphs <- dummy[c("obsp","varp")] + seurat@graphs <- list(dummy$obsp, + dummy$varp) return(seurat) } diff --git a/man/anndata_to_graphs.Rd b/man/anndata_to_graphs.Rd new file mode 100644 index 00000000..94ae91db --- /dev/null +++ b/man/anndata_to_graphs.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Seurat.R +\name{anndata_to_graphs} +\alias{anndata_to_graphs} +\title{Graphs to AnnData} +\usage{ +anndata_to_graphs(ad, seur) +} +\arguments{ +\item{ad}{An AnnData object.} + +\item{seur}{A Seurat object.} +} +\value{ +A \link[SeuratObject]{SeuratObject}. +} +\description{ +Transfer Seurat graphs to relevant AnnData slots. +} +\keyword{internal} diff --git a/man/graphs_to_anndata.Rd b/man/graphs_to_anndata.Rd new file mode 100644 index 00000000..67bdf3ba --- /dev/null +++ b/man/graphs_to_anndata.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Seurat.R +\name{graphs_to_anndata} +\alias{graphs_to_anndata} +\title{Graphs to AnnData} +\usage{ +graphs_to_anndata(seur, ad) +} +\arguments{ +\item{seur}{A Seurat object.} + +\item{ad}{An AnnData object.} +} +\value{ +An AnnData object. +} +\description{ +Transfer Seurat graphs to relevant AnnData slots. +} +\keyword{internal} From 2fe5c1ffc101ac5f495096b171d75f5b587230c5 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:15:25 +0000 Subject: [PATCH 12/19] Add conda env yaml [skip ci] --- inst/conda/anndataR.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 inst/conda/anndataR.yml diff --git a/inst/conda/anndataR.yml b/inst/conda/anndataR.yml new file mode 100644 index 00000000..c74c52bb --- /dev/null +++ b/inst/conda/anndataR.yml @@ -0,0 +1,10 @@ +name: anndataR +channels: + - conda-forge + - nodefaults +dependencies: + - python + - anndata + - scanpy + - pip + \ No newline at end of file From ffb01c4cca66f230ccad2b409206e63362c81d92 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:18:00 +0000 Subject: [PATCH 13/19] Add conda env yaml [skip ci] --- .github/workflows/lint.yaml | 2 +- .github/workflows/rworkflows.yml | 92 ++++++++++++++++++++++++++++++++ DESCRIPTION | 8 +++ NAMESPACE | 4 ++ NEWS.md | 12 +++++ R/setup_conda.R | 29 ++++++++++ _pkgdown.yml | 4 -- man/setup_conda.Rd | 67 +++++++++++++++++++++++ 8 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/rworkflows.yml create mode 100644 R/setup_conda.R delete mode 100644 _pkgdown.yml create mode 100644 man/setup_conda.Rd diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4a67da1b..bece5011 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -17,7 +17,7 @@ jobs: env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml new file mode 100644 index 00000000..a29d4bf2 --- /dev/null +++ b/.github/workflows/rworkflows.yml @@ -0,0 +1,92 @@ +name: rworkflows +'on': + push: +<<<<<<< HEAD + branches: + - master + - main + - devel + - RELEASE_** + pull_request: + branches: + - master + - main + - devel + - RELEASE_** +jobs: + rworkflows: + permissions: write-all +======= + branches: + - main + - rworkflows + pull_request: + branches: + - main + - rworkflows +jobs: + rworkflows: + permissions: + contents: write +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c + runs-on: ${{ matrix.config.os }} + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + container: ${{ matrix.config.cont }} + strategy: + fail-fast: ${{ false }} + matrix: + config: + - os: ubuntu-latest + bioc: devel + r: auto +<<<<<<< HEAD + cont: ghcr.io/bioconductor/bioconductor_docker:devel + rspm: ~ +======= + cont: bioconductor/bioconductor_docker:devel + rspm: https://packagemanager.rstudio.com/cran/__linux__/latest/release + - os: ubuntu-latest + bioc: release + r: auto + cont: bioconductor/bioconductor_docker:latest + http-user-agent: 'release' +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c + - os: macOS-latest + bioc: release + r: auto + cont: ~ + rspm: ~ + - os: windows-latest + bioc: release + r: auto + cont: ~ + rspm: ~ + steps: + - uses: neurogenomics/rworkflows@master + with: + run_bioccheck: ${{ true }} + run_rcmdcheck: ${{ true }} +<<<<<<< HEAD + as_cran: ${{ true }} +======= + as_cran: ${{ false }} +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c + run_vignettes: ${{ true }} + has_testthat: ${{ true }} + run_covr: ${{ true }} + run_pkgdown: ${{ true }} + has_runit: ${{ false }} + has_latex: ${{ false }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +<<<<<<< HEAD + run_docker: ${{ true }} + miniforge_variant: 'Mambaforge' + environment_file: +======= + run_docker: ${{ false }} + docker_user: username + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + runner_os: ${{ runner.os }} + cache_version: cache-v1 + enable_act: ${{ false }} +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c diff --git a/DESCRIPTION b/DESCRIPTION index b02f89a1..fa5e6511 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,10 @@ Package: anndataR Title: AnnData interoperability in R +<<<<<<< HEAD Version: 0.99.1 +======= +Version: 0.99.0 +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c Authors@R: c( person( "Robrecht", "Cannoodt", @@ -86,5 +90,9 @@ Config/Needs/website: pkgdown, tibble, knitr, rprojroot, stringr, readr, Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE, r6 = TRUE) +<<<<<<< HEAD RoxygenNote: 7.2.3 biocViews: SingleCell, DataImport, DataRepresentation +======= +RoxygenNote: 7.2.3.9000 +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c diff --git a/NAMESPACE b/NAMESPACE index 14cf49ef..a8d473dc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,7 +7,11 @@ export(from_DimReduc) export(from_Seurat) export(from_SingleCellExperiment) export(read_h5ad) +<<<<<<< HEAD export(to_DimReduc) +======= +export(setup_conda) +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c export(to_HDF5AnnData) export(to_InMemoryAnnData) export(to_Seurat) diff --git a/NEWS.md b/NEWS.md index 4e341cf4..f1fdcf79 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # anndataR 0.99.1 ## New features @@ -39,6 +40,17 @@ - Added helper function `messager` to avoid notes about pasting inside messages. Can also easily turn off verbosity by setting the env var "ANNDATAR_VERBOSE". * Fix all unit tests given updates. +======= +# anndataR 0.99.0 + +## New features + +- Add `rworkflows` CI. +- Update *lint.yaml* to use `actions/checkout@v4` (which has less issues). +- New function `setup_conda` automatically installs miniconda + and sets up conda env: #97 +- Change version to Bioc-recommended devel version: 0.99.0 +>>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c # anndataR 0.1.0 diff --git a/R/setup_conda.R b/R/setup_conda.R new file mode 100644 index 00000000..a8d0867f --- /dev/null +++ b/R/setup_conda.R @@ -0,0 +1,29 @@ +#' Setup conda +#' +#' Install miniconda and setup a conda env to use python dependencies. +#' @inheritParams reticulate::install_miniconda +#' @inheritParams reticulate::py_install +#' @inheritDotParams reticulate::py_install +#' @returns Null +#' +#' @export +#' @examples +#' \dontrun{ +#' setup_conda() +#' } +setup_conda <- function(path = reticulate::miniconda_path(), + update = TRUE, + force = FALSE, + packages = c("anndata", "scanpy"), + ...){ + + requireNamespace("reticulate") + if(!reticulate::py_module_available(module = "anndata")){ + reticulate::install_miniconda(path = path, + update = update, + force = force) + reticulate::py_install(packages = packages, + pip = TRUE, + ...) + } +} diff --git a/_pkgdown.yml b/_pkgdown.yml deleted file mode 100644 index 455f90d8..00000000 --- a/_pkgdown.yml +++ /dev/null @@ -1,4 +0,0 @@ -url: https://scverse.org/anndataR/ -template: - bootstrap: 5 - diff --git a/man/setup_conda.Rd b/man/setup_conda.Rd new file mode 100644 index 00000000..210b5e8b --- /dev/null +++ b/man/setup_conda.Rd @@ -0,0 +1,67 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup_conda.R +\name{setup_conda} +\alias{setup_conda} +\title{Setup conda} +\usage{ +setup_conda( + path = reticulate::miniconda_path(), + update = TRUE, + force = FALSE, + packages = c("anndata", "scanpy"), + ... +) +} +\arguments{ +\item{path}{The location where Miniconda is (or should be) installed. Note +that the Miniconda installer does not support paths containing spaces. See +\link[reticulate]{miniconda_path} for more details on the default path used by \code{reticulate}.} + +\item{update}{Boolean; update to the latest version of Miniconda after +installation?} + +\item{force}{Boolean; force re-installation if Miniconda is already installed +at the requested path?} + +\item{packages}{A vector of Python packages to install.} + +\item{...}{ + Arguments passed on to \code{\link[reticulate:py_install]{reticulate::py_install}} + \describe{ + \item{\code{envname}}{The name, or full path, of the environment in which Python +packages are to be installed. When \code{NULL} (the default), the active +environment as set by the \code{RETICULATE_PYTHON_ENV} variable will be used; +if that is unset, then the \code{r-reticulate} environment will be used.} + \item{\code{method}}{Installation method. By default, "auto" automatically finds a +method that will work in the local environment. Change the default to force +a specific installation method. Note that the "virtualenv" method is not +available on Windows.} + \item{\code{conda}}{The path to a \code{conda} executable. Use \code{"auto"} to allow +\code{reticulate} to automatically find an appropriate \code{conda} binary. +See \strong{Finding Conda} and \code{\link[reticulate:conda_binary]{conda_binary()}} for more details.} + \item{\code{python_version}}{The requested Python version. Ignored when attempting +to install with a Python virtual environment.} + \item{\code{pip}}{Boolean; use \code{pip} for package installation? This is only relevant +when Conda environments are used, as otherwise packages will be installed +from the Conda repositories.} + \item{\code{pip_ignore_installed,ignore_installed}}{Boolean; whether pip should +ignore previously installed versions of the requested packages. Setting +this to \code{TRUE} causes pip to install the latest versions of all +dependencies into the requested environment. This ensure that no +dependencies are satisfied by a package that exists either in the site +library or was previously installed from a different--potentially +incompatible--distribution channel. (\code{ignore_installed} is an alias for +\code{pip_ignore_installed}, \code{pip_ignore_installed} takes precedence).} + }} +} +\value{ +Null +} +\description{ +Install miniconda and setup a conda env to use python dependencies. +} +\examples{ +\dontrun{ +setup_conda() +} +} From fdd313b992d9dd2bd4fab494f24bff0062885724 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:23:00 +0000 Subject: [PATCH 14/19] Fix merge --- .github/workflows/rworkflows.yml | 41 ++------------------------------ DESCRIPTION | 6 +---- NEWS.md | 3 +-- 3 files changed, 4 insertions(+), 46 deletions(-) diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index a29d4bf2..8ea34edc 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -1,7 +1,6 @@ name: rworkflows 'on': push: -<<<<<<< HEAD branches: - master - main @@ -16,19 +15,6 @@ name: rworkflows jobs: rworkflows: permissions: write-all -======= - branches: - - main - - rworkflows - pull_request: - branches: - - main - - rworkflows -jobs: - rworkflows: - permissions: - contents: write ->>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c runs-on: ${{ matrix.config.os }} name: ${{ matrix.config.os }} (${{ matrix.config.r }}) container: ${{ matrix.config.cont }} @@ -39,18 +25,8 @@ jobs: - os: ubuntu-latest bioc: devel r: auto -<<<<<<< HEAD cont: ghcr.io/bioconductor/bioconductor_docker:devel rspm: ~ -======= - cont: bioconductor/bioconductor_docker:devel - rspm: https://packagemanager.rstudio.com/cran/__linux__/latest/release - - os: ubuntu-latest - bioc: release - r: auto - cont: bioconductor/bioconductor_docker:latest - http-user-agent: 'release' ->>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c - os: macOS-latest bioc: release r: auto @@ -66,11 +42,7 @@ jobs: with: run_bioccheck: ${{ true }} run_rcmdcheck: ${{ true }} -<<<<<<< HEAD as_cran: ${{ true }} -======= - as_cran: ${{ false }} ->>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c run_vignettes: ${{ true }} has_testthat: ${{ true }} run_covr: ${{ true }} @@ -78,15 +50,6 @@ jobs: has_runit: ${{ false }} has_latex: ${{ false }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -<<<<<<< HEAD run_docker: ${{ true }} - miniforge_variant: 'Mambaforge' - environment_file: -======= - run_docker: ${{ false }} - docker_user: username - DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} - runner_os: ${{ runner.os }} - cache_version: cache-v1 - enable_act: ${{ false }} ->>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c + run_docker: ${{ true }} + miniforge_variant: 'Mambaforge' diff --git a/DESCRIPTION b/DESCRIPTION index fa5e6511..e81e8511 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,10 +1,6 @@ Package: anndataR Title: AnnData interoperability in R -<<<<<<< HEAD -Version: 0.99.1 -======= -Version: 0.99.0 ->>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c +Version: 0.99.1 Authors@R: c( person( "Robrecht", "Cannoodt", diff --git a/NEWS.md b/NEWS.md index f1fdcf79..da1098ed 100644 --- a/NEWS.md +++ b/NEWS.md @@ -40,7 +40,7 @@ - Added helper function `messager` to avoid notes about pasting inside messages. Can also easily turn off verbosity by setting the env var "ANNDATAR_VERBOSE". * Fix all unit tests given updates. -======= + # anndataR 0.99.0 ## New features @@ -50,7 +50,6 @@ - New function `setup_conda` automatically installs miniconda and sets up conda env: #97 - Change version to Bioc-recommended devel version: 0.99.0 ->>>>>>> 3a22fe5dbde8f24cb3156e150427d537c904a79c # anndataR 0.1.0 From 80276b10c43ed02ad77e6ef7ca9f32f8a2f86590 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:27:34 +0000 Subject: [PATCH 15/19] Add environment_file --- .github/workflows/R-CMD-check.yaml | 58 ------------------------------ .github/workflows/rworkflows.yml | 1 + NEWS.md | 1 - 3 files changed, 1 insertion(+), 59 deletions(-) delete mode 100644 .github/workflows/R-CMD-check.yaml diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml deleted file mode 100644 index 5675be9b..00000000 --- a/.github/workflows/R-CMD-check.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help -on: - push: - branches: [main] - pull_request: - -name: R-CMD-check - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - R-CMD-check: - runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - - strategy: - fail-fast: false - matrix: - config: - - {os: macos-latest, r: 'release'} - - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} - - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes - - steps: - - uses: actions/checkout@v3 - - - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - r-version: ${{ matrix.config.r }} - http-user-agent: ${{ matrix.config.http-user-agent }} - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::rcmdcheck - needs: check - - - name: Install Python dependencies for reticulate - run: | - reticulate::install_miniconda() - reticulate::py_install(c("anndata", "scanpy"), pip = TRUE) - shell: Rscript {0} - - - uses: r-lib/actions/check-r-package@v2 - with: - upload-snapshots: true diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index 8ea34edc..b98f10ce 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -53,3 +53,4 @@ jobs: run_docker: ${{ true }} run_docker: ${{ true }} miniforge_variant: 'Mambaforge' + environment_file: https://github.com/bschilder/anndataR/raw/rworkflows/inst/conda/anndataR.yml diff --git a/NEWS.md b/NEWS.md index a15b2849..1a5a637f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -21,7 +21,6 @@ * *DimReduc.R* - New functions for converting Seurat's DimReduc objects. - ## Bug fixes * *DESCRIPTION*: From cd037e2729e691e578b91af89bb7ece60344a597 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:28:41 +0000 Subject: [PATCH 16/19] Add environment_file --- .github/workflows/rworkflows.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index b98f10ce..6f26cda4 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -51,6 +51,5 @@ jobs: has_latex: ${{ false }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run_docker: ${{ true }} - run_docker: ${{ true }} miniforge_variant: 'Mambaforge' environment_file: https://github.com/bschilder/anndataR/raw/rworkflows/inst/conda/anndataR.yml From 45240029983c62f6a3d2e64a4d7674dd406ec945 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:31:08 +0000 Subject: [PATCH 17/19] Add environment_file --- .github/workflows/rworkflows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index 6f26cda4..61ea2053 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -6,12 +6,14 @@ name: rworkflows - main - devel - RELEASE_** + - rworkflows pull_request: branches: - master - main - devel - RELEASE_** + - rworkflows jobs: rworkflows: permissions: write-all From 03dd3716b7b854e165cbeac8925f0c2a50bf6455 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:33:56 +0000 Subject: [PATCH 18/19] Add environment_file with relative path --- .github/workflows/rworkflows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index 61ea2053..5a06361f 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -54,4 +54,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run_docker: ${{ true }} miniforge_variant: 'Mambaforge' - environment_file: https://github.com/bschilder/anndataR/raw/rworkflows/inst/conda/anndataR.yml + environment_file: inst/conda/anndataR.yml From 9cf3f1128669d37b4da9c5edfe18839746ee50c0 Mon Sep 17 00:00:00 2001 From: "Brian M. Schilder" <34280215+bschilder@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:47:21 +0000 Subject: [PATCH 19/19] install miniconda via reticulate --- .github/workflows/R-CMD-check.yaml | 58 ++++++++++++++++++++++++++++++ .github/workflows/rworkflows.yml | 7 ++-- 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/R-CMD-check.yaml diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 00000000..5675be9b --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,58 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main] + pull_request: + +name: R-CMD-check + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - name: Install Python dependencies for reticulate + run: | + reticulate::install_miniconda() + reticulate::py_install(c("anndata", "scanpy"), pip = TRUE) + shell: Rscript {0} + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true diff --git a/.github/workflows/rworkflows.yml b/.github/workflows/rworkflows.yml index 5a06361f..9766db61 100644 --- a/.github/workflows/rworkflows.yml +++ b/.github/workflows/rworkflows.yml @@ -40,6 +40,11 @@ jobs: cont: ~ rspm: ~ steps: + - name: Install Python dependencies for reticulate + run: | + reticulate::install_miniconda() + reticulate::py_install(c("anndata", "scanpy"), pip = TRUE) + shell: Rscript {0} - uses: neurogenomics/rworkflows@master with: run_bioccheck: ${{ true }} @@ -53,5 +58,3 @@ jobs: has_latex: ${{ false }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run_docker: ${{ true }} - miniforge_variant: 'Mambaforge' - environment_file: inst/conda/anndataR.yml