├── .Rbuildignore ├── .air.toml ├── .github ├── .gitignore ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── R-CMD-check.yaml │ ├── lint.yaml │ ├── pkgdown.yaml │ └── pr-commands.yaml ├── .gitignore ├── .lintr ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── AbstractAnnData.R ├── AnnData-usage.R ├── AnnData.R ├── HDF5AnnData.R ├── InMemoryAnnData.R ├── anndataR-package.R ├── anndata_constructors.R ├── as_AnnData.R ├── as_Seurat.R ├── as_SingleCellExperiment.R ├── check_requires.R ├── from_Seurat.R ├── from_SingleCellExperiment.R ├── generate_dataframe.R ├── generate_dataset.R ├── generate_matrix.R ├── generate_vector.R ├── known_issues.R ├── read_h5ad.R ├── read_h5ad_helpers.R ├── ui.R ├── utils.R ├── write_h5ad.R ├── write_h5ad_helpers.R └── write_hdf5_helpers.R ├── README.md ├── _pkgdown.yml ├── anndataR.Rproj ├── inst ├── extdata │ └── example.h5ad ├── known_issues.yaml └── scripts │ └── example_h5ad.py ├── man ├── AbstractAnnData.Rd ├── AnnData-usage.Rd ├── AnnData.Rd ├── HDF5AnnData.Rd ├── InMemoryAnnData.Rd ├── anndataR-package.Rd ├── as_AnnData.Rd ├── as_HDF5AnnData.Rd ├── as_InMemoryAnnData.Rd ├── as_Seurat.Rd ├── as_SingleCellExperiment.Rd ├── figures │ ├── lifecycle-deprecated.svg │ ├── lifecycle-experimental.svg │ ├── lifecycle-stable.svg │ ├── lifecycle-superseded.svg │ ├── logo.png │ └── logo.svg ├── from_Seurat.Rd ├── from_SingleCellExperiment.Rd ├── generate_dataset.Rd ├── read_h5ad.Rd ├── to_Seurat.Rd ├── to_SingleCellExperiment.Rd └── write_h5ad.Rd ├── tests ├── testthat.R └── testthat │ ├── helper-expect_equal_py.R │ ├── helper-py-R-equivalences.R │ ├── helper-skip_if_no_anndata.R │ ├── helper-skip_if_no_h5diff.R │ ├── test-HDF5-read.R │ ├── test-HDF5-write.R │ ├── test-HDF5AnnData.R │ ├── test-InMemoryAnnData.R │ ├── test-as_Seurat.R │ ├── test-as_SingleCellExperiment.R │ ├── test-from_Seurat.R │ ├── test-from_SingleCellExperiment.R │ ├── test-generate_dataset.R │ ├── test-h5ad-fileclosure.R │ ├── test-h5ad-read.R │ ├── test-has_row_names.R │ ├── test-roundtrip-X.R │ ├── test-roundtrip-empty.R │ ├── test-roundtrip-layers.R │ ├── test-roundtrip-obsmvarm.R │ ├── test-roundtrip-obspvarp.R │ ├── test-roundtrip-obsvar.R │ ├── test-roundtrip-uns-nested.R │ ├── test-roundtrip-uns.R │ └── test-utils.R └── vignettes ├── .gitignore ├── anndataR.Rmd ├── development_status.Rmd ├── diagrams ├── class_diagram.mmd ├── class_diagram.svg └── script.sh ├── known_issues.Rmd ├── seurat_mapping.Rmd ├── singlecellexperiment_mapping.Rmd ├── software_design.Rmd ├── usage_h5ad.Rmd ├── usage_seurat.Rmd └── usage_singlecellexperiment.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^LICENSE\.md$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^\.github$ 5 | ^README\.qmd$ 6 | ^\.metals$ 7 | ^\.vscode$ 8 | ^\.lintr$ 9 | ^\.air.toml$ 10 | ^_pkgdown\.yml$ 11 | ^docs$ 12 | ^pkgdown$ 13 | ^vignettes/diagrams/*\.mmd$ 14 | ^vignettes/diagrams/*\.svg$ 15 | ^vignettes/design\.Rmd$ -------------------------------------------------------------------------------- /.air.toml: -------------------------------------------------------------------------------- 1 | [format] 2 | line-width = 80 3 | indent-width = 2 4 | indent-style = "space" 5 | line-ending = "auto" 6 | persistent-line-breaks = true 7 | exclude = [] 8 | default-exclude = true 9 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: 4 | # patreon: # Replace with a single Patreon username 5 | open_collective: anndatar 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | # polar: # Replace with a single Polar username 13 | # buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: https://www.data-intuitive.com 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | name: R-CMD-check 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | R-CMD-check: 16 | runs-on: ${{ matrix.config.os }} 17 | 18 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | config: 24 | - {os: macos-latest, r: 'release'} 25 | - {os: windows-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 27 | - {os: ubuntu-latest, r: 'release'} 28 | - {os: ubuntu-latest, r: 'oldrel-1'} 29 | 30 | env: 31 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 32 | R_KEEP_PKG_SOURCE: yes 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - uses: r-lib/actions/setup-pandoc@v2 38 | 39 | - uses: r-lib/actions/setup-r@v2 40 | with: 41 | r-version: ${{ matrix.config.r }} 42 | http-user-agent: ${{ matrix.config.http-user-agent }} 43 | use-public-rspm: true 44 | 45 | - uses: r-lib/actions/setup-r-dependencies@v2 46 | with: 47 | extra-packages: any::rcmdcheck 48 | needs: check 49 | 50 | - name: Cache miniconda 51 | uses: actions/cache@v4 52 | if: runner.os == 'Linux' 53 | # no need to cache on macOS or Windows, since it takes longer to unpack 54 | # the cache than to install miniconda and dependencies 55 | with: 56 | path: ~/.local/share/r-miniconda 57 | key: ${{ runner.os }}-v${{ matrix.config.r }}-reticulate-miniconda 58 | 59 | - name: Install Python dependencies for reticulate 60 | run: | 61 | if (!reticulate:::miniconda_exists()) { 62 | reticulate::install_miniconda() 63 | } 64 | reticulate::py_install(c("anndata", "scanpy", "dummy-anndata"), pip = TRUE) 65 | shell: Rscript {0} 66 | 67 | - name: Install h5diff 68 | run: | 69 | if [ "$RUNNER_OS" == "Linux" ]; then 70 | sudo apt-get install hdf5-tools 71 | fi 72 | shell: bash 73 | 74 | - uses: r-lib/actions/check-r-package@v2 75 | with: 76 | upload-snapshots: true 77 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | name: lint 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | env: 18 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: r-lib/actions/setup-r@v2 23 | with: 24 | use-public-rspm: true 25 | 26 | - name: Install air 27 | run: curl -LsSf https://github.com/posit-dev/air/releases/latest/download/air-installer.sh | sh 28 | 29 | - uses: r-lib/actions/setup-r-dependencies@v2 30 | with: 31 | extra-packages: any::lintr, any::styler, local::. 32 | needs: lint 33 | 34 | - name: Lint 35 | run: lintr::lint_package() 36 | shell: Rscript {0} 37 | env: 38 | LINTR_ERROR_ON_LINT: true 39 | 40 | - name: Check code style 41 | run: air format --check . 42 | 43 | - name: Check vignette style 44 | run: styler::style_dir("vignettes", dry = "fail") 45 | shell: Rscript {0} 46 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown 12 | 13 | jobs: 14 | pkgdown: 15 | runs-on: ubuntu-latest 16 | # Only restrict concurrency for non-PR jobs 17 | concurrency: 18 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 19 | env: 20 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 21 | permissions: 22 | contents: write 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - uses: r-lib/actions/setup-pandoc@v2 27 | 28 | - uses: r-lib/actions/setup-r@v2 29 | with: 30 | use-public-rspm: true 31 | 32 | - uses: r-lib/actions/setup-r-dependencies@v2 33 | with: 34 | extra-packages: local::. 35 | needs: website 36 | 37 | - name: Build site 38 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 39 | shell: Rscript {0} 40 | 41 | - name: Deploy to GitHub pages 🚀 42 | if: github.event_name != 'pull_request' 43 | uses: JamesIves/github-pages-deploy-action@v4 44 | with: 45 | clean: false 46 | repository-name: data-intuitive/anndataR 47 | branch: gh-pages 48 | folder: docs 49 | token: ${{ secrets.GTHB_PAT }} 50 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: pr-commands 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GTHB_PAT }} 18 | permissions: 19 | contents: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | persist-credentials: false 24 | 25 | - uses: r-lib/actions/pr-fetch@v2 26 | with: 27 | repo-token: ${{ secrets.GTHB_PAT }} 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | use-public-rspm: true 32 | 33 | - uses: r-lib/actions/setup-r-dependencies@v2 34 | with: 35 | extra-packages: any::roxygen2 36 | needs: pr-document 37 | 38 | - name: Document 39 | run: roxygen2::roxygenise() 40 | shell: Rscript {0} 41 | 42 | - name: commit 43 | run: | 44 | git config --local user.name "$GITHUB_ACTOR" 45 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 46 | git add man/\* NAMESPACE DESCRIPTION 47 | git commit -m 'Document (GHA)' || echo "No changes to commit" 48 | 49 | - uses: r-lib/actions/pr-push@v2 50 | with: 51 | repo-token: ${{ secrets.GTHB_PAT }} 52 | 53 | style: 54 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR') && startsWith(github.event.comment.body, '/style') }} 55 | name: style 56 | runs-on: ubuntu-latest 57 | env: 58 | GITHUB_PAT: ${{ secrets.GTHB_PAT }} 59 | permissions: 60 | contents: write 61 | steps: 62 | - uses: actions/checkout@v4 63 | with: 64 | persist-credentials: false 65 | 66 | - uses: r-lib/actions/pr-fetch@v2 67 | with: 68 | repo-token: ${{ secrets.GTHB_PAT }} 69 | 70 | - uses: r-lib/actions/setup-r@v2 71 | 72 | - name: Install air 73 | run: curl -LsSf https://github.com/posit-dev/air/releases/latest/download/air-installer.sh | sh 74 | 75 | - name: Install styler 76 | uses: r-lib/actions/setup-r-dependencies@v2 77 | with: 78 | packages: styler, roxygen2 79 | 80 | - name: Style code 81 | run: air format . 82 | 83 | - name: Style vignettes 84 | run: styler::style_dir("vignettes/") 85 | shell: Rscript {0} 86 | 87 | - name: Commit changes 88 | run: | 89 | if FILES_TO_COMMIT=($(git diff-index --name-only ${{ github.sha }} \ 90 | | egrep --ignore-case '\.(R|[qR]md|Rmarkdown|Rnw|Rprofile)$')) 91 | then 92 | git config --local user.name "$GITHUB_ACTOR" 93 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 94 | git commit ${FILES_TO_COMMIT[*]} -m "Style code (GHA)" 95 | else 96 | echo "No changes to commit" 97 | fi 98 | 99 | - uses: r-lib/actions/pr-push@v2 100 | with: 101 | repo-token: ${{ secrets.GTHB_PAT }} 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | .RDataTmp 8 | 9 | # User-specific files 10 | .Ruserdata 11 | 12 | # Example code in package build process 13 | *-Ex.R 14 | 15 | # Output files from R CMD build 16 | /*.tar.gz 17 | 18 | # Output files from R CMD check 19 | /*.Rcheck/ 20 | 21 | # RStudio files 22 | .Rproj.user/ 23 | 24 | # VScode files 25 | .metals/ 26 | .vscode/ 27 | 28 | # produced vignettes 29 | vignettes/*.html 30 | vignettes/*.pdf 31 | inst/doc 32 | 33 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 34 | .httr-oauth 35 | 36 | # knitr and R markdown default cache directories 37 | *_cache/ 38 | /cache/ 39 | 40 | # Temporary files created by R markdown 41 | *.utf8.md 42 | *.knit.md 43 | 44 | # R Environment Variables 45 | .Renviron 46 | 47 | # pkgdown site 48 | docs/ 49 | 50 | # translation temp files 51 | po/*~ 52 | 53 | # RStudio Connect folder 54 | rsconnect/ 55 | -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | line_length_linter = line_length_linter(120L), 3 | object_name_linter = object_name_linter(styles = c("snake_case", "symbols", "CamelCase", "SNAKE_CASE")) 4 | ) 5 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: anndataR 2 | Title: AnnData interoperability in R 3 | Version: 0.99.0 4 | Authors@R: c( 5 | person("Robrecht", "Cannoodt", , "robrecht@data-intuitive.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-3641-729X", github = "rcannood")), 7 | person("Luke", "Zappia", , "luke@lazappi.id.au", role = "aut", 8 | comment = c(ORCID = "0000-0001-7744-8565", github = "lazappi")), 9 | person("Martin", "Morgan", , "mtmorgan.bioc@gmail.com", role = "aut", 10 | comment = c(ORCID = "0000-0002-5874-8148", github = "mtmorgan")), 11 | person("Louise", "Deconinck", , "louise.deconinck@gmail.com", role = "aut", 12 | comment = c(ORCID = "0000-0001-8100-6823", github = "LouiseDck")), 13 | person("Danila", "Bredikhin", , "danila.bredikhin@embl.de", role = "ctb", 14 | comment = c(ORCID = "0000-0001-8089-6983", github = "gtca")), 15 | person("Isaac", "Virshup", role = "ctb", 16 | comment = c(ORCID = "0000-0002-1710-8945", github = "ivirshup")), 17 | person("Brian", "Schilder", , "brian_schilder@alumni.brown.edu", role = "ctb", 18 | comment = c(ORCID = "0000-0001-5949-2191", github = "bschilder")), 19 | person("Chananchida", "Sang-aram", role = "ctb", 20 | comment = c(ORCID = "0000-0002-0922-0822", github = "csangara")), 21 | person("Data Intuitive", , , "info@data-intuitive.com", role = c("fnd", "cph")), 22 | person("Chan Zuckerberg Initiative", role = "fnd") 23 | ) 24 | Description: Bring the power and flexibility of AnnData to the R 25 | ecosystem, allowing you to effortlessly manipulate and analyze your 26 | single-cell data. This package lets you work with backed h5ad and zarr 27 | files, directly access various slots (e.g. X, obs, var), or convert 28 | the data into SingleCellExperiment and Seurat objects. 29 | License: MIT + file LICENSE 30 | URL: https://anndatar.data-intuitive.com/, 31 | https://github.com/scverse/anndataR 32 | BugReports: https://github.com/scverse/anndataR/issues 33 | Depends: 34 | R (>= 4.0.0) 35 | Imports: 36 | cli, 37 | lifecycle, 38 | Matrix, 39 | methods, 40 | purrr, 41 | R6 (>= 2.4.0), 42 | rlang, 43 | stats 44 | Suggests: 45 | anndata, 46 | BiocStyle, 47 | hdf5r (>= 1.3.11), 48 | knitr, 49 | processx, 50 | reticulate (>= 1.36.1), 51 | rmarkdown, 52 | S4Vectors, 53 | Seurat, 54 | SeuratObject, 55 | SingleCellExperiment, 56 | SummarizedExperiment, 57 | testthat (>= 3.0.0), 58 | vctrs, 59 | withr, 60 | yaml 61 | VignetteBuilder: 62 | knitr 63 | biocViews: SingleCell, DataImport, DataRepresentation 64 | Config/Needs/website: pkgdown, tibble, knitr, rprojroot, stringr, readr, 65 | purrr, dplyr, tidyr 66 | Config/testthat/edition: 3 67 | Encoding: UTF-8 68 | Roxygen: list(markdown = TRUE, r6 = TRUE) 69 | RoxygenNote: 7.3.2 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: Robrecht Cannoodt, Luke Zappia, Martin Morgan, Louise Deconinck 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 Robrecht Cannoodt, Luke Zappia, Martin Morgan, Louise Deconinck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(as_AnnData,Seurat) 4 | S3method(as_AnnData,SingleCellExperiment) 5 | export(AnnData) 6 | export(as_AnnData) 7 | export(from_Seurat) 8 | export(from_SingleCellExperiment) 9 | export(generate_dataset) 10 | export(read_h5ad) 11 | export(to_Seurat) 12 | export(to_SingleCellExperiment) 13 | export(write_h5ad) 14 | importFrom(Matrix,as.matrix) 15 | importFrom(Matrix,sparseMatrix) 16 | importFrom(Matrix,t) 17 | importFrom(R6,R6Class) 18 | importFrom(cli,cli_abort) 19 | importFrom(cli,cli_inform) 20 | importFrom(cli,cli_warn) 21 | importFrom(lifecycle,deprecated) 22 | importFrom(methods,as) 23 | importFrom(methods,new) 24 | importFrom(purrr,map_dfr) 25 | importFrom(purrr,map_lgl) 26 | importFrom(rlang,`%||%`) 27 | importFrom(rlang,caller_env) 28 | importFrom(stats,setNames) 29 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # anndataR 0.99.0 2 | 3 | ## New features 4 | 5 | * PR #158: Change package version from 0.0.0.9000 --> 0.99.0 to align with Bioc devel 6 | versioning standards. 7 | - Update DESCRIPTION file to reflect current release R version used by Bio: 8 | R (>= 3.2.0) --> R (>= 4.3.0). 9 | - Reformat NEWS file to follow some conventions. 10 | 11 | * Various PRs: Initial release of anndataR, providing support for working with 12 | AnnData objects in R. Feature list: 13 | - Slots: 14 | - X 15 | - layers 16 | - obs 17 | - obs_names 18 | - var 19 | - var_names 20 | - Backends: 21 | - HDF5AnnData 22 | - InMemoryAnnData 23 | - Converters: 24 | - SingleCellExperiment 25 | - Seurat 26 | 27 | -------------------------------------------------------------------------------- /R/AnnData-usage.R: -------------------------------------------------------------------------------- 1 | #' AnnData structure and usage 2 | #' 3 | #' @description 4 | #' The `AnnData` object stores a data matrix `X` together with annotations of 5 | #' observations `obs` (`obsm`, `obsp`) and variables `var` (`varm`, `varp`). 6 | #' Additional layers of data can be stored in `layers` and unstructured 7 | #' annotations in `uns`. 8 | #' 9 | #' ## Back ends 10 | #' 11 | #' There are different back ends for `AnnData` objects that inherit from the 12 | #' abstract [AbstractAnnData] class. For example, the [InMemoryAnnData] stores 13 | #' data in memory or the [HDF5AnnData] is backed by a H5AD file. 14 | #' 15 | #' ## Usage 16 | #' 17 | #' The items listed as **"Slots"** are elements of the `AnnData` object that 18 | #' contain data and can be accessed or set. **"Fields"** return information 19 | #' about the `AnnData` object but cannot be set directly. Both, as well as 20 | #' methods, can be accessed using the `$` operator 21 | #' 22 | #' For example: 23 | #' 24 | #' - `adata$X` returns the `X` matrix 25 | #' - `adata$X <- x` sets the `X` matrix 26 | #' - `adata$method()` calls a method 27 | #' 28 | #' @slot X The main data matrix. Either `NULL` or an observation x variable 29 | #' matrix (without dimnames) with dimensions consistent with `n_obs` and 30 | #' `n_vars`. 31 | #' @slot layers Additional data layers. Must be `NULL` or a named list of 32 | #' matrices having dimensions consistent with `n_obs` and `n_vars`. 33 | #' @slot obs Observation annotations. A `data.frame` with columns containing 34 | #' information about observations. The number of rows of `obs` defines the 35 | #' observation dimension of the `AnnData` object (`n_obs`). If `NULL`, an 36 | #' `n_obs` × 0 `data.frame` will automatically be generated. 37 | #' @slot var Variable annotations. A `data.frame` with columns containing 38 | #' information about variables. The number of rows of `var` defines the 39 | #' variable dimension of the `AnnData` object (`n_vars`). If `NULL`, an 40 | #' `n_vars` × 0 `data.frame` will automatically be generated. 41 | #' @slot obs_names Observation names. Either `NULL` or a vector of unique 42 | #' identifiers used to identify each row of `obs` and to act as an index into 43 | #' the observation dimension of the `AnnData` object. For compatibility with 44 | #' _R_ representations, `obs_names` should be a unique character vector. 45 | #' @slot var_names Variable names. Either `NULL` or a vector of unique 46 | #' identifiers used to identify each row of `var` and to act as an index into 47 | #' the variable dimension of the `AnnData` object. For compatibility with _R_ 48 | #' representations, `var_names` should be a unique character vector. 49 | #' @slot obsm Multi-dimensional observation annotation. Must be `NULL` or a 50 | #' named list of array-like elements with number of rows equal to `n_obs`. 51 | #' @slot varm Multi-dimensional variable annotations. Must be `NULL` or a named 52 | #' list of array-like elements with number of rows equal to `n_vars`. 53 | #' @slot obsp Observation pairs. Must be `NULL` or a named list of array-like 54 | #' elements with number of rows and columns equal to `n_obs`. 55 | #' @slot varp Variable pairs. Must be `NULL` or a named list of array-like 56 | #' elements with number of rows and columns equal to `n_vars`. 57 | #' @slot uns Unstructured annotations. Must be `NULL` or a named list. 58 | #' 59 | #' @field shape Dimensions (observations x variables) of the `AnnData` object 60 | #' @field n_obs Number of observations 61 | #' @field n_vars Number of variables 62 | #' @field obs_keys Keys (column names) of `obs` 63 | #' @field var_keys Keys (column names) of `var` 64 | #' @field layers_keys Keys (element names) of `layers` 65 | #' @field obsm_keys Keys (element names) of `obsm` 66 | #' @field varm_keys Keys (element names) of `varm` 67 | #' @field obsp_keys Keys (element names) of `obsp` 68 | #' @field varp_keys Keys (element names) of `varp` 69 | #' @field uns_keys Keys (element names) of `uns` 70 | #' 71 | #' @section Methods: 72 | #' 73 | #' ## Conversion methods: 74 | #' 75 | #' \describe{ 76 | #' \item{ 77 | #' `as_SingleCellExperiment()` 78 | #' }{ 79 | #' Convert to [`SingleCellExperiment::SingleCellExperiment`], see 80 | #' [as_SingleCellExperiment()] 81 | #' } 82 | #' \item{`as_Seurat()`}{Convert to [`SeuratObject::Seurat`], see [as_Seurat()]} 83 | #' \item{`as_InMemoryAnnData()`}{Convert to [`InMemoryAnnData`], as [as_InMemoryAnnData()]} 84 | #' \item{`as_HDF5AnnData()`}{Convert to [`HDF5AnnData`], see [as_HDF5AnnData()]} 85 | #' } 86 | #' 87 | #' ## Output methods: 88 | #' 89 | #' \describe{ 90 | #' \item{ 91 | #' `write_h5ad()` 92 | #' }{ 93 | #' Write the `AnnData` object to an HDF5 file, see [write_h5ad()] 94 | #' } 95 | #' } 96 | #' 97 | #' ## General methods: 98 | #' 99 | #' \describe{ 100 | #' \item{`print()`}{Print a summary of the `AnnData` object} 101 | #' } 102 | #' 103 | #' @section Functions that can be used to create AnnData objects: 104 | #' 105 | #' \describe{ 106 | #' \item{[AnnData()]}{Create an [InMemoryAnnData] object} 107 | #' \item{[read_h5ad()]}{Read an `AnnData` from a H5AD file} 108 | #' \item{[as_AnnData()]}{Convert other objects to an `AnnData` object} 109 | #' } 110 | #' 111 | #' @seealso The documentation for the Python `anndata` package 112 | #' 113 | #' @seealso [AbstractAnnData] for the abstract class that all `AnnData` objects 114 | #' inherit from 115 | #' @seealso [InMemoryAnnData] for the in-memory implementation of `AnnData` 116 | #' @seealso [HDF5AnnData] for the HDF5-backed implementation of `AnnData` 117 | #' 118 | #' @name AnnData-usage 119 | NULL 120 | -------------------------------------------------------------------------------- /R/AnnData.R: -------------------------------------------------------------------------------- 1 | #' Create an in-memory AnnData object. 2 | #' 3 | #' For more information on the functionality of an AnnData object, see [AnnData-usage] 4 | #' 5 | #' @param X See the `X` slot in [AnnData-usage] 6 | #' @param layers See the `layers` slot in [AnnData-usage] 7 | #' @param obs See the `obs` slot in [AnnData-usage] 8 | #' @param var See the `var` slot in [AnnData-usage] 9 | #' @param obsm See the `obsm` slot in [AnnData-usage] 10 | #' @param varm See the `varm` slot in [AnnData-usage] 11 | #' @param obsp See the `obsp` slot in [AnnData-usage] 12 | #' @param varp See the `varp` slot in [AnnData-usage] 13 | #' @param uns See the `uns` slot in [AnnData-usage] 14 | #' @param shape Shape tuple (e.g. `c(n_obs, n_vars)`). Can be provided if both 15 | #' `X` or `obs` and `var` are not provided. 16 | #' 17 | #' @return An [InMemoryAnnData] object 18 | #' @export 19 | #' 20 | #' @seealso [AnnData-usage] for details of `AnnData` structure and usage 21 | #' @family AnnData creators 22 | #' 23 | #' @examples 24 | #' adata <- AnnData( 25 | #' X = matrix(1:12, nrow = 3, ncol = 4), 26 | #' obs = data.frame( 27 | #' row.names = paste0("obs", 1:3), 28 | #' n_counts = c(1, 2, 3), 29 | #' n_cells = c(1, 2, 3) 30 | #' ), 31 | #' var = data.frame( 32 | #' row.names = paste0("var", 1:4), 33 | #' n_cells = c(1, 2, 3, 4) 34 | #' ) 35 | #' ) 36 | #' 37 | #' adata 38 | AnnData <- function( 39 | X = NULL, 40 | obs = NULL, 41 | var = NULL, 42 | layers = NULL, 43 | obsm = NULL, 44 | varm = NULL, 45 | obsp = NULL, 46 | varp = NULL, 47 | uns = NULL, 48 | shape = NULL 49 | ) { 50 | InMemoryAnnData$new( 51 | X = X, 52 | obs = obs, 53 | var = var, 54 | layers = layers, 55 | obsm = obsm, 56 | varm = varm, 57 | obsp = obsp, 58 | varp = varp, 59 | uns = uns, 60 | shape = shape 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /R/anndataR-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | #' @importFrom cli cli_abort cli_warn cli_inform 6 | #' @importFrom lifecycle deprecated 7 | #' @importFrom methods as new 8 | #' @importFrom purrr map_lgl map_dfr 9 | #' @importFrom R6 R6Class 10 | #' @importFrom rlang `%||%` 11 | #' @importFrom stats setNames 12 | ## usethis namespace: end 13 | NULL 14 | -------------------------------------------------------------------------------- /R/anndata_constructors.R: -------------------------------------------------------------------------------- 1 | #' List the available AnnData generators. 2 | #' 3 | #' @noRd 4 | anndata_constructors <- function() { 5 | list( 6 | "HDF5AnnData" = HDF5AnnData, 7 | "InMemoryAnnData" = InMemoryAnnData 8 | ) 9 | } 10 | 11 | #' Retrieve the AnnData constructor for a given class. 12 | #' 13 | #' @param class Name of the AnnData class. Must be one of `"HDF5AnnData"` 14 | #' or `"InMemoryAnnData"`. 15 | #' 16 | #' @noRd 17 | get_anndata_constructor <- function( 18 | class = c("HDF5AnnData", "InMemoryAnnData") 19 | ) { 20 | # TODO: also support directly passing the correct class? 21 | class <- match.arg(class) 22 | anndata_constructors()[[class]] 23 | } 24 | -------------------------------------------------------------------------------- /R/check_requires.R: -------------------------------------------------------------------------------- 1 | #' Check required packages 2 | #' 3 | #' Check that required packages are available and give a nice error message with 4 | #' install instructions if not 5 | #' 6 | #' @param what A message stating what the packages are required for. Used at the 7 | #' start of the error message e.g. "{what} requires...". 8 | #' @param requires Character vector of required package names 9 | #' @param where Where to install the packages from. Either "CRAN" or "Bioc" 10 | #' 11 | #' @return `'TRUE` invisibly if all packages are available, otherwise calls 12 | #' [cli::cli_abort()] 13 | #' 14 | #' @importFrom rlang caller_env 15 | #' @noRd 16 | check_requires <- function(what, requires, where = c("CRAN", "Bioc")) { 17 | where <- match.arg(where) 18 | 19 | is_available <- map_lgl(requires, requireNamespace, quietly = TRUE) 20 | 21 | if (any(!is_available)) { 22 | missing <- requires[!is_available] 23 | 24 | # nolint start object_usage_linter 25 | missing_str <- paste0("\"", paste(missing, collapse = "\", \""), "\"") 26 | if (length(missing) > 1) { 27 | missing_str <- paste0("c(", missing_str, ")") 28 | } 29 | fun <- switch( 30 | where, 31 | CRAN = "install.packages", 32 | Bioc = "install.packages(\"BiocManager\"); BiocManager::install" 33 | ) 34 | # nolint end object_usage_linter 35 | 36 | cli_abort( 37 | c( 38 | "{what} requires the {.pkg {missing}} package{?s}", 39 | "i" = paste( 40 | "To continue, install {cli::qty(missing)}{?it/them} using", 41 | "{.code {fun}({missing_str})}" 42 | ) 43 | ), 44 | call = caller_env() 45 | ) 46 | } 47 | 48 | invisible(TRUE) 49 | } 50 | -------------------------------------------------------------------------------- /R/generate_dataframe.R: -------------------------------------------------------------------------------- 1 | #' Generate a dataframe 2 | #' 3 | #' Generate a dataframe with different types of columns 4 | #' 5 | #' @param num_rows Number of rows to generate 6 | #' @param types Types of columns to generate 7 | #' 8 | #' @return A dataframe with the generated columns 9 | #' 10 | #' @noRd 11 | #' 12 | #' @examples 13 | #' generate_dataframe(10L) 14 | generate_dataframe <- function(num_rows, types = names(vector_generators)) { 15 | data <- lapply(types, generate_vector, n = num_rows) 16 | names(data) <- types 17 | as.data.frame(data) 18 | } 19 | -------------------------------------------------------------------------------- /R/generate_matrix.R: -------------------------------------------------------------------------------- 1 | # nolint start 2 | generate_numeric_matrix <- function(n_obs, n_vars, NAs = FALSE) { 3 | # byrow = TRUE to mimic the way a matrix gets filled in Python 4 | m <- matrix( 5 | seq(0.5, n_obs * n_vars), 6 | nrow = n_obs, 7 | ncol = n_vars, 8 | byrow = TRUE 9 | ) 10 | if (NAs) { 11 | m[1, 1] <- NA_real_ 12 | } 13 | m 14 | } 15 | 16 | generate_integer_matrix <- function(n_obs, n_vars, NAs = FALSE) { 17 | # byrow = TRUE to mimic the way a matrix gets filled in Python 18 | m <- matrix( 19 | seq(0L, n_obs * n_vars - 1), 20 | nrow = n_obs, 21 | ncol = n_vars, 22 | byrow = TRUE 23 | ) 24 | if (NAs) { 25 | m[1, 1] <- NA_integer_ 26 | } 27 | m 28 | } 29 | 30 | matrix_generators <- list( 31 | numeric_matrix = function(n_obs, n_vars) { 32 | generate_numeric_matrix(n_obs, n_vars) 33 | }, 34 | numeric_dense = function(n_obs, n_vars) { 35 | m <- generate_numeric_matrix(n_obs, n_vars) 36 | as(m, "denseMatrix") 37 | }, 38 | numeric_csparse = function(n_obs, n_vars) { 39 | m <- generate_numeric_matrix(n_obs, n_vars) 40 | as(m, "CsparseMatrix") 41 | }, 42 | numeric_rsparse = function(n_obs, n_vars) { 43 | m <- generate_numeric_matrix(n_obs, n_vars) 44 | as(m, "RsparseMatrix") 45 | }, 46 | numeric_matrix_with_nas = function(n_obs, n_vars) { 47 | generate_numeric_matrix(n_obs, n_vars, NAs = TRUE) 48 | }, 49 | numeric_dense_with_nas = function(n_obs, n_vars) { 50 | m <- generate_numeric_matrix(n_obs, n_vars, NAs = TRUE) 51 | as(m, "denseMatrix") 52 | }, 53 | numeric_csparse_with_nas = function(n_obs, n_vars) { 54 | m <- generate_numeric_matrix(n_obs, n_vars, NAs = TRUE) 55 | as(m, "CsparseMatrix") 56 | }, 57 | numeric_rsparse_with_nas = function(n_obs, n_vars) { 58 | m <- generate_numeric_matrix(n_obs, n_vars, NAs = TRUE) 59 | as(m, "RsparseMatrix") 60 | }, 61 | integer_matrix = function(n_obs, n_vars) { 62 | generate_integer_matrix(n_obs, n_vars) 63 | }, 64 | integer_dense = function(n_obs, n_vars) { 65 | m <- generate_integer_matrix(n_obs, n_vars) 66 | as(m, "denseMatrix") 67 | }, 68 | integer_csparse = function(n_obs, n_vars) { 69 | m <- generate_integer_matrix(n_obs, n_vars) 70 | as(m, "CsparseMatrix") 71 | }, 72 | integer_rsparse = function(n_obs, n_vars) { 73 | m <- generate_integer_matrix(n_obs, n_vars) 74 | as(m, "RsparseMatrix") 75 | }, 76 | integer_matrix_with_nas = function(n_obs, n_vars) { 77 | m <- generate_integer_matrix(n_obs, n_vars, NAs = TRUE) 78 | m 79 | }, 80 | integer_dense_with_nas = function(n_obs, n_vars) { 81 | m <- generate_integer_matrix(n_obs, n_vars, NAs = TRUE) 82 | as(m, "denseMatrix") 83 | }, 84 | integer_csparse_with_nas = function(n_obs, n_vars) { 85 | m <- generate_integer_matrix(n_obs, n_vars, NAs = TRUE) 86 | as(m, "CsparseMatrix") 87 | }, 88 | integer_rsparse_with_nas = function(n_obs, n_vars) { 89 | m <- generate_integer_matrix(n_obs, n_vars, NAs = TRUE) 90 | as(m, "RsparseMatrix") 91 | } 92 | ) 93 | # nolint end 94 | 95 | #' Generate a matrix 96 | #' 97 | #' Generate a matrix of a given type 98 | #' 99 | #' @param n_obs Number of observations to generate 100 | #' @param n_vars Number of variables to generate 101 | #' 102 | #' @return A matrix of the given type 103 | #' 104 | #' @noRd 105 | #' 106 | #' @examples 107 | #' generate_matrix(10L, 20L) 108 | generate_matrix <- function(n_obs, n_vars, type = names(matrix_generators)) { 109 | type <- match.arg(type) 110 | matrix_generators[[type]](n_obs, n_vars) 111 | } 112 | -------------------------------------------------------------------------------- /R/generate_vector.R: -------------------------------------------------------------------------------- 1 | vector_generators <- list( 2 | character = function(n) paste0("value_", seq(from = 0, to = n - 1)), 3 | integer = function(n) seq(from = 0, to = n - 1), 4 | factor = function(n) factor(rep(c("Value1", "Value2"), length.out = n)), 5 | factor_ordered = function(n) { 6 | factor(rep(c("Value1", "Value2"), length.out = n), ordered = TRUE) 7 | }, 8 | logical = function(n) sample(c(TRUE, FALSE), n, replace = TRUE), 9 | numeric = function(n) seq(from = 0.5, to = n), 10 | character_with_nas = function(n) { 11 | x <- paste0("value", seq_len(n)) 12 | x[seq(1, n, by = 2)] <- NA_character_ 13 | x 14 | }, 15 | integer_with_nas = function(n) { 16 | x <- seq(from = 0, to = n - 1) 17 | x[1] <- NA_integer_ 18 | x 19 | }, 20 | factor_with_nas = function(n) { 21 | x <- factor(rep(c("Value1", "Value2"), length.out = n)) 22 | x[1] <- NA_character_ 23 | x 24 | }, 25 | factor_ordered_with_nas = function(n) { 26 | x <- factor(rep(c("Value1", "Value2"), length.out = n), ordered = TRUE) 27 | x[1] <- NA_character_ 28 | x 29 | }, 30 | logical_with_nas = function(n) { 31 | x <- sample(c(TRUE, FALSE), n, replace = TRUE) 32 | x[seq(1, n, by = 2)] <- NA 33 | x 34 | }, 35 | numeric_with_nas = function(n) { 36 | x <- runif(n) 37 | x[seq(1, n, by = 2)] <- NA_real_ 38 | x 39 | } 40 | ) 41 | 42 | #' Generate a vector 43 | #' 44 | #' Generate a vector of a given type 45 | #' 46 | #' @param n Number of elements to generate 47 | #' @param type Type of vector to generate 48 | #' 49 | #' @return A vector of the given type 50 | #' 51 | #' @noRd 52 | #' 53 | #' @examples 54 | #' generate_vector(10L) 55 | generate_vector <- function(n, type = names(vector_generators)) { 56 | type <- match.arg(type) 57 | vector_generators[[type]](n) 58 | } 59 | -------------------------------------------------------------------------------- /R/known_issues.R: -------------------------------------------------------------------------------- 1 | # This file contains the known issues that are currently present in the package. 2 | # It can be used to generate documentation, but also throw warnings instead of errors 3 | # in tests. 4 | read_known_issues <- function() { 5 | check_requires("Reading known issues", "yaml") 6 | 7 | data <- yaml::read_yaml(system.file( 8 | "known_issues.yaml", 9 | package = "anndataR" 10 | )) 11 | 12 | map_dfr( 13 | data$known_issues, 14 | function(row) { 15 | expected_names <- c( 16 | "backend", 17 | "slot", 18 | "dtype", 19 | "process", 20 | "error_message", 21 | "description", 22 | "proposed_solution", 23 | "to_investigate", 24 | "to_fix" 25 | ) 26 | if (!all(expected_names %in% names(row))) { 27 | cli_abort(c( 28 | "Unexpected columns in {.file known_issues.yaml}", 29 | "i" = "Expected columns: {.val {expected_names}}", 30 | "i" = "Actual columns: {.val {names(row)}}" 31 | )) 32 | } 33 | 34 | expand.grid(row) 35 | } 36 | ) 37 | } 38 | 39 | is_known <- function(backend, slot, dtype, process, known_issues = NULL) { 40 | if (is.null(known_issues)) { 41 | known_issues <- read_known_issues() 42 | } 43 | 44 | filt <- rep(TRUE, nrow(known_issues)) 45 | 46 | if (!is.null(backend)) { 47 | filt <- filt & known_issues$backend %in% backend 48 | } 49 | if (!is.null(slot)) { 50 | filt <- filt & known_issues$slot %in% slot 51 | } 52 | if (!is.null(dtype)) { 53 | filt <- filt & known_issues$dtype %in% dtype 54 | } 55 | if (!is.null(process)) { 56 | filt <- filt & known_issues$process %in% process 57 | } 58 | 59 | filt 60 | } 61 | 62 | message_if_known <- function( 63 | backend, 64 | slot, 65 | dtype, 66 | process, 67 | known_issues = NULL 68 | ) { 69 | if (is.null(known_issues)) { 70 | known_issues <- read_known_issues() 71 | } 72 | 73 | filt <- is_known(backend, slot, dtype, process, known_issues) 74 | 75 | if (any(filt)) { 76 | # take first 77 | row <- known_issues[which(filt)[[1]], ] 78 | 79 | paste0( 80 | "Known issue for backend '", 81 | row$backend, 82 | "', slot '", 83 | row$slot, 84 | "', dtype '", 85 | row$dtype, 86 | "', process '", 87 | row$process, 88 | "': ", 89 | row$description 90 | ) 91 | } else { 92 | NULL 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /R/read_h5ad.R: -------------------------------------------------------------------------------- 1 | #' Read H5AD 2 | #' 3 | #' Read data from a H5AD file 4 | #' 5 | #' @param path Path to the H5AD file to read 6 | #' @param as The type of object to return. One of: 7 | #' 8 | #' * `"InMemoryAnnData"`: Read the H5AD file into memory as an 9 | #' [`InMemoryAnnData`] object 10 | #' * `"HDF5AnnData"`: Read the H5AD file as an [`HDF5AnnData`] object 11 | #' * `"SingleCellExperiment"`: Read the H5AD file as a 12 | #' [`SingleCellExperiment::SingleCellExperiment`] object 13 | #' * `"Seurat"`: Read the H5AD file as a 14 | #' [`SeuratObject::Seurat`] object 15 | #' @param to `r lifecycle::badge('deprecated')` Deprecated, use `as` instead 16 | #' @param mode The mode to open the HDF5 file. 17 | #' 18 | #' * `a` creates a new file or opens an existing one for read/write. 19 | #' * `r` opens an existing file for reading. 20 | #' * `r+` opens an existing file for read/write. 21 | #' * `w` creates a file, truncating any existing ones. 22 | #' * `w-`/`x` are synonyms, creating a file and failing if it already exists. 23 | #' @param ... Extra arguments provided to the `as_*` conversion function for the 24 | #' object specified by `as` 25 | #' 26 | #' @return The object specified by `as` 27 | #' @export 28 | #' 29 | #' @family AnnData creators 30 | #' 31 | #' @examples 32 | #' h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") 33 | #' 34 | #' # Read the H5AD as a SingleCellExperiment object 35 | #' if (requireNamespace("SingleCellExperiment", quietly = TRUE)) { 36 | #' sce <- read_h5ad(h5ad_file, as = "SingleCellExperiment") 37 | #' } 38 | #' 39 | #' # Read the H5AD as a Seurat object 40 | #' if (requireNamespace("SeuratObject", quietly = TRUE)) { 41 | #' seurat <- read_h5ad(h5ad_file, as = "Seurat") 42 | #' } 43 | read_h5ad <- function( 44 | path, 45 | as = c("InMemoryAnnData", "HDF5AnnData", "SingleCellExperiment", "Seurat"), 46 | to = deprecated(), 47 | mode = c("r", "r+", "a", "w", "w-", "x"), 48 | ... 49 | ) { 50 | as <- match.arg(as) 51 | mode <- match.arg(mode) 52 | 53 | if (lifecycle::is_present(to)) { 54 | lifecycle::deprecate_warn( 55 | when = "0.99.0", 56 | what = "read_h5ad(to = )", 57 | with = "read_h5ad(as = )", 58 | details = "Overwriting `as` with `to`." 59 | ) 60 | as <- to 61 | } 62 | 63 | hdf5_adata <- HDF5AnnData$new(path, mode = mode) 64 | 65 | if (as == "HDF5AnnData") { 66 | return(hdf5_adata) 67 | } 68 | 69 | adata <- switch( 70 | as, 71 | "SingleCellExperiment" = hdf5_adata$as_SingleCellExperiment(...), 72 | "Seurat" = hdf5_adata$as_Seurat(...), 73 | "InMemoryAnnData" = hdf5_adata$as_InMemoryAnnData(...) 74 | ) 75 | hdf5_adata$close() 76 | adata 77 | } 78 | -------------------------------------------------------------------------------- /R/ui.R: -------------------------------------------------------------------------------- 1 | #' Style a vector 2 | #' 3 | #' Style a vector for use in **{cli}** calls 4 | #' 5 | #' @param x The vector to style 6 | #' @param last The separator before the last element in the vector 7 | #' @param trunc The maxium number of elements to show 8 | #' @param trunc_style The truncation style to use, either "both-ends" 9 | #' (1, 2, ..., n-1, 1) or "head" (1, 2, 3, 4, ...) 10 | #' @param wrap Whether to wrap the output to appear as a vector, if `TRUE` then 11 | #' "c(1, 2, 3)" else "1, 2, 3" 12 | #' 13 | #' @returns A styled string that can be included directly using "{syle_vec(...)}" 14 | #' @noRd 15 | style_vec <- function( 16 | x, 17 | last = ", ", 18 | trunc = 20L, 19 | trunc_style = c("both-ends", "head"), 20 | wrap = FALSE 21 | ) { 22 | trunc_style <- match.arg(trunc_style) 23 | 24 | style <- list( 25 | "vec-last" = last, 26 | "vec-trunc" = trunc, 27 | "vec-trunc-style" = trunc_style 28 | ) 29 | 30 | x <- cli::cli_vec(x, style) 31 | 32 | if (isTRUE(wrap)) { 33 | x_str <- "c({.val {x}})" 34 | } else { 35 | x_str <- "{.val {x}}" 36 | } 37 | 38 | cli::cli_fmt(cli::cli_text(x_str)) 39 | } 40 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | wrap_message <- function(...) { 2 | txt <- paste0(..., collapse = "") 3 | paste(strwrap(txt, exdent = 2L), collapse = "\n") 4 | } 5 | 6 | has_row_names <- function(x) { 7 | if (is.data.frame(x)) { 8 | .row_names_info(x) > 0 9 | } else { 10 | !is.null(dimnames(x)[[1]]) 11 | } 12 | } 13 | 14 | get_shape <- function(obs, var, X, shape) { 15 | n_obs <- 16 | if (!is.null(obs)) { 17 | nrow(obs) 18 | } else if (!is.null(X)) { 19 | nrow(X) 20 | } else if (!is.null(shape)) { 21 | shape[[1]] 22 | } else { 23 | 0L 24 | } 25 | n_vars <- 26 | if (!is.null(var)) { 27 | nrow(var) 28 | } else if (!is.null(X)) { 29 | ncol(X) 30 | } else if (!is.null(shape)) { 31 | shape[[2]] 32 | } else { 33 | 0L 34 | } 35 | c(n_obs, n_vars) 36 | } 37 | 38 | get_initial_obs <- function(obs, X, shape) { 39 | if (is.null(obs)) { 40 | obs <- data.frame(matrix(NA, nrow = shape[[1]], ncol = 0)) 41 | if (!is.null(X)) { 42 | rownames(obs) <- rownames(X) 43 | } 44 | } 45 | obs 46 | } 47 | 48 | get_initial_var <- function(var, X, shape) { 49 | if (is.null(var)) { 50 | var <- data.frame(matrix(NA, nrow = shape[[2]], ncol = 0)) 51 | if (!is.null(X)) { 52 | rownames(var) <- colnames(X) 53 | } 54 | } 55 | var 56 | } 57 | 58 | to_py_matrix <- function(mat) { 59 | if (inherits(mat, "dgCMatrix")) { 60 | mat <- as(mat, "RsparseMatrix") 61 | } else if (!inherits(mat, "dgRMatrix")) { 62 | mat <- as.matrix(mat) 63 | } 64 | Matrix::t(mat) 65 | } 66 | 67 | # nolint start: object_name_linter 68 | to_R_matrix <- function(mat) { 69 | # nolint end: object_name_linter 70 | if (inherits(mat, "dgRMatrix")) { 71 | mat <- as(mat, "CsparseMatrix") 72 | } else if (!inherits(mat, "dgCMatrix")) { 73 | mat <- as.matrix(mat) 74 | } 75 | Matrix::t(mat) 76 | } 77 | 78 | self_name <- function(x) { 79 | if (is.null(names(x))) { 80 | x <- setNames(x, x) 81 | } else if (any(names(x) == "")) { 82 | is_missing <- names(x) == "" 83 | names(x)[is_missing] <- x[is_missing] 84 | } 85 | 86 | x 87 | } 88 | 89 | #' Get mapping 90 | #' 91 | #' Get a mapping argument for a conversion function 92 | #' 93 | #' @param mapping The user-supplied mapping argument. Can be a named vector, 94 | #' `TRUE` or `FALSE`. 95 | #' @param guesser A function that guesses the default mapping from `obj` if 96 | #' `mapping` is `TRUE` 97 | #' @param obj The object that is being converted and is passed to `guesser` 98 | #' if needed 99 | #' @param name The name of the mapping argument, used for error messages 100 | #' @param ... Additional arguments passed to `guesser` 101 | #' 102 | #' @description 103 | #' If `mapping` is `NULL` or empty it is set to `FALSE` with a warning. `FALSE` 104 | #' values return an empty mapping. 105 | #' 106 | #' @returns A named mapping vector 107 | #' @noRd 108 | get_mapping <- function(mapping, guesser, obj, name, ...) { 109 | if (rlang::is_empty(mapping)) { 110 | cli_warn(c( 111 | "The {.arg {name}} argument is empty, setting it to {.val {FALSE}}" 112 | )) 113 | 114 | mapping <- FALSE 115 | } 116 | 117 | # If FALSE, return an empty mapping 118 | if (isFALSE(mapping)) { 119 | return(list()) 120 | } 121 | 122 | # If TRUE, use the guesser function to get the default mapping 123 | if (isTRUE(mapping)) { 124 | return(guesser(obj, ...)) 125 | } 126 | 127 | if (!is.vector(mapping)) { 128 | cli_abort(paste( 129 | "{.arg {name}} must be a vector, {.val {TRUE}} or {.val {FALSE}}, not", 130 | "{.cls {class(mapping)}}" 131 | )) 132 | } 133 | 134 | # Make sure provided mapping has names 135 | self_name(mapping) 136 | } 137 | -------------------------------------------------------------------------------- /R/write_h5ad.R: -------------------------------------------------------------------------------- 1 | #' Write H5AD 2 | #' 3 | #' Write an H5AD file 4 | #' 5 | #' @param object The object to write, either a 6 | #' [`SingleCellExperiment::SingleCellExperiment`] or a 7 | #' [`SeuratObject::Seurat`] object 8 | #' @param path Path of the file to write to 9 | #' @param compression The compression algorithm to use when writing the HDF5 10 | #' file. Can be one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`. 11 | #' @param mode The mode to open the HDF5 file. 12 | #' 13 | #' * `a` creates a new file or opens an existing one for read/write 14 | #' * `r+` opens an existing file for read/write 15 | #' * `w` creates a file, truncating any existing ones 16 | #' * `w-`/`x` are synonyms creating a file and failing if it already exists 17 | #' @param ... Additional arguments passed to [as_AnnData()] 18 | #' 19 | #' @return `path` invisibly 20 | #' @export 21 | #' 22 | #' @examples 23 | #' adata <- AnnData( 24 | #' X = matrix(1:5, 3L, 5L), 25 | #' layers = list( 26 | #' A = matrix(5:1, 3L, 5L), 27 | #' B = matrix(letters[1:5], 3L, 5L) 28 | #' ), 29 | #' obs = data.frame(row.names = LETTERS[1:3], cell = 1:3), 30 | #' var = data.frame(row.names = letters[1:5], gene = 1:5) 31 | #' ) 32 | #' h5ad_file <- tempfile(fileext = ".h5ad") 33 | #' adata$write_h5ad(h5ad_file) 34 | #' 35 | #' # Write a SingleCellExperiment as an H5AD 36 | #' if (requireNamespace("SingleCellExperiment", quietly = TRUE)) { 37 | #' ncells <- 100 38 | #' counts <- matrix(rpois(20000, 5), ncol = ncells) 39 | #' logcounts <- log2(counts + 1) 40 | #' 41 | #' pca <- matrix(runif(ncells * 5), ncells) 42 | #' tsne <- matrix(rnorm(ncells * 2), ncells) 43 | #' 44 | #' sce <- SingleCellExperiment::SingleCellExperiment( 45 | #' assays = list(counts = counts, logcounts = logcounts), 46 | #' reducedDims = list(PCA = pca, tSNE = tsne) 47 | #' ) 48 | #' 49 | #' adata <- as_AnnData(sce) 50 | #' h5ad_file <- tempfile(fileext = ".h5ad") 51 | #' adata$write_h5ad(h5ad_file) 52 | #' } 53 | #' 54 | #' # Write a Seurat as a H5AD 55 | #' if (requireNamespace("Seurat", quietly = TRUE)) { 56 | #' library(Seurat) 57 | #' 58 | #' counts <- matrix(1:15, 5L, 3L) 59 | #' dimnames(counts) <- list( 60 | #' LETTERS[1:5], 61 | #' letters[1:3] 62 | #' ) 63 | #' cell.metadata <- data.frame( 64 | #' row.names = letters[1:3], 65 | #' cell = 1:3 66 | #' ) 67 | #' obj <- CreateSeuratObject(counts, meta.data = cell.metadata) 68 | #' gene.metadata <- data.frame( 69 | #' row.names = LETTERS[1:5], 70 | #' gene = 1:5 71 | #' ) 72 | #' obj[["RNA"]] <- AddMetaData(GetAssay(obj), gene.metadata) 73 | #' 74 | #' adata <- as_AnnData(obj) 75 | #' h5ad_file <- tempfile(fileext = ".h5ad") 76 | #' adata$write_h5ad(h5ad_file) 77 | #' } 78 | write_h5ad <- function( 79 | object, 80 | path, 81 | compression = c("none", "gzip", "lzf"), 82 | mode = c("w-", "r", "r+", "a", "w", "x"), 83 | ... 84 | ) { 85 | mode <- match.arg(mode) 86 | adata <- if (inherits(object, "AbstractAnnData")) { 87 | object$as_HDF5AnnData( 88 | path, 89 | compression = compression, 90 | mode = mode 91 | ) 92 | } else { 93 | as_AnnData( 94 | object, 95 | output_class = "HDF5AnnData", 96 | file = path, 97 | compression = compression, 98 | mode = mode, 99 | ... 100 | ) 101 | } 102 | 103 | adata$close() 104 | rm(adata) 105 | gc() 106 | 107 | invisible(path) 108 | } 109 | -------------------------------------------------------------------------------- /R/write_hdf5_helpers.R: -------------------------------------------------------------------------------- 1 | #' Create a dataset in a HDF5 file 2 | #' 3 | #' Write HDF5 dataset with chosen compression (can be none) 4 | #' 5 | #' @noRd 6 | #' 7 | #' @param file Path to a HDF5 file 8 | #' @param name Name of the element within the H5AD file containing the data 9 | #' frame 10 | #' @param value Value to write. Must be a vector to the same length as the data 11 | #' frame. 12 | #' @param compression The compression to use when writing the element. Can be 13 | #' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`. 14 | #' @param scalar Whether to write the value as a scalar or not 15 | #' @param dtype The data type of the attribute to write. If `NULL` then the data 16 | #' type is guessed using `hdf5r::guess_dtype()`. 17 | #' @param space The space of the attribute to write. If `NULL` then the space 18 | #' is guessed using `hdf5r::guess_space()`. 19 | #' 20 | #' @return Whether the `path` exists in `file` 21 | hdf5_create_dataset <- function( 22 | file, 23 | name, 24 | value, 25 | compression = c("none", "gzip", "lzf"), 26 | scalar = FALSE, 27 | dtype = NULL, 28 | space = NULL 29 | ) { 30 | compression <- match.arg(compression) 31 | 32 | if (!is.null(dim(value))) { 33 | dims <- dim(value) 34 | } else { 35 | dims <- length(value) 36 | } 37 | 38 | if (is.null(dtype)) { 39 | dtype <- hdf5r::guess_dtype(value, scalar = scalar, string_len = Inf) 40 | } 41 | 42 | if (is.null(space)) { 43 | space <- hdf5r::guess_space(value, dtype = dtype, chunked = FALSE) 44 | } 45 | 46 | # TODO: lzf compression is not supported in hdf5r 47 | # TODO: allow the user to choose compression level 48 | gzip_level <- if (compression == "none") 0 else 9 49 | 50 | out <- file$create_dataset( 51 | name = name, 52 | dims = dims, 53 | gzip_level = gzip_level, 54 | robj = value, 55 | chunk_dims = NULL, 56 | space = space, 57 | dtype = dtype 58 | ) 59 | # todo: create attr? 60 | 61 | out 62 | } 63 | 64 | 65 | #' HDF5 path exists 66 | #' 67 | #' Check that a path in HDF5 exists 68 | #' 69 | #' @noRd 70 | #' 71 | #' @param file Path to a HDF5 file 72 | #' @param target_path The path within the file to test for 73 | #' 74 | #' @return Whether the `path` exists in `file` 75 | hdf5_path_exists <- function(file, target_path) { 76 | tryCatch( 77 | { 78 | file$exists(target_path) 79 | }, 80 | error = function(e) { 81 | FALSE 82 | } 83 | ) 84 | } 85 | 86 | #' Create a HDF5 attribute 87 | #' 88 | #' Create a HDF5 attribute in a HDF5 file 89 | #' 90 | #' @noRd 91 | #' 92 | #' @param file Path to a H5AD file or an open H5AD handle 93 | #' @param name Name of the element within the H5AD file 94 | #' @param attr_name Name of the attribute to write 95 | #' @param attr_value Value of the attribute to write 96 | #' @param is_scalar Whether to write attributes as scalar values. Can be `TRUE` 97 | #' to write all attributes as scalars, `FALSE` to write no attributes as 98 | #' scalars, or a vector of the names of `attributes` that should be written. 99 | #' @param dtype The data type of the attribute to write. If `NULL` then the data 100 | #' type is guessed using `hdf5r::guess_dtype()`. 101 | #' @param space The space of the attribute to write. If `NULL` and `is_scalar` then 102 | #' the space is set to a scalar space. If `NULL` and `!is_scalar` then the space 103 | #' is guessed using `hdf5r::guess_space()`. 104 | hdf5_create_attribute <- function( 105 | file, 106 | name, 107 | attr_name, 108 | attr_value, 109 | is_scalar = TRUE, 110 | dtype = NULL, 111 | space = NULL 112 | ) { 113 | if (!inherits(file, "H5File")) { 114 | cli_abort("{.arg file} must be an open H5AD handle") 115 | } 116 | 117 | if (is.null(dtype)) { 118 | dtype <- hdf5r::guess_dtype( 119 | attr_value, 120 | scalar = is_scalar, 121 | string_len = Inf 122 | ) 123 | } 124 | if (is.null(space)) { 125 | space <- 126 | if (is_scalar) { 127 | hdf5r::H5S$new(type = "scalar") 128 | } else { 129 | hdf5r::guess_space(attr_value, dtype = dtype, chunked = FALSE) 130 | } 131 | } 132 | file$create_attr_by_name( 133 | attr_name = attr_name, 134 | obj_name = name, 135 | robj = attr_value, 136 | dtype = dtype, 137 | space = space 138 | ) 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # {anndataR}: An R package for working with AnnData objects anndataR logo 2 | 3 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 4 | [![CRAN status](https://www.r-pkg.org/badges/version/anndataR.png)](https://CRAN.R-project.org/package=anndataR) 5 | [![R-CMD-check](https://github.com/scverse/anndataR/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/scverse/anndataR/actions/workflows/R-CMD-check.yaml) 6 | 7 | 8 | **{anndataR}** aims to make the AnnData format a first-class citizen in 9 | the R ecosystem, and to make it easy to work with AnnData files in R, 10 | either directly or by converting them to a SingleCellExperiment or Seurat 11 | object. 12 | 13 | **{anndataR}** is an scverse® community project maintained by [Data Intuitive](https://data-intuitive.com/), and is fiscally sponsored by the [Chan Zuckerberg Initiative](https://chanzuckerberg.com/). 14 | 15 | 16 | ## Features of {anndataR} 17 | 18 | - Provide an `R6` class to work with AnnData objects in R (either in-memory or on-disk). 19 | - Read/write `*.h5ad` files natively 20 | - Convert to/from `SingleCellExperiment` objects 21 | - Convert to/from `Seurat` objects 22 | 23 | > [!WARNING] 24 | > 25 | > This package is still in the experimental stage, and may not work as 26 | > expected. You can find the status of development of anndataR on the 27 | > [feature tracking page](https://anndatar.data-intuitive.com/articles/design.html#feature-tracking) 28 | > of the website. Please [report](https://github.com/scverse/anndataR/issues) any issues you encounter. 29 | 30 | ## Installation 31 | 32 | You can install the development version of **{anndataR}** like so: 33 | 34 | ``` r 35 | # install.packages("pak") 36 | pak::pak("scverse/anndataR") 37 | ``` 38 | 39 | You will need to install additional dependencies, depending on 40 | the task you want to perform. 41 | 42 | - To read/write `*.h5ad` files, install [hdf5r](https://cran.r-project.org/package=hdf5r): 43 | `install.packages("hdf5r")` 44 | - To convert to/from `SingleCellExperiment` objects, install [SingleCellExperiment](https://bioconductor.org/packages/release/bioc/html/SingleCellExperiment.html): 45 | `BiocManager::install("SingleCellExperiment")` 46 | - To convert to/from `Seurat` objects, install [SeuratObject](https://cran.r-project.org/package=SeuratObject): 47 | `install.packages("SeuratObject")` 48 | 49 | Alternatively, you can install all suggested dependencies at once: 50 | 51 | ``` r 52 | pak::pak("scverse/anndataR", dependencies = TRUE) 53 | ``` 54 | 55 | ## Getting started 56 | 57 | The best way to get started with **{anndataR}** is to explore the package vignettes (available at https://anndatar.data-intuitive.com/articles/). 58 | 59 | - **Getting started**: An introduction to the package and its features. 60 | `vignette("anndataR", package = "anndataR")` 61 | - **Reading and writing H5AD files**: How to read and write `*.h5ad` files. 62 | `vignette("usage_h5ad", package = "anndataR")` 63 | - **Converting to/from Seurat objects**: How to convert between `AnnData` and `Seurat` objects. 64 | `vignette("usage_seurat", package = "anndataR")` 65 | - **Converting to/from SingleCellExperiment objects**: How to convert between `AnnData` and `SingleCellExperiment` objects. 66 | `vignette("usage_singlecellexperiment", package = "anndataR")` 67 | - **Software Design**: An overview of the design of the package. 68 | `vignette("software_design", package = "anndataR")` 69 | - **Development Status**: An overview of the development status of the package. 70 | `vignette("development_status", package = "anndataR")` 71 | - **Known Isses**: An overview of known issues with the package. 72 | `vignette("known_issues", package = "anndataR")` 73 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://anndataR.data-intuitive.com 2 | 3 | template: 4 | bootstrap: 5 5 | 6 | navbar: 7 | components: 8 | articles: 9 | text: Articles 10 | menu: 11 | - text: Usage 12 | - text: Read/write H5AD files 13 | href: articles/usage_h5ad.html 14 | - text: Read/write Seurat objects 15 | href: articles/usage_seurat.html 16 | - text: Read/write SingleCellExperiment objects 17 | href: articles/usage_singlecellexperiment.html 18 | - text: Mapping between SingleCellExperiment and AnnData 19 | href: articles/singlecellexperiment_mapping.html 20 | - text: ------- 21 | - text: Development 22 | - text: Software design 23 | href: articles/software_design.html 24 | - text: Development status 25 | href: articles/development_status.html 26 | - text: Known issues 27 | href: articles/known_issues.html 28 | -------------------------------------------------------------------------------- /anndataR.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 74a02069-5853-40a0-aa2f-7c3b8d406e45 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /inst/extdata/example.h5ad: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scverse/anndataR/8dfa46ae06fd5cc1d9b19479c5512a56d2357042/inst/extdata/example.h5ad -------------------------------------------------------------------------------- /inst/scripts/example_h5ad.py: -------------------------------------------------------------------------------- 1 | # python v3.10.10 2 | import anndata # anndata v0.8.0 3 | import scanpy # scanpy v1.9.3 4 | import numpy # numpy v1.23.5 5 | import pandas # pandas v2.0.0 6 | import scipy.sparse # scipy v1.10.1 7 | 8 | # This script uses Python to create an example H5AD file for testing 9 | # interoperability between languages. It is designed to be a small but 10 | # relatively complex file that tests reading of different types and data 11 | # structures. The standard scanpy workflow has also been applied to populate 12 | # some of the most common information from real analyses. It should be updated 13 | # to test new issues as they are discovered. 14 | # 15 | # NOTE: When updating this script for the {anndataR} example H5AD file please 16 | # update the package versions used above, update the script version, date and 17 | # changelog below and format the file using Python Black 18 | # (https://black.readthedocs.io/en/stable/). 19 | # 20 | # Version: 0.2.0 21 | # Date: 2023-05-11 22 | # 23 | # CHANGELOG 24 | # 25 | # v0.2.0 (2023-05-11) 26 | # - Add 1D sparse matrix to `adata.uns["Sparse1D"] 27 | # - Reduce the size of `adata.uns["String2D"]` and add columns to values 28 | # v0.1.1 (2023-05-09) 29 | # - Reduce the size of `adata.uns["String2D"]` to save space 30 | # - Reduce dimension to 50 x 100 to save space 31 | # v0.1.0 (2023-05-08) 32 | # - Initial version 33 | 34 | numpy.random.seed(0) 35 | 36 | # Randomly generate a counts matrix 37 | counts = numpy.random.poisson(2, size=(50, 100)) 38 | 39 | # Create an AnnData 40 | adata = anndata.AnnData(scipy.sparse.csr_matrix(counts.copy(), dtype=numpy.float32)) 41 | adata.obs_names = [f"Cell{i:03d}" for i in range(adata.n_obs)] 42 | adata.var_names = [f"Gene{i:03d}" for i in range(adata.n_vars)] 43 | 44 | # Populate layers with different matrix types 45 | adata.layers["counts"] = adata.X.copy() 46 | adata.layers["dense_counts"] = counts.copy() 47 | adata.layers["csc_counts"] = scipy.sparse.csc_matrix(counts.copy(), dtype=numpy.float32) 48 | 49 | # Populate adata.var with different types 50 | adata.var["String"] = [f"String{i}" for i in range(adata.n_vars)] 51 | 52 | # Populate adata.obs with different types 53 | adata.obs["Float"] = 42.42 54 | adata.obs["FloatNA"] = adata.obs["Float"] 55 | adata.obs["FloatNA"][0] = float("nan") 56 | adata.obs["Int"] = numpy.arange(adata.n_obs) 57 | adata.obs["IntNA"] = pandas.array([None] + [42] * (adata.n_obs - 1)) 58 | adata.obs["Bool"] = pandas.array([False] + [True] * (adata.n_obs - 1)) 59 | adata.obs["BoolNA"] = pandas.array([False, None] + [True] * (adata.n_obs - 2)) 60 | 61 | # Populate adata.uns with different types 62 | adata.uns["Category"] = pandas.array(["a", "b", None], dtype="category") 63 | adata.uns["Bool"] = [True, True, False] 64 | adata.uns["BoolNA"] = pandas.array([True, False, None]) 65 | adata.uns["Int"] = [1, 2, 3] 66 | adata.uns["IntNA"] = pandas.array([1, 2, None]) 67 | adata.uns["IntScalar"] = 1 68 | adata.uns["Sparse1D"] = scipy.sparse.csc_matrix([1, 2, 0, 0, 0, 3]) 69 | adata.uns["StringScalar"] = "A string" 70 | adata.uns["String"] = [f"String {i}" for i in range(10)] 71 | adata.uns["String2D"] = [[f"row{i}col{j}" for i in range(10)] for j in range(5)] 72 | adata.uns["DataFrameEmpty"] = pandas.DataFrame(index=adata.obs.index) 73 | 74 | # Run the standard scanpy workflow 75 | scanpy.pp.calculate_qc_metrics(adata, percent_top=None, inplace=True) 76 | scanpy.pp.normalize_total(adata, inplace=True) 77 | adata.layers["dense_X"] = adata.X.copy().toarray() 78 | scanpy.pp.log1p(adata) 79 | scanpy.pp.highly_variable_genes(adata) 80 | scanpy.tl.pca(adata) 81 | scanpy.pp.neighbors(adata) 82 | scanpy.tl.umap(adata) 83 | scanpy.tl.leiden(adata) 84 | scanpy.tl.rank_genes_groups(adata, "leiden") 85 | 86 | # Write the H5AD file 87 | adata.write("example.h5ad") 88 | -------------------------------------------------------------------------------- /man/AnnData-usage.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/AnnData-usage.R 3 | \name{AnnData-usage} 4 | \alias{AnnData-usage} 5 | \title{AnnData structure and usage} 6 | \description{ 7 | The \code{AnnData} object stores a data matrix \code{X} together with annotations of 8 | observations \code{obs} (\code{obsm}, \code{obsp}) and variables \code{var} (\code{varm}, \code{varp}). 9 | Additional layers of data can be stored in \code{layers} and unstructured 10 | annotations in \code{uns}. 11 | \subsection{Back ends}{ 12 | 13 | There are different back ends for \code{AnnData} objects that inherit from the 14 | abstract \link{AbstractAnnData} class. For example, the \link{InMemoryAnnData} stores 15 | data in memory or the \link{HDF5AnnData} is backed by a H5AD file. 16 | } 17 | 18 | \subsection{Usage}{ 19 | 20 | The items listed as \strong{"Slots"} are elements of the \code{AnnData} object that 21 | contain data and can be accessed or set. \strong{"Fields"} return information 22 | about the \code{AnnData} object but cannot be set directly. Both, as well as 23 | methods, can be accessed using the \code{$} operator 24 | 25 | For example: 26 | \itemize{ 27 | \item \code{adata$X} returns the \code{X} matrix 28 | \item \code{adata$X <- x} sets the \code{X} matrix 29 | \item \code{adata$method()} calls a method 30 | } 31 | } 32 | } 33 | \section{Fields}{ 34 | 35 | \describe{ 36 | \item{\code{shape}}{Dimensions (observations x variables) of the \code{AnnData} object} 37 | 38 | \item{\code{n_obs}}{Number of observations} 39 | 40 | \item{\code{n_vars}}{Number of variables} 41 | 42 | \item{\code{obs_keys}}{Keys (column names) of \code{obs}} 43 | 44 | \item{\code{var_keys}}{Keys (column names) of \code{var}} 45 | 46 | \item{\code{layers_keys}}{Keys (element names) of \code{layers}} 47 | 48 | \item{\code{obsm_keys}}{Keys (element names) of \code{obsm}} 49 | 50 | \item{\code{varm_keys}}{Keys (element names) of \code{varm}} 51 | 52 | \item{\code{obsp_keys}}{Keys (element names) of \code{obsp}} 53 | 54 | \item{\code{varp_keys}}{Keys (element names) of \code{varp}} 55 | 56 | \item{\code{uns_keys}}{Keys (element names) of \code{uns}} 57 | }} 58 | 59 | \section{Slots}{ 60 | 61 | \describe{ 62 | \item{\code{X}}{The main data matrix. Either \code{NULL} or an observation x variable 63 | matrix (without dimnames) with dimensions consistent with \code{n_obs} and 64 | \code{n_vars}.} 65 | 66 | \item{\code{layers}}{Additional data layers. Must be \code{NULL} or a named list of 67 | matrices having dimensions consistent with \code{n_obs} and \code{n_vars}.} 68 | 69 | \item{\code{obs}}{Observation annotations. A \code{data.frame} with columns containing 70 | information about observations. The number of rows of \code{obs} defines the 71 | observation dimension of the \code{AnnData} object (\code{n_obs}). If \code{NULL}, an 72 | \code{n_obs} × 0 \code{data.frame} will automatically be generated.} 73 | 74 | \item{\code{var}}{Variable annotations. A \code{data.frame} with columns containing 75 | information about variables. The number of rows of \code{var} defines the 76 | variable dimension of the \code{AnnData} object (\code{n_vars}). If \code{NULL}, an 77 | \code{n_vars} × 0 \code{data.frame} will automatically be generated.} 78 | 79 | \item{\code{obs_names}}{Observation names. Either \code{NULL} or a vector of unique 80 | identifiers used to identify each row of \code{obs} and to act as an index into 81 | the observation dimension of the \code{AnnData} object. For compatibility with 82 | \emph{R} representations, \code{obs_names} should be a unique character vector.} 83 | 84 | \item{\code{var_names}}{Variable names. Either \code{NULL} or a vector of unique 85 | identifiers used to identify each row of \code{var} and to act as an index into 86 | the variable dimension of the \code{AnnData} object. For compatibility with \emph{R} 87 | representations, \code{var_names} should be a unique character vector.} 88 | 89 | \item{\code{obsm}}{Multi-dimensional observation annotation. Must be \code{NULL} or a 90 | named list of array-like elements with number of rows equal to \code{n_obs}.} 91 | 92 | \item{\code{varm}}{Multi-dimensional variable annotations. Must be \code{NULL} or a named 93 | list of array-like elements with number of rows equal to \code{n_vars}.} 94 | 95 | \item{\code{obsp}}{Observation pairs. Must be \code{NULL} or a named list of array-like 96 | elements with number of rows and columns equal to \code{n_obs}.} 97 | 98 | \item{\code{varp}}{Variable pairs. Must be \code{NULL} or a named list of array-like 99 | elements with number of rows and columns equal to \code{n_vars}.} 100 | 101 | \item{\code{uns}}{Unstructured annotations. Must be \code{NULL} or a named list.} 102 | }} 103 | 104 | \section{Methods}{ 105 | 106 | \subsection{Conversion methods:}{ 107 | 108 | \describe{ 109 | \item{ 110 | \code{as_SingleCellExperiment()} 111 | }{ 112 | Convert to \code{\link[SingleCellExperiment:SingleCellExperiment]{SingleCellExperiment::SingleCellExperiment}}, see 113 | \code{\link[=as_SingleCellExperiment]{as_SingleCellExperiment()}} 114 | } 115 | \item{\code{as_Seurat()}}{Convert to \code{\link[SeuratObject:Seurat-class]{SeuratObject::Seurat}}, see \code{\link[=as_Seurat]{as_Seurat()}}} 116 | \item{\code{as_InMemoryAnnData()}}{Convert to \code{\link{InMemoryAnnData}}, as \code{\link[=as_InMemoryAnnData]{as_InMemoryAnnData()}}} 117 | \item{\code{as_HDF5AnnData()}}{Convert to \code{\link{HDF5AnnData}}, see \code{\link[=as_HDF5AnnData]{as_HDF5AnnData()}}} 118 | } 119 | } 120 | 121 | \subsection{Output methods:}{ 122 | 123 | \describe{ 124 | \item{ 125 | \code{write_h5ad()} 126 | }{ 127 | Write the \code{AnnData} object to an HDF5 file, see \code{\link[=write_h5ad]{write_h5ad()}} 128 | } 129 | } 130 | } 131 | 132 | \subsection{General methods:}{ 133 | 134 | \describe{ 135 | \item{\code{print()}}{Print a summary of the \code{AnnData} object} 136 | } 137 | } 138 | } 139 | 140 | \section{Functions that can be used to create AnnData objects}{ 141 | 142 | 143 | \describe{ 144 | \item{\code{\link[=AnnData]{AnnData()}}}{Create an \link{InMemoryAnnData} object} 145 | \item{\code{\link[=read_h5ad]{read_h5ad()}}}{Read an \code{AnnData} from a H5AD file} 146 | \item{\code{\link[=as_AnnData]{as_AnnData()}}}{Convert other objects to an \code{AnnData} object} 147 | } 148 | } 149 | 150 | \seealso{ 151 | The documentation for the Python \code{anndata} package 152 | \url{https://anndata.readthedocs.io/en/stable/} 153 | 154 | \link{AbstractAnnData} for the abstract class that all \code{AnnData} objects 155 | inherit from 156 | 157 | \link{InMemoryAnnData} for the in-memory implementation of \code{AnnData} 158 | 159 | \link{HDF5AnnData} for the HDF5-backed implementation of \code{AnnData} 160 | } 161 | -------------------------------------------------------------------------------- /man/AnnData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/AnnData.R 3 | \name{AnnData} 4 | \alias{AnnData} 5 | \title{Create an in-memory AnnData object.} 6 | \usage{ 7 | AnnData( 8 | X = NULL, 9 | obs = NULL, 10 | var = NULL, 11 | layers = NULL, 12 | obsm = NULL, 13 | varm = NULL, 14 | obsp = NULL, 15 | varp = NULL, 16 | uns = NULL, 17 | shape = NULL 18 | ) 19 | } 20 | \arguments{ 21 | \item{X}{See the \code{X} slot in \link{AnnData-usage}} 22 | 23 | \item{obs}{See the \code{obs} slot in \link{AnnData-usage}} 24 | 25 | \item{var}{See the \code{var} slot in \link{AnnData-usage}} 26 | 27 | \item{layers}{See the \code{layers} slot in \link{AnnData-usage}} 28 | 29 | \item{obsm}{See the \code{obsm} slot in \link{AnnData-usage}} 30 | 31 | \item{varm}{See the \code{varm} slot in \link{AnnData-usage}} 32 | 33 | \item{obsp}{See the \code{obsp} slot in \link{AnnData-usage}} 34 | 35 | \item{varp}{See the \code{varp} slot in \link{AnnData-usage}} 36 | 37 | \item{uns}{See the \code{uns} slot in \link{AnnData-usage}} 38 | 39 | \item{shape}{Shape tuple (e.g. \code{c(n_obs, n_vars)}). Can be provided if both 40 | \code{X} or \code{obs} and \code{var} are not provided.} 41 | } 42 | \value{ 43 | An \link{InMemoryAnnData} object 44 | } 45 | \description{ 46 | For more information on the functionality of an AnnData object, see \link{AnnData-usage} 47 | } 48 | \examples{ 49 | adata <- AnnData( 50 | X = matrix(1:12, nrow = 3, ncol = 4), 51 | obs = data.frame( 52 | row.names = paste0("obs", 1:3), 53 | n_counts = c(1, 2, 3), 54 | n_cells = c(1, 2, 3) 55 | ), 56 | var = data.frame( 57 | row.names = paste0("var", 1:4), 58 | n_cells = c(1, 2, 3, 4) 59 | ) 60 | ) 61 | 62 | adata 63 | } 64 | \seealso{ 65 | \link{AnnData-usage} for details of \code{AnnData} structure and usage 66 | 67 | Other AnnData creators: 68 | \code{\link{as_AnnData}()}, 69 | \code{\link{read_h5ad}()} 70 | } 71 | \concept{AnnData creators} 72 | -------------------------------------------------------------------------------- /man/anndataR-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/anndataR-package.R 3 | \docType{package} 4 | \name{anndataR-package} 5 | \alias{anndataR} 6 | \alias{anndataR-package} 7 | \title{anndataR: AnnData interoperability in R} 8 | \description{ 9 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 10 | 11 | Bring the power and flexibility of AnnData to the R ecosystem, allowing you to effortlessly manipulate and analyze your single-cell data. This package lets you work with backed h5ad and zarr files, directly access various slots (e.g. X, obs, var), or convert the data into SingleCellExperiment and Seurat objects. 12 | } 13 | \seealso{ 14 | Useful links: 15 | \itemize{ 16 | \item \url{https://anndatar.data-intuitive.com/} 17 | \item \url{https://github.com/scverse/anndataR} 18 | \item Report bugs at \url{https://github.com/scverse/anndataR/issues} 19 | } 20 | 21 | } 22 | \author{ 23 | \strong{Maintainer}: Robrecht Cannoodt \email{robrecht@data-intuitive.com} (\href{https://orcid.org/0000-0003-3641-729X}{ORCID}) (rcannood) 24 | 25 | Authors: 26 | \itemize{ 27 | \item Luke Zappia \email{luke@lazappi.id.au} (\href{https://orcid.org/0000-0001-7744-8565}{ORCID}) (lazappi) 28 | \item Martin Morgan \email{mtmorgan.bioc@gmail.com} (\href{https://orcid.org/0000-0002-5874-8148}{ORCID}) (mtmorgan) 29 | \item Louise Deconinck \email{louise.deconinck@gmail.com} (\href{https://orcid.org/0000-0001-8100-6823}{ORCID}) (LouiseDck) 30 | } 31 | 32 | Other contributors: 33 | \itemize{ 34 | \item Danila Bredikhin \email{danila.bredikhin@embl.de} (\href{https://orcid.org/0000-0001-8089-6983}{ORCID}) (gtca) [contributor] 35 | \item Isaac Virshup (\href{https://orcid.org/0000-0002-1710-8945}{ORCID}) (ivirshup) [contributor] 36 | \item Brian Schilder \email{brian_schilder@alumni.brown.edu} (\href{https://orcid.org/0000-0001-5949-2191}{ORCID}) (bschilder) [contributor] 37 | \item Chananchida Sang-aram (\href{https://orcid.org/0000-0002-0922-0822}{ORCID}) (csangara) [contributor] 38 | \item Data Intuitive \email{info@data-intuitive.com} [funder, copyright holder] 39 | \item Chan Zuckerberg Initiative [funder] 40 | } 41 | 42 | } 43 | \keyword{internal} 44 | -------------------------------------------------------------------------------- /man/as_HDF5AnnData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/HDF5AnnData.R 3 | \name{as_HDF5AnnData} 4 | \alias{as_HDF5AnnData} 5 | \title{Convert an \code{AnnData} to an \code{HDF5AnnData}} 6 | \usage{ 7 | as_HDF5AnnData( 8 | adata, 9 | file, 10 | compression = c("none", "gzip", "lzf"), 11 | mode = c("w-", "r", "r+", "a", "w", "x") 12 | ) 13 | } 14 | \arguments{ 15 | \item{adata}{An \code{AnnData} object to be converted to \code{\link{HDF5AnnData}}} 16 | 17 | \item{file}{The file name (character) of the \code{.h5ad} file} 18 | 19 | \item{compression}{The compression algorithm to use when writing the 20 | HDF5 file. Can be one of \code{"none"}, \code{"gzip"} or \code{"lzf"}. Defaults to 21 | \code{"none"}.} 22 | 23 | \item{mode}{The mode to open the HDF5 file: 24 | \itemize{ 25 | \item \code{a} creates a new file or opens an existing one for read/write 26 | \item \code{r} opens an existing file for reading 27 | \item \verb{r+} opens an existing file for read/write 28 | \item \code{w} creates a file, truncating any existing ones 29 | \item \verb{w-}/\code{x} are synonyms, creating a file and failing if it already exists 30 | }} 31 | } 32 | \value{ 33 | An \code{\link{HDF5AnnData}} object with the same data as the input \code{AnnData} 34 | object. 35 | } 36 | \description{ 37 | Convert another \code{AnnData} object to an \code{\link{HDF5AnnData}} object 38 | } 39 | \examples{ 40 | ad <- AnnData( 41 | X = matrix(1:5, 3L, 5L), 42 | layers = list( 43 | A = matrix(5:1, 3L, 5L), 44 | B = matrix(letters[1:5], 3L, 5L) 45 | ), 46 | obs = data.frame(row.names = LETTERS[1:3], cell = 1:3), 47 | var = data.frame(row.names = letters[1:5], gene = 1:5), 48 | ) 49 | ad$as_HDF5AnnData("test.h5ad") 50 | # remove file 51 | unlink("test.h5ad") 52 | } 53 | \seealso{ 54 | Other object converters: 55 | \code{\link{as_AnnData}()}, 56 | \code{\link{as_InMemoryAnnData}()}, 57 | \code{\link{as_Seurat}()}, 58 | \code{\link{as_SingleCellExperiment}()} 59 | } 60 | \concept{object converters} 61 | \keyword{internal} 62 | -------------------------------------------------------------------------------- /man/as_InMemoryAnnData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/InMemoryAnnData.R 3 | \name{as_InMemoryAnnData} 4 | \alias{as_InMemoryAnnData} 5 | \title{Convert an \code{AnnData} to an \code{InMemoryAnnData}} 6 | \usage{ 7 | as_InMemoryAnnData(adata) 8 | } 9 | \arguments{ 10 | \item{adata}{An \code{AnnData} object to be converted to \code{\link{InMemoryAnnData}}} 11 | } 12 | \value{ 13 | An \code{\link{InMemoryAnnData}} object with the same data as the input 14 | \code{AnnData} object 15 | } 16 | \description{ 17 | Convert another \code{AnnData} object to an \code{\link{InMemoryAnnData}} object 18 | } 19 | \examples{ 20 | ad <- AnnData( 21 | X = matrix(1:5, 3L, 5L), 22 | layers = list( 23 | A = matrix(5:1, 3L, 5L), 24 | B = matrix(letters[1:5], 3L, 5L) 25 | ), 26 | obs = data.frame(row.names = LETTERS[1:3], cell = 1:3), 27 | var = data.frame(row.names = letters[1:5], gene = 1:5) 28 | ) 29 | ad$as_InMemoryAnnData() 30 | } 31 | \seealso{ 32 | Other object converters: 33 | \code{\link{as_AnnData}()}, 34 | \code{\link{as_HDF5AnnData}()}, 35 | \code{\link{as_Seurat}()}, 36 | \code{\link{as_SingleCellExperiment}()} 37 | } 38 | \concept{object converters} 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /man/as_Seurat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_Seurat.R 3 | \name{as_Seurat} 4 | \alias{as_Seurat} 5 | \title{Convert an \code{AnnData} to a \code{Seurat}} 6 | \usage{ 7 | as_Seurat( 8 | adata, 9 | assay_name = "RNA", 10 | x_mapping = NULL, 11 | layers_mapping = TRUE, 12 | object_metadata_mapping = TRUE, 13 | assay_metadata_mapping = TRUE, 14 | reduction_mapping = TRUE, 15 | graph_mapping = TRUE, 16 | misc_mapping = TRUE 17 | ) 18 | } 19 | \arguments{ 20 | \item{adata}{The \code{AnnData} object to convert.} 21 | 22 | \item{assay_name}{Name of the assay to be created in the new \code{Seurat} object} 23 | 24 | \item{x_mapping}{A string specifying the name of the layer in the resulting 25 | \code{Seurat} object where the data in the \code{X} slot of \code{adata} will be mapped to} 26 | 27 | \item{layers_mapping}{A named vector where names are names of \code{Layers} in the 28 | resulting \code{Seurat} object and values are keys of \code{layers} in \code{adata}. See 29 | below for details.} 30 | 31 | \item{object_metadata_mapping}{A named vector where names are cell metadata 32 | columns in the resulting \code{Seurat} object and values are columns of \code{obs} in 33 | \code{adata}. See below for details.} 34 | 35 | \item{assay_metadata_mapping}{A named vector where names are variable 36 | metadata columns in the assay of the resulting \code{Seurat} object and values 37 | are columns of \code{var} in \code{adata}. See below for details.} 38 | 39 | \item{reduction_mapping}{A named vector where names are names of \code{Embeddings} 40 | in the resulting \code{Seurat} object and values are keys of \code{obsm} in \code{adata}. 41 | Alternatively, a named list where names are names of \code{Embeddings} in the 42 | resulting \code{Seurat} object and values are vectors with the items \code{"key"}, 43 | \code{"embeddings"} and (optionally) \code{"loadings"}. See below for details.} 44 | 45 | \item{graph_mapping}{A named vector where names are names of \code{Graphs} in the 46 | resulting \code{Seurat} object and values are keys of \code{obsp} in \code{adata}. See 47 | below for details.} 48 | 49 | \item{misc_mapping}{A named vector where names are names of \code{Misc} in the 50 | resulting \code{Seurat} object and values are keys of \code{uns} in \code{adata}. See 51 | below for details.} 52 | } 53 | \value{ 54 | A \code{Seurat} object containing the requested data from \code{adata} 55 | } 56 | \description{ 57 | Convert an \code{AnnData} object to a \code{Seurat} object 58 | } 59 | \details{ 60 | \subsection{Mapping arguments}{ 61 | 62 | All mapping arguments expect a named character vector where names are the 63 | names of the slot in the \code{Seurat} object and values are the keys of the 64 | corresponding slot of \code{adata}. If \code{TRUE}, the conversion function will guess 65 | which items to copy as described in the conversion table below. In most 66 | cases, the default is to copy all items using the same names except where the 67 | correspondence between objects is unclear. The \code{reduction_mapping} argument 68 | can also accept a more complex list format, see below for details. To avoid 69 | copying anything to a slot, set the mapping argument to \code{FALSE}. Empt 70 | mapping arguments (\code{NULL}, \code{c()}, \code{list()}) will be treated as \code{FALSE} with 71 | a warning. If an unnamed vector is provided, the values will be used as 72 | names. 73 | \subsection{Examples:}{ 74 | \itemize{ 75 | \item \code{TRUE} will guess which items to copy as described in the conversion 76 | table 77 | \item \code{c(seurat_item = "adata_item")} will copy \code{adata_item} from the slot in 78 | \code{adata} to \code{seurat_item} in the corresponding slot of the new \code{Seurat} 79 | object 80 | \item \code{FALSE} will avoid copying anything to the slot 81 | \item \code{c("adata_item")} is equivalent to \code{c(adata_item = "adata_item")} 82 | } 83 | } 84 | 85 | } 86 | 87 | \subsection{Conversion table}{\tabular{llll}{ 88 | \strong{From \code{AnnData}} \tab \strong{To \code{Seurat}} \tab \strong{Example mapping argument} \tab \strong{Default if \code{NULL}} \cr 89 | \code{adata$X} \tab \code{Layers(seurat)} \tab \code{x_mapping = "counts"} \emph{OR} \code{layers_mapping = c(counts = NA)} \tab The data in \code{adata$X} is copied to a layer named \code{X} \cr 90 | \code{adata$layers} \tab \code{Layers(seurat)} \tab \code{layers_mapping = c(counts = "counts")} \tab All items are copied by name \cr 91 | \code{adata$obs} \tab \code{seurat[[]]} \tab \code{object_metadata_mapping = c(n_counts = "n_counts", cell_type = "CellType")} \tab All columns are copied by name \cr 92 | \code{adata$var} \tab \code{seurat[[assay_name]][[]]} \tab \code{assay_metadata_mapping = c(n_cells = "n_cells", pct_zero = "PctZero")} \tab All columns are copied by name \cr 93 | \code{adata$obsm} \tab \code{Embeddings(sce)} \tab \code{reduction_mapping = c(pca = "X_pca")} \strong{OR} \code{reduction_mapping = list(pca = c(key = "PC_", obsm = "X_pca", varm = "PCs"))} \tab All items that can be coerced to a numeric matrix are copied by name without loadings except for \code{"X_pca"} for which loadings are added from \code{"PCs"} \cr 94 | \code{adata$obsp} \tab \code{Graphs(seurat)} \tab \code{graph_mapping = c(nn = "connectivities")} \tab All items are copied by name \cr 95 | \code{adata$varp} \tab \emph{NA} \tab \emph{NA} \tab There is no corresponding slot for \code{varp} \cr 96 | \code{adata$uns} \tab \code{Misc(seurat)} \tab \code{misc_mapping = c(project_metadata = "metadata")} \tab All items are copied by name \cr 97 | } 98 | 99 | } 100 | 101 | \subsection{The \code{reduction_mapping} argument}{ 102 | 103 | For the simpler named vector format, the names should be the names of 104 | \code{Embeddings} in the resulting \code{Seurat} object, and the values 105 | should be the keys of \code{obsm} in \code{adata}. A key will created from the \code{obsm} 106 | key. 107 | 108 | For more advanced mapping, use the list format where each item is a vector 109 | with the following names defining arguments to 110 | \code{\link[SeuratObject:CreateDimReducObject]{SeuratObject::CreateDimReducObject()}}: 111 | \itemize{ 112 | \item \code{key}: the key of the resulting \code{\link[SeuratObject:DimReduc-class]{SeuratObject::DimReduc}} object, passed 113 | to the \code{key} argument after processing with \code{\link[SeuratObject:Key]{SeuratObject::Key()}} 114 | \item \code{embeddings}: a key of the \code{obsm} slot in \code{adata}, 115 | \code{adata$obsm[[embeddings]]} is passed to the \code{embeddings} argument 116 | \item \code{loadings}: a key of the \code{varm} slot in \code{adata} (optional), 117 | \code{adata$varm[[loadings]]} is passed to the \code{loadings} argument 118 | } 119 | } 120 | 121 | \subsection{The \code{x_mapping} and \code{layers_mapping} arguments}{ 122 | 123 | In order to specify where the data in \code{adata$X} will be stored in the 124 | \code{Layers(seurat)} slot of the resulting object, you can use either the \code{x_mapping} 125 | argument or the \code{layers_mapping} argument. 126 | If you use \code{x_mapping}, it should be a string specifying the name of the layer 127 | in \code{Layers(seurat)} where the data in \code{adata$X} will be stored. 128 | If you use \code{layers_mapping}, it should be a named vector where names are names 129 | of \code{Layers(seurat)} and values are keys of \code{layers} in \code{adata}. 130 | In order to indicate the \code{adata$X} slot, you use \code{NA} as the value in the vector. 131 | The name you provide for \code{x_mapping} may not be a name in \code{layers_mapping}. 132 | You must provide a layer named \code{counts} or \code{data} in either \code{x_mapping} or 133 | \code{layers_mapping}. 134 | } 135 | } 136 | \examples{ 137 | \dontshow{if (rlang::is_installed("Seurat")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 138 | ad <- AnnData( 139 | X = matrix(1:5, 3L, 5L), 140 | obs = data.frame(row.names = LETTERS[1:3], cell = 1:3), 141 | var = data.frame(row.names = letters[1:5], gene = 1:5) 142 | ) 143 | 144 | # Default usage 145 | seurat <- ad$as_Seurat( 146 | assay_name = "RNA", 147 | x_mapping = "counts", 148 | layers_mapping = TRUE, 149 | object_metadata_mapping = TRUE, 150 | assay_metadata_mapping = TRUE, 151 | reduction_mapping = TRUE, 152 | graph_mapping = TRUE, 153 | misc_mapping = TRUE 154 | ) 155 | \dontshow{\}) # examplesIf} 156 | } 157 | \seealso{ 158 | Other object converters: 159 | \code{\link{as_AnnData}()}, 160 | \code{\link{as_HDF5AnnData}()}, 161 | \code{\link{as_InMemoryAnnData}()}, 162 | \code{\link{as_SingleCellExperiment}()} 163 | } 164 | \concept{object converters} 165 | \keyword{internal} 166 | -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: deprecated 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | deprecated 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: experimental 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | experimental 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: stable 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | lifecycle 21 | 22 | 25 | 26 | stable 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | 2 | lifecycle: superseded 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | lifecycle 18 | 19 | superseded 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scverse/anndataR/8dfa46ae06fd5cc1d9b19479c5512a56d2357042/man/figures/logo.png -------------------------------------------------------------------------------- /man/from_Seurat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/from_Seurat.R 3 | \name{from_Seurat} 4 | \alias{from_Seurat} 5 | \title{Convert a Seurat object to an AnnData object} 6 | \usage{ 7 | from_Seurat( 8 | seurat_obj, 9 | assay_name = NULL, 10 | x_mapping = NULL, 11 | layers_mapping = TRUE, 12 | obs_mapping = TRUE, 13 | var_mapping = TRUE, 14 | obsm_mapping = TRUE, 15 | varm_mapping = TRUE, 16 | obsp_mapping = TRUE, 17 | varp_mapping = TRUE, 18 | uns_mapping = TRUE, 19 | output_class = c("InMemory", "HDF5AnnData"), 20 | ... 21 | ) 22 | } 23 | \arguments{ 24 | \item{seurat_obj}{See \code{\link[=as_AnnData]{as_AnnData()}}} 25 | 26 | \item{assay_name}{See \code{\link[=as_AnnData]{as_AnnData()}}} 27 | 28 | \item{x_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 29 | 30 | \item{layers_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 31 | 32 | \item{obs_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 33 | 34 | \item{var_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 35 | 36 | \item{obsm_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 37 | 38 | \item{varm_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 39 | 40 | \item{obsp_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 41 | 42 | \item{varp_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 43 | 44 | \item{uns_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 45 | 46 | \item{output_class}{See \code{\link[=as_AnnData]{as_AnnData()}}} 47 | 48 | \item{...}{See \code{\link[=as_AnnData]{as_AnnData()}}} 49 | } 50 | \value{ 51 | An \code{AnnData} object 52 | } 53 | \description{ 54 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 55 | 56 | This function is deprecated, use \code{\link[=as_AnnData]{as_AnnData()}} instead 57 | } 58 | -------------------------------------------------------------------------------- /man/from_SingleCellExperiment.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/from_SingleCellExperiment.R 3 | \name{from_SingleCellExperiment} 4 | \alias{from_SingleCellExperiment} 5 | \title{Convert a SingleCellExperiment object to an AnnData object} 6 | \usage{ 7 | from_SingleCellExperiment( 8 | sce, 9 | x_mapping = NULL, 10 | layers_mapping = TRUE, 11 | obs_mapping = TRUE, 12 | var_mapping = TRUE, 13 | obsm_mapping = TRUE, 14 | varm_mapping = TRUE, 15 | obsp_mapping = TRUE, 16 | varp_mapping = TRUE, 17 | uns_mapping = TRUE, 18 | output_class = c("InMemory", "HDF5AnnData"), 19 | ... 20 | ) 21 | } 22 | \arguments{ 23 | \item{sce}{See \code{\link[=as_AnnData]{as_AnnData()}}} 24 | 25 | \item{x_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 26 | 27 | \item{layers_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 28 | 29 | \item{obs_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 30 | 31 | \item{var_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 32 | 33 | \item{obsm_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 34 | 35 | \item{varm_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 36 | 37 | \item{obsp_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 38 | 39 | \item{varp_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 40 | 41 | \item{uns_mapping}{See \code{\link[=as_AnnData]{as_AnnData()}}} 42 | 43 | \item{output_class}{See \code{\link[=as_AnnData]{as_AnnData()}}} 44 | 45 | \item{...}{See \code{\link[=as_AnnData]{as_AnnData()}}} 46 | } 47 | \value{ 48 | An \code{AnnData} object 49 | } 50 | \description{ 51 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 52 | 53 | This function is deprecated, use \code{\link[=as_AnnData]{as_AnnData()}} instead 54 | } 55 | -------------------------------------------------------------------------------- /man/generate_dataset.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/generate_dataset.R 3 | \name{generate_dataset} 4 | \alias{generate_dataset} 5 | \title{Generate a dataset} 6 | \usage{ 7 | generate_dataset( 8 | n_obs = 10L, 9 | n_vars = 20L, 10 | x_type = "numeric_matrix", 11 | layer_types = c("numeric_matrix", "numeric_dense", "numeric_csparse", 12 | "numeric_rsparse", "numeric_matrix_with_nas", "numeric_dense_with_nas", 13 | "numeric_csparse_with_nas", "numeric_rsparse_with_nas", "integer_matrix", 14 | "integer_dense", "integer_csparse", "integer_rsparse", "integer_matrix_with_nas", 15 | "integer_dense_with_nas", "integer_csparse_with_nas", "integer_rsparse_with_nas"), 16 | obs_types = c("character", "integer", "factor", "factor_ordered", "logical", "numeric", 17 | "character_with_nas", "integer_with_nas", "factor_with_nas", 18 | "factor_ordered_with_nas", "logical_with_nas", "numeric_with_nas"), 19 | var_types = c("character", "integer", "factor", "factor_ordered", "logical", "numeric", 20 | "character_with_nas", "integer_with_nas", "factor_with_nas", 21 | "factor_ordered_with_nas", "logical_with_nas", "numeric_with_nas"), 22 | obsm_types = c("numeric_matrix", "numeric_dense", "numeric_csparse", "numeric_rsparse", 23 | "numeric_matrix_with_nas", "numeric_dense_with_nas", "numeric_csparse_with_nas", 24 | "numeric_rsparse_with_nas", "integer_matrix", "integer_dense", "integer_csparse", 25 | "integer_rsparse", "integer_matrix_with_nas", "integer_dense_with_nas", 26 | "integer_csparse_with_nas", "integer_rsparse_with_nas", "character", "integer", 27 | "factor", "factor_ordered", "logical", "numeric", "character_with_nas", 28 | "integer_with_nas", "factor_with_nas", 29 | "factor_ordered_with_nas", 30 | "logical_with_nas", "numeric_with_nas"), 31 | varm_types = c("numeric_matrix", "numeric_dense", "numeric_csparse", "numeric_rsparse", 32 | "numeric_matrix_with_nas", "numeric_dense_with_nas", "numeric_csparse_with_nas", 33 | "numeric_rsparse_with_nas", "integer_matrix", "integer_dense", "integer_csparse", 34 | "integer_rsparse", "integer_matrix_with_nas", "integer_dense_with_nas", 35 | "integer_csparse_with_nas", "integer_rsparse_with_nas", "character", "integer", 36 | "factor", "factor_ordered", "logical", "numeric", "character_with_nas", 37 | "integer_with_nas", "factor_with_nas", 38 | "factor_ordered_with_nas", 39 | "logical_with_nas", "numeric_with_nas"), 40 | obsp_types = c("numeric_matrix", "numeric_dense", "numeric_csparse", "numeric_rsparse", 41 | "numeric_matrix_with_nas", "numeric_dense_with_nas", "numeric_csparse_with_nas", 42 | "numeric_rsparse_with_nas", "integer_matrix", "integer_dense", "integer_csparse", 43 | "integer_rsparse", "integer_matrix_with_nas", "integer_dense_with_nas", 44 | "integer_csparse_with_nas", "integer_rsparse_with_nas"), 45 | varp_types = c("numeric_matrix", "numeric_dense", "numeric_csparse", "numeric_rsparse", 46 | "numeric_matrix_with_nas", "numeric_dense_with_nas", "numeric_csparse_with_nas", 47 | "numeric_rsparse_with_nas", "integer_matrix", "integer_dense", "integer_csparse", 48 | "integer_rsparse", "integer_matrix_with_nas", "integer_dense_with_nas", 49 | "integer_csparse_with_nas", "integer_rsparse_with_nas"), 50 | uns_types = c("scalar_character", "scalar_integer", "scalar_factor", 51 | "scalar_factor_ordered", "scalar_logical", "scalar_numeric", 52 | "scalar_character_with_nas", "scalar_integer_with_nas", "scalar_factor_with_nas", 53 | "scalar_factor_ordered_with_nas", "scalar_logical_with_nas", 54 | "scalar_numeric_with_nas", "vec_character", "vec_integer", "vec_factor", 55 | "vec_factor_ordered", "vec_logical", "vec_numeric", "vec_character_with_nas", 56 | "vec_integer_with_nas", "vec_factor_with_nas", "vec_factor_ordered_with_nas", 57 | "vec_logical_with_nas", 58 | "vec_numeric_with_nas", "df_character", "df_integer", 59 | "df_factor", "df_factor_ordered", "df_logical", "df_numeric", 60 | "df_character_with_nas", "df_integer_with_nas", "df_factor_with_nas", 61 | "df_factor_ordered_with_nas", "df_logical_with_nas", "df_numeric_with_nas", 62 | "mat_numeric_matrix", "mat_numeric_dense", "mat_numeric_csparse", 63 | "mat_numeric_rsparse", "mat_numeric_matrix_with_nas", "mat_numeric_dense_with_nas", 64 | "mat_numeric_csparse_with_nas", "mat_numeric_rsparse_with_nas", "mat_integer_matrix", 65 | 66 | "mat_integer_dense", "mat_integer_csparse", "mat_integer_rsparse", 67 | "mat_integer_matrix_with_nas", "mat_integer_dense_with_nas", 68 | "mat_integer_csparse_with_nas", "mat_integer_rsparse_with_nas", "list"), 69 | example = FALSE, 70 | format = c("list", "AnnData", "SingleCellExperiment", "Seurat") 71 | ) 72 | } 73 | \arguments{ 74 | \item{n_obs}{Number of observations to generate} 75 | 76 | \item{n_vars}{Number of variables to generate} 77 | 78 | \item{x_type}{Type of matrix to generate for X} 79 | 80 | \item{layer_types}{Types of matrices to generate for layers} 81 | 82 | \item{obs_types}{Types of vectors to generate for obs} 83 | 84 | \item{var_types}{Types of vectors to generate for var} 85 | 86 | \item{obsm_types}{Types of matrices to generate for obsm} 87 | 88 | \item{varm_types}{Types of matrices to generate for varm} 89 | 90 | \item{obsp_types}{Types of matrices to generate for obsp} 91 | 92 | \item{varp_types}{Types of matrices to generate for varp} 93 | 94 | \item{uns_types}{Types of objects to generate for uns} 95 | 96 | \item{example}{If \code{TRUE}, the types will be overridden to a small set of 97 | types. This is useful for documentations.} 98 | 99 | \item{format}{Object type to output, one of "list", "AnnData", 100 | "SingleCellExperiment", or "Seurat".} 101 | } 102 | \value{ 103 | Object containing the generated dataset as defined by \code{output} 104 | } 105 | \description{ 106 | Generate a dataset with different types of columns and layers 107 | } 108 | \examples{ 109 | dummy <- generate_dataset() 110 | \dontrun{ 111 | dummy <- generate_dataset(format = "AnnData") 112 | dummy <- generate_dataset(format = "SingleCellExperiment") 113 | dummy <- generate_dataset(format = "Seurat") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /man/read_h5ad.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/read_h5ad.R 3 | \name{read_h5ad} 4 | \alias{read_h5ad} 5 | \title{Read H5AD} 6 | \usage{ 7 | read_h5ad( 8 | path, 9 | as = c("InMemoryAnnData", "HDF5AnnData", "SingleCellExperiment", "Seurat"), 10 | to = deprecated(), 11 | mode = c("r", "r+", "a", "w", "w-", "x"), 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{path}{Path to the H5AD file to read} 17 | 18 | \item{as}{The type of object to return. One of: 19 | \itemize{ 20 | \item \code{"InMemoryAnnData"}: Read the H5AD file into memory as an 21 | \code{\link{InMemoryAnnData}} object 22 | \item \code{"HDF5AnnData"}: Read the H5AD file as an \code{\link{HDF5AnnData}} object 23 | \item \code{"SingleCellExperiment"}: Read the H5AD file as a 24 | \code{\link[SingleCellExperiment:SingleCellExperiment]{SingleCellExperiment::SingleCellExperiment}} object 25 | \item \code{"Seurat"}: Read the H5AD file as a 26 | \code{\link[SeuratObject:Seurat-class]{SeuratObject::Seurat}} object 27 | }} 28 | 29 | \item{to}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated, use \code{as} instead} 30 | 31 | \item{mode}{The mode to open the HDF5 file. 32 | \itemize{ 33 | \item \code{a} creates a new file or opens an existing one for read/write. 34 | \item \code{r} opens an existing file for reading. 35 | \item \verb{r+} opens an existing file for read/write. 36 | \item \code{w} creates a file, truncating any existing ones. 37 | \item \verb{w-}/\code{x} are synonyms, creating a file and failing if it already exists. 38 | }} 39 | 40 | \item{...}{Extra arguments provided to the \verb{as_*} conversion function for the 41 | object specified by \code{as}} 42 | } 43 | \value{ 44 | The object specified by \code{as} 45 | } 46 | \description{ 47 | Read data from a H5AD file 48 | } 49 | \examples{ 50 | h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") 51 | 52 | # Read the H5AD as a SingleCellExperiment object 53 | if (requireNamespace("SingleCellExperiment", quietly = TRUE)) { 54 | sce <- read_h5ad(h5ad_file, as = "SingleCellExperiment") 55 | } 56 | 57 | # Read the H5AD as a Seurat object 58 | if (requireNamespace("SeuratObject", quietly = TRUE)) { 59 | seurat <- read_h5ad(h5ad_file, as = "Seurat") 60 | } 61 | } 62 | \seealso{ 63 | Other AnnData creators: 64 | \code{\link{AnnData}()}, 65 | \code{\link{as_AnnData}()} 66 | } 67 | \concept{AnnData creators} 68 | -------------------------------------------------------------------------------- /man/to_Seurat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_Seurat.R 3 | \name{to_Seurat} 4 | \alias{to_Seurat} 5 | \title{Convert an AnnData object to a Seurat object} 6 | \usage{ 7 | to_Seurat(...) 8 | } 9 | \arguments{ 10 | \item{...}{Arguments passed to \code{\link[=as_Seurat]{as_Seurat()}}} 11 | } 12 | \value{ 13 | A \code{Seurat} object 14 | } 15 | \description{ 16 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 17 | 18 | This function is deprecated, use \code{adata$as_Seurat()} instead 19 | } 20 | -------------------------------------------------------------------------------- /man/to_SingleCellExperiment.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as_SingleCellExperiment.R 3 | \name{to_SingleCellExperiment} 4 | \alias{to_SingleCellExperiment} 5 | \title{Convert an AnnData object to a SingleCellExperiment object} 6 | \usage{ 7 | to_SingleCellExperiment(...) 8 | } 9 | \arguments{ 10 | \item{...}{Arguments passed to \code{\link[=as_SingleCellExperiment]{as_SingleCellExperiment()}}} 11 | } 12 | \value{ 13 | A \code{SingleCellExperiment} object 14 | } 15 | \description{ 16 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 17 | 18 | This function is deprecated, use \code{adata$as_SingleCellExperiment()} instead 19 | } 20 | -------------------------------------------------------------------------------- /man/write_h5ad.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/write_h5ad.R 3 | \name{write_h5ad} 4 | \alias{write_h5ad} 5 | \title{Write H5AD} 6 | \usage{ 7 | write_h5ad( 8 | object, 9 | path, 10 | compression = c("none", "gzip", "lzf"), 11 | mode = c("w-", "r", "r+", "a", "w", "x"), 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{object}{The object to write, either a 17 | \code{\link[SingleCellExperiment:SingleCellExperiment]{SingleCellExperiment::SingleCellExperiment}} or a 18 | \code{\link[SeuratObject:Seurat-class]{SeuratObject::Seurat}} object} 19 | 20 | \item{path}{Path of the file to write to} 21 | 22 | \item{compression}{The compression algorithm to use when writing the HDF5 23 | file. Can be one of \code{"none"}, \code{"gzip"} or \code{"lzf"}. Defaults to \code{"none"}.} 24 | 25 | \item{mode}{The mode to open the HDF5 file. 26 | \itemize{ 27 | \item \code{a} creates a new file or opens an existing one for read/write 28 | \item \verb{r+} opens an existing file for read/write 29 | \item \code{w} creates a file, truncating any existing ones 30 | \item \verb{w-}/\code{x} are synonyms creating a file and failing if it already exists 31 | }} 32 | 33 | \item{...}{Additional arguments passed to \code{\link[=as_AnnData]{as_AnnData()}}} 34 | } 35 | \value{ 36 | \code{path} invisibly 37 | } 38 | \description{ 39 | Write an H5AD file 40 | } 41 | \examples{ 42 | adata <- AnnData( 43 | X = matrix(1:5, 3L, 5L), 44 | layers = list( 45 | A = matrix(5:1, 3L, 5L), 46 | B = matrix(letters[1:5], 3L, 5L) 47 | ), 48 | obs = data.frame(row.names = LETTERS[1:3], cell = 1:3), 49 | var = data.frame(row.names = letters[1:5], gene = 1:5) 50 | ) 51 | h5ad_file <- tempfile(fileext = ".h5ad") 52 | adata$write_h5ad(h5ad_file) 53 | 54 | # Write a SingleCellExperiment as an H5AD 55 | if (requireNamespace("SingleCellExperiment", quietly = TRUE)) { 56 | ncells <- 100 57 | counts <- matrix(rpois(20000, 5), ncol = ncells) 58 | logcounts <- log2(counts + 1) 59 | 60 | pca <- matrix(runif(ncells * 5), ncells) 61 | tsne <- matrix(rnorm(ncells * 2), ncells) 62 | 63 | sce <- SingleCellExperiment::SingleCellExperiment( 64 | assays = list(counts = counts, logcounts = logcounts), 65 | reducedDims = list(PCA = pca, tSNE = tsne) 66 | ) 67 | 68 | adata <- as_AnnData(sce) 69 | h5ad_file <- tempfile(fileext = ".h5ad") 70 | adata$write_h5ad(h5ad_file) 71 | } 72 | 73 | # Write a Seurat as a H5AD 74 | if (requireNamespace("Seurat", quietly = TRUE)) { 75 | library(Seurat) 76 | 77 | counts <- matrix(1:15, 5L, 3L) 78 | dimnames(counts) <- list( 79 | LETTERS[1:5], 80 | letters[1:3] 81 | ) 82 | cell.metadata <- data.frame( 83 | row.names = letters[1:3], 84 | cell = 1:3 85 | ) 86 | obj <- CreateSeuratObject(counts, meta.data = cell.metadata) 87 | gene.metadata <- data.frame( 88 | row.names = LETTERS[1:5], 89 | gene = 1:5 90 | ) 91 | obj[["RNA"]] <- AddMetaData(GetAssay(obj), gene.metadata) 92 | 93 | adata <- as_AnnData(obj) 94 | h5ad_file <- tempfile(fileext = ".h5ad") 95 | adata$write_h5ad(h5ad_file) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | library(testthat) 10 | library(anndataR) 11 | 12 | test_check("anndataR") 13 | -------------------------------------------------------------------------------- /tests/testthat/helper-expect_equal_py.R: -------------------------------------------------------------------------------- 1 | expect_equal_py <- function(a, b) { 2 | requireNamespace("testthat") 3 | requireNamespace("reticulate") 4 | 5 | bi <- reticulate::import_builtins() 6 | 7 | testthat::expect_equal(bi$type(a), bi$type(b)) # does this always work? 8 | 9 | if (inherits(a, "pandas.core.frame.DataFrame")) { 10 | pd <- reticulate::import("pandas") 11 | testthat::expect_null( 12 | pd$testing$assert_frame_equal( 13 | a, 14 | b, 15 | check_dtype = FALSE, 16 | check_exact = FALSE 17 | ) 18 | ) 19 | } else if ( 20 | inherits(a, "np.ndarray") || inherits(a, "scipy.sparse.base.spmatrix") 21 | ) { 22 | scipy <- reticulate::import("scipy") 23 | np <- reticulate::import("numpy") 24 | 25 | testthat::expect_equal(a$dtype, b$dtype) 26 | 27 | testthat::expect_equal( 28 | py_to_r_ifneedbe(a$shape), 29 | py_to_r_ifneedbe(b$shape) 30 | ) 31 | 32 | a_dense <- 33 | if (scipy$sparse$issparse(a)) { 34 | a$toarray() 35 | } else { 36 | a 37 | } 38 | b_dense <- 39 | if (scipy$sparse$issparse(b)) { 40 | b$toarray() 41 | } else { 42 | b 43 | } 44 | 45 | testthat::expect_null( 46 | np$testing$assert_allclose(a_dense, b_dense) 47 | ) 48 | } 49 | } 50 | 51 | py_to_r_ifneedbe <- function(x) { 52 | if (inherits(x, "python.builtin.object")) { 53 | requireNamespace("reticulate") 54 | reticulate::py_to_r(x) 55 | } else { 56 | x 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/testthat/helper-py-R-equivalences.R: -------------------------------------------------------------------------------- 1 | matrix_equivalences <- list( 2 | c("float_matrix", "numeric_matrix"), 3 | c("float_matrix", "numeric_dense"), 4 | c("float_matrix_nas", "numeric_matrix_with_nas"), 5 | c("float_matrix_nas", "numeric_dense_with_nas"), 6 | c("integer_matrix", "integer_matrix"), 7 | c("integer_matrix", "integer_dense"), 8 | c("float_csparse", "numeric_csparse"), 9 | c("float_csparse_nas", "numeric_csparse_with_nas"), 10 | c("float_rsparse", "numeric_rsparse"), 11 | c("float_rsparse_nas", "numeric_rsparse_with_nas") 12 | ) 13 | 14 | # python, R 15 | vector_equivalences <- list( 16 | c("categorical", "factor"), 17 | c("categorical_ordered", "factor_ordered"), 18 | c("categorical_missing_values", "factor_with_nas"), 19 | c("categorical_ordered_missing_values", "factor_ordered_with_nas"), 20 | c("string_array", "character"), 21 | c("dense_array", "numeric"), 22 | c("integer_array", "integer"), 23 | c("boolean_array", "logical"), 24 | c("nullable_integer_array", "integer_with_nas"), 25 | c("nullable_boolean_array", "logical_with_nas") 26 | ) 27 | 28 | all_equivalences <- c(matrix_equivalences, vector_equivalences) 29 | 30 | check_arg <- function(args, name, falseval) { 31 | if (name %in% names(args)) { 32 | args[[name]][[1]] 33 | } else { 34 | falseval 35 | } 36 | } 37 | 38 | r_generate_dataset <- function(n_obs, n_vars, write = FALSE, ...) { 39 | args <- list(...) 40 | 41 | data <- generate_dataset( 42 | n_obs, 43 | n_vars, 44 | x_type = check_arg(args, "x_type", "numeric_matrix"), 45 | layer_types = check_arg(args, "layer_types", character()), 46 | obs_types = ifelse("obs_types" %in% names(args), args$obs_types, "integer"), 47 | var_types = ifelse("var_types" %in% names(args), args$var_types, "integer"), 48 | obsm_types = check_arg(args, "obsm_types", character()), 49 | varm_types = check_arg(args, "varm_types", character()), 50 | obsp_types = check_arg(args, "obsp_types", character()), 51 | varp_types = check_arg(args, "varp_types", character()), 52 | uns_types = check_arg(args, "uns_types", character()), 53 | format = "AnnData" 54 | ) 55 | if (write) { 56 | r_write_dataset(data) 57 | } 58 | 59 | data 60 | } 61 | 62 | r_write_dataset <- function(dataset, file = NULL) { 63 | if (is.null(file)) { 64 | file <- tempfile(pattern = "hdf5_write_R_", fileext = ".h5ad") 65 | } 66 | write_h5ad(dataset, file) 67 | file 68 | } 69 | -------------------------------------------------------------------------------- /tests/testthat/helper-skip_if_no_anndata.R: -------------------------------------------------------------------------------- 1 | # helper function to skip tests if we don't have the Python 'anndata' module 2 | # or the R {anndata} package 3 | skip_if_no_anndata <- function() { 4 | testthat::skip_if_not_installed("reticulate") 5 | testthat::skip_if_not_installed("anndata") 6 | requireNamespace("reticulate") 7 | testthat::skip_if_not( 8 | reticulate::py_module_available("anndata"), 9 | message = "Python anndata module not available for testing" 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/helper-skip_if_no_h5diff.R: -------------------------------------------------------------------------------- 1 | # helper function to skip tests if h5diff is not available 2 | skip_if_no_h5diff <- function() { 3 | testthat::skip_if_not( 4 | { 5 | s <- system2( 6 | command = "which", 7 | args = "h5diff", 8 | stdout = TRUE, 9 | stderr = TRUE 10 | ) 11 | is.null(attr(s, "status")) 12 | }, 13 | message = "h5diff not available for testing" 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /tests/testthat/test-HDF5-read.R: -------------------------------------------------------------------------------- 1 | skip_if_not_installed("hdf5r") 2 | 3 | requireNamespace("vctrs") 4 | 5 | filename <- system.file("extdata", "example.h5ad", package = "anndataR") 6 | file <- hdf5r::H5File$new( 7 | filename, 8 | mode = "r" 9 | ) 10 | 11 | test_that("reading encoding works", { 12 | encoding <- read_h5ad_encoding(file, "obs") 13 | expect_equal(names(encoding), c("type", "version")) 14 | }) 15 | 16 | test_that("reading dense matrices works", { 17 | mat <- read_h5ad_dense_array(file, "layers/dense_counts") 18 | expect_true(is.matrix(mat)) 19 | expect_type(mat, "integer") 20 | expect_equal(dim(mat), c(50, 100)) 21 | 22 | mat <- read_h5ad_dense_array(file, "layers/dense_X") 23 | expect_true(is.matrix(mat)) 24 | expect_type(mat, "double") 25 | expect_equal(dim(mat), c(50, 100)) 26 | }) 27 | 28 | test_that("reading sparse matrices works", { 29 | mat <- read_h5ad_sparse_array(file, "layers/csc_counts", type = "csc") 30 | expect_s4_class(mat, "dgCMatrix") 31 | expect_equal(dim(mat), c(50, 100)) 32 | 33 | mat <- read_h5ad_sparse_array(file, "layers/counts", type = "csr") 34 | expect_s4_class(mat, "dgRMatrix") 35 | expect_equal(dim(mat), c(50, 100)) 36 | }) 37 | 38 | test_that("reading recarrays works", { 39 | array_list <- read_h5ad_rec_array( 40 | file, 41 | "uns/rank_genes_groups/logfoldchanges" 42 | ) 43 | expect_true(is.list(array_list)) 44 | expect_equal(names(array_list), c("0", "1", "2", "3", "4", "5")) 45 | for (array in array_list) { 46 | expect_true(is.vector(array)) 47 | expect_type(array, "double") 48 | expect_equal(length(array), 100) 49 | } 50 | }) 51 | 52 | test_that("reading 1D numeric arrays works", { 53 | array_1d <- read_h5ad_dense_array(file, "obs/Int") 54 | expect_equal(array_1d, array(0L:49L)) 55 | 56 | array_1d <- read_h5ad_dense_array(file, "obs/Float") 57 | expect_equal(array_1d, array(rep(42.42, 50))) 58 | }) 59 | 60 | test_that("reading 1D sparse numeric arrays works", { 61 | array_1d <- read_h5ad_sparse_array(file, "uns/Sparse1D", type = "csc") 62 | expect_s4_class(array_1d, "dgCMatrix") 63 | expect_equal(dim(array_1d), c(1, 6)) 64 | }) 65 | 66 | test_that("reading 1D nullable arrays works", { 67 | array_1d <- read_h5ad_nullable_integer(file, "obs/IntNA") 68 | expect_vector(array_1d, ptype = integer(), size = 50) 69 | expect_true(any(is.na(array_1d))) 70 | 71 | array_1d <- read_h5ad_dense_array(file, "obs/FloatNA") 72 | expected <- array(rep(42.42, 50)) 73 | expected[1] <- NA 74 | expect_equal(array_1d, expected) 75 | 76 | array_1d <- read_h5ad_nullable_boolean(file, "obs/Bool") 77 | expect_vector(array_1d, ptype = logical(), size = 50) 78 | expect_false(any(is.na(array_1d))) 79 | 80 | array_1d <- read_h5ad_nullable_boolean(file, "obs/BoolNA") 81 | expect_vector(array_1d, ptype = logical(), size = 50) 82 | expect_true(any(is.na(array_1d))) 83 | }) 84 | 85 | test_that("reading string scalars works", { 86 | scalar <- read_h5ad_string_scalar(file, "uns/StringScalar") 87 | expect_equal(scalar, "A string") 88 | }) 89 | 90 | test_that("reading numeric scalars works", { 91 | scalar <- read_h5ad_numeric_scalar(file, "uns/IntScalar") 92 | expect_equal(scalar, 1) 93 | }) 94 | 95 | test_that("reading string arrays works", { 96 | array <- read_h5ad_string_array(file, "uns/String") 97 | expect_equal(array, array(paste0("String ", 0L:9L))) 98 | 99 | array <- read_h5ad_string_array(file, "uns/String2D") 100 | expect_true(is.matrix(array)) 101 | expect_type(array, "character") 102 | expect_equal(dim(array), c(5, 10)) 103 | }) 104 | 105 | test_that("reading mappings works", { 106 | mapping <- read_h5ad_mapping(file, "uns") 107 | expect_type(mapping, "list") 108 | expect_type(names(mapping), "character") 109 | }) 110 | 111 | test_that("reading dataframes works", { 112 | df <- read_h5ad_data_frame(file, "obs") 113 | expect_s3_class(df, "data.frame") 114 | expect_equal( 115 | colnames(df), 116 | c( 117 | "Float", 118 | "FloatNA", 119 | "Int", 120 | "IntNA", 121 | "Bool", 122 | "BoolNA", 123 | "n_genes_by_counts", 124 | "log1p_n_genes_by_counts", 125 | "total_counts", 126 | "log1p_total_counts", 127 | "leiden" 128 | ) 129 | ) 130 | }) 131 | 132 | file$close_all() 133 | 134 | test_that("reading H5AD as SingleCellExperiment works", { 135 | skip_if_not_installed("SingleCellExperiment") 136 | 137 | sce <- read_h5ad(filename, as = "SingleCellExperiment") 138 | expect_s4_class(sce, "SingleCellExperiment") 139 | }) 140 | 141 | test_that("reading H5AD as Seurat works", { 142 | skip_if_not_installed("Seurat") 143 | 144 | seurat <- read_h5ad(filename, as = "Seurat") 145 | expect_s4_class(seurat, "Seurat") 146 | }) 147 | 148 | test_that("deprecated to argument in read_h5ad() works", { 149 | expect_warning(mem_ad <- read_h5ad(filename, to = "InMemoryAnnData")) 150 | expect_true(inherits(mem_ad, "InMemoryAnnData")) 151 | }) 152 | -------------------------------------------------------------------------------- /tests/testthat/test-HDF5AnnData.R: -------------------------------------------------------------------------------- 1 | skip_if_not_installed("hdf5r") 2 | 3 | requireNamespace("vctrs") 4 | 5 | file <- system.file("extdata", "example.h5ad", package = "anndataR") 6 | 7 | test_that("opening H5AD works", { 8 | adata <- HDF5AnnData$new(file) 9 | expect_true(inherits(adata, "HDF5AnnData")) 10 | adata$close() 11 | }) 12 | 13 | adata <- HDF5AnnData$new(file) 14 | 15 | # GETTERS ---------------------------------------------------------------- 16 | # trackstatus: class=HDF5AnnData, feature=test_get_X, status=done 17 | test_that("reading X works", { 18 | X <- adata$X 19 | expect_s4_class(X, "dgRMatrix") 20 | expect_equal(dim(X), c(50, 100)) 21 | }) 22 | 23 | # trackstatus: class=HDF5AnnData, feature=test_get_layers, status=done 24 | test_that("reading layers works", { 25 | layers <- adata$layers 26 | expect_true(is.list(layers), "list") 27 | expect_equal( 28 | names(layers), 29 | c("counts", "csc_counts", "dense_X", "dense_counts") 30 | ) 31 | }) 32 | 33 | # trackstatus: class=HDF5AnnData, feature=test_get_obsm, status=done 34 | test_that("reading obsm works", { 35 | obsm <- adata$obsm 36 | expect_true(is.list(obsm), "list") 37 | expect_equal( 38 | names(obsm), 39 | c("X_pca", "X_umap") 40 | ) 41 | }) 42 | 43 | # trackstatus: class=HDF5AnnData, feature=test_get_varm, status=done 44 | test_that("reading varm works", { 45 | varm <- adata$varm 46 | expect_true(is.list(varm), "list") 47 | expect_equal( 48 | names(varm), 49 | c("PCs") 50 | ) 51 | }) 52 | 53 | test_that("obsm/varm validation works", { 54 | N_OBS <- 5 55 | N_VAR <- 3 56 | 57 | mtx <- matrix( 58 | 0, 59 | N_OBS, 60 | N_VAR 61 | ) 62 | 63 | adata <- AnnData( 64 | X = mtx, 65 | obs = data.frame(row.names = as.character(1:N_OBS)), 66 | var = data.frame(row.names = as.character(1:N_VAR)) 67 | ) 68 | 69 | adata$obsm <- list(PCA = matrix(0, N_OBS, 4)) 70 | adata$varm <- list(PCs = matrix(0, N_VAR, 4)) 71 | 72 | expect_error(adata$obsm <- list(PCA = matrix(0, 4, 4))) 73 | expect_error(adata$varm <- list(PCs = matrix(0, 4, 4))) 74 | }) 75 | 76 | test_that("obsp/varp validation works", { 77 | N_OBS <- 5 78 | N_VAR <- 3 79 | 80 | adata <- AnnData( 81 | obs = data.frame(row.names = as.character(1:N_OBS)), 82 | var = data.frame(row.names = as.character(1:N_VAR)) 83 | ) 84 | 85 | adata$obsp <- list(graph1 = matrix(0, N_OBS, N_OBS)) 86 | adata$varp <- list(graph1 = matrix(0, N_VAR, N_VAR)) 87 | 88 | expect_error(adata$obsp <- list(graph1 = matrix(0, 4, 4))) 89 | expect_error(adata$varp <- list(graph1 = matrix(0, 4, 4))) 90 | }) 91 | 92 | 93 | # trackstatus: class=HDF5AnnData, feature=test_get_obs, status=done 94 | test_that("reading obs works", { 95 | obs <- adata$obs 96 | expect_s3_class(obs, "data.frame") 97 | expect_equal( 98 | colnames(obs), 99 | c( 100 | "Float", 101 | "FloatNA", 102 | "Int", 103 | "IntNA", 104 | "Bool", 105 | "BoolNA", 106 | "n_genes_by_counts", 107 | "log1p_n_genes_by_counts", 108 | "total_counts", 109 | "log1p_total_counts", 110 | "leiden" 111 | ) 112 | ) 113 | }) 114 | 115 | 116 | # trackstatus: class=HDF5AnnData, feature=test_get_var, status=done 117 | test_that("reading var works", { 118 | var <- adata$var 119 | expect_s3_class(var, "data.frame") 120 | expect_equal( 121 | colnames(var), 122 | c( 123 | "String", 124 | "n_cells_by_counts", 125 | "mean_counts", 126 | "log1p_mean_counts", 127 | "pct_dropout_by_counts", 128 | "total_counts", 129 | "log1p_total_counts", 130 | "highly_variable", 131 | "means", 132 | "dispersions", 133 | "dispersions_norm" 134 | ) 135 | ) 136 | }) 137 | 138 | # trackstatus: class=HDF5AnnData, feature=test_get_obs_names, status=done 139 | test_that("reading obs names works", { 140 | obs_names <- adata$obs_names 141 | expect_vector(obs_names, ptype = character(), size = 50) 142 | }) 143 | 144 | # trackstatus: class=HDF5AnnData, feature=test_get_var_names, status=done 145 | test_that("reading var names works", { 146 | var_names <- adata$var_names 147 | expect_vector(var_names, ptype = character(), size = 100) 148 | }) 149 | 150 | # SETTERS ---------------------------------------------------------------- 151 | test_that("creating empty H5AD works", { 152 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 153 | expect_silent(HDF5AnnData$new(file = h5ad_file, mode = "w-")) 154 | }) 155 | 156 | # trackstatus: class=HDF5AnnData, feature=test_set_X, status=done 157 | test_that("writing X works", { 158 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 159 | obs <- data.frame(row.names = 1:10) 160 | var <- data.frame(row.names = 1:20) 161 | h5ad <- HDF5AnnData$new(h5ad_file, obs = obs, var = var, mode = "w-") 162 | 163 | X <- matrix(rnorm(10 * 20), nrow = 10, ncol = 20) 164 | expect_silent(h5ad$X <- X) 165 | }) 166 | 167 | # trackstatus: class=HDF5AnnData, feature=test_set_layers, status=done 168 | test_that("writing layers works", { 169 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 170 | obs <- data.frame(row.names = 1:10) 171 | var <- data.frame(row.names = 1:20) 172 | h5ad <- HDF5AnnData$new(h5ad_file, obs = obs, var = var, mode = "w-") 173 | 174 | X <- matrix(rnorm(10 * 20), nrow = 10, ncol = 20) 175 | expect_silent(h5ad$layers <- list(layer1 = X, layer2 = X)) 176 | }) 177 | 178 | # trackstatus: class=HDF5AnnData, feature=test_set_obs, status=done 179 | test_that("writing obs works", { 180 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 181 | obs <- data.frame(row.names = 1:10) 182 | var <- data.frame(row.names = 1:20) 183 | h5ad <- HDF5AnnData$new(h5ad_file, obs = obs, var = var, mode = "w-") 184 | 185 | obs <- data.frame( 186 | Letters = LETTERS[1:10], 187 | Numbers = 1:10, 188 | row.names = paste0("Row", 1:10) 189 | ) 190 | h5ad$obs <- obs 191 | expect_identical(h5ad$obs_names, paste0("Row", 1:10)) 192 | }) 193 | 194 | # trackstatus: class=HDF5AnnData, feature=test_set_var, status=done 195 | test_that("writing var works", { 196 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 197 | obs <- data.frame(row.names = 1:10) 198 | var <- data.frame(row.names = 1:20) 199 | h5ad <- HDF5AnnData$new(h5ad_file, obs = obs, var = var, mode = "w-") 200 | 201 | var <- data.frame( 202 | Letters = LETTERS[1:20], 203 | Numbers = 1:20, 204 | row.names = paste0("Row", 1:20) 205 | ) 206 | h5ad$var <- var 207 | expect_identical(h5ad$var_names, paste0("Row", 1:20)) 208 | }) 209 | 210 | # trackstatus: class=HDF5AnnData, feature=test_set_obs_names, status=done 211 | test_that("writing obs names works", { 212 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 213 | obs <- data.frame(row.names = 1:10) 214 | var <- data.frame(row.names = 1:20) 215 | h5ad <- HDF5AnnData$new(h5ad_file, obs = obs, var = var, mode = "w-") 216 | 217 | h5ad$obs_names <- LETTERS[1:10] 218 | expect_identical(h5ad$obs_names, LETTERS[1:10]) 219 | }) 220 | 221 | # trackstatus: class=HDF5AnnData, feature=test_set_var_names, status=done 222 | test_that("writing var names works", { 223 | h5ad_file <- withr::local_tempfile(fileext = ".h5ad") 224 | obs <- data.frame(row.names = 1:10) 225 | var <- data.frame(row.names = 1:20) 226 | h5ad <- HDF5AnnData$new(h5ad_file, obs = obs, var = var, mode = "w-") 227 | 228 | h5ad$var_names <- LETTERS[1:20] 229 | expect_identical(h5ad$var_names, LETTERS[1:20]) 230 | }) 231 | 232 | test_that("deprecated to_InMemoryAnnData() works", { 233 | expect_warning(mem_ad <- adata$to_InMemoryAnnData()) 234 | expect_true(inherits(mem_ad, "InMemoryAnnData")) 235 | }) 236 | -------------------------------------------------------------------------------- /tests/testthat/test-InMemoryAnnData.R: -------------------------------------------------------------------------------- 1 | dummy <- generate_dataset(10L, 20L) 2 | 3 | # GETTERS ---------------------------------------------------------------- 4 | test_that("Creating InMemoryAnnData works", { 5 | ad <- AnnData( 6 | X = dummy$X, 7 | obs = dummy$obs, 8 | var = dummy$var 9 | ) 10 | 11 | # trackstatus: class=InMemoryAnnData, feature=test_get_X, status=done 12 | expect_equal(ad$X, dummy$X) 13 | # trackstatus: class=InMemoryAnnData, feature=test_get_obs, status=done 14 | expect_equal(ad$obs, dummy$obs) 15 | # trackstatus: class=InMemoryAnnData, feature=test_get_var, status=done 16 | expect_equal(ad$var, dummy$var) 17 | # trackstatus: class=InMemoryAnnData, feature=test_get_obs_names, status=done 18 | expect_equal(ad$obs_names, rownames(dummy$obs)) 19 | # trackstatus: class=InMemoryAnnData, feature=test_get_var_names, status=done 20 | expect_equal(ad$var_names, rownames(dummy$var)) 21 | expect_identical(ad$shape(), c(10L, 20L)) 22 | }) 23 | 24 | test_that("Creating InMemoryAnnData works with empty obs", { 25 | ad <- AnnData( 26 | obs = data.frame(), 27 | var = dummy$var 28 | ) 29 | expect_identical(ad$shape(), c(0L, 20L)) 30 | }) 31 | 32 | test_that("Creating InMemoryAnnData works with empty var", { 33 | ad <- AnnData( 34 | obs = dummy$obs, 35 | var = data.frame() 36 | ) 37 | expect_identical(ad$shape(), c(10L, 0L)) 38 | }) 39 | 40 | test_that("Creating AnnData works with only X, no obs or var", { 41 | X <- dummy$X 42 | dimnames(X) <- list( 43 | rownames(dummy$obs), 44 | rownames(dummy$var) 45 | ) 46 | ad <- AnnData(X = X) 47 | expect_identical(ad$shape(), c(10L, 20L)) 48 | expect_identical(ad$obs_names, rownames(dummy$obs)) 49 | expect_identical(ad$var_names, rownames(dummy$var)) 50 | }) 51 | 52 | # trackstatus: class=InMemoryAnnData, feature=test_get_layers, status=done 53 | test_that("'layers' works", { 54 | ## layers test helper function 55 | layers_test <- function(obs, var, layers) { 56 | expect_no_condition({ 57 | ad <- AnnData(obs = obs, var = var, layers = layers) 58 | }) 59 | expect_identical(ad$layers, layers) 60 | } 61 | 62 | obs <- var <- data.frame() 63 | layers_test(obs, var, NULL) 64 | layers_test(obs, var, setNames(list(), character())) 65 | layers_test(obs, var, list(A = matrix(0, 0, 0))) 66 | ## must be a named list 67 | expect_error(AnnData(obs, var, list())) 68 | 69 | obs <- data.frame(row.names = letters[1:3]) 70 | var <- data.frame(row.names = LETTERS[1:5]) 71 | layers_test(obs, var, NULL) 72 | layers_test(obs, var, list(A = matrix(0, 3, 5))) 73 | layers_test(obs, var, list(A = matrix(0, 3, 5), B = matrix(1, 3, 5))) 74 | 75 | ## must be a named list 76 | layers <- list(matrix(0, 3, 5)) 77 | expect_error(AnnData(obs = obs, var = var, layers = layers)) 78 | ## matching dimensions 79 | layers <- list(A = matrix(0, 0, 0)) 80 | expect_error(AnnData(obs = obs, var = var, layers = layers)) 81 | layers <- list(A = matrix(0, 3, 5), B = matrix(1, 5, 3)) 82 | expect_error(AnnData(obs = obs, var = var, layers = layers)) 83 | }) 84 | 85 | test_that("*_keys() works", { 86 | obs <- var <- data.frame() 87 | ad <- AnnData(obs = obs, var = var) 88 | expect_identical(ad$layers_keys(), NULL) 89 | expect_identical(ad$obs_keys(), character()) 90 | expect_identical(ad$var_keys(), character()) 91 | 92 | layers <- setNames(list(), character()) 93 | ad <- AnnData(obs = obs, var = var, layers = layers) 94 | expect_identical(ad$layers_keys(), character()) 95 | 96 | layers <- list(A = matrix(0, 3, 5), B = matrix(1, 3, 5)) 97 | obs <- data.frame(row.names = letters[1:3], x = 1:3) 98 | var <- data.frame(row.names = letters[1:5], y = 1:5) 99 | ad <- AnnData(obs = obs, var = var, layers = layers) 100 | expect_identical(ad$layers_keys(), c("A", "B")) 101 | expect_identical(ad$obs_keys(), "x") 102 | expect_identical(ad$var_keys(), "y") 103 | }) 104 | 105 | # SETTERS ----------------------------------------------------------------- 106 | 107 | # trackstatus: class=InMemoryAnnData, feature=test_set_X, status=done 108 | test_that("write to X", { 109 | ad <- AnnData( 110 | X = dummy$X, 111 | obs = dummy$obs, 112 | var = dummy$var 113 | ) 114 | 115 | X2 <- Matrix::rsparsematrix(nrow = 10, ncol = 20, density = .1) 116 | ad$X <- X2 117 | 118 | expect_equal(ad$X, X2) 119 | 120 | # change row in X 121 | ad$X[2, ] <- 10 122 | expect_equal(ad$X[2, ], rep(10, 20L)) 123 | 124 | # change column in X 125 | ad$X[, 3] <- 5 126 | expect_equal(ad$X[, 3], rep(5, 10L)) 127 | }) 128 | 129 | # trackstatus: class=InMemoryAnnData, feature=test_set_obs, status=done 130 | test_that("write to obs", { 131 | ad <- AnnData( 132 | X = dummy$X, 133 | obs = dummy$obs, 134 | var = dummy$var 135 | ) 136 | 137 | obs2 <- data.frame( 138 | foo = sample(letters, 10, replace = TRUE), 139 | bar = sample.int(4, 10, replace = TRUE), 140 | zing = sample(c(TRUE, FALSE), 10, replace = TRUE) 141 | ) 142 | ad$obs <- obs2 143 | 144 | expect_equal(ncol(ad$obs), 3) 145 | expect_equal(nrow(ad$obs), 10) 146 | expect_equal(ad$obs, obs2) 147 | 148 | # change row in obs 149 | obs2row <- data.frame(foo = "a", bar = 3, zing = FALSE) 150 | ad$obs[2, ] <- obs2row 151 | expect_equal(ad$obs[2, ], obs2row, ignore_attr = TRUE) 152 | 153 | # change column in obs 154 | ad$obs[, 3] <- FALSE 155 | expect_equal(ad$obs[, 3], rep(FALSE, 10L)) 156 | }) 157 | 158 | # trackstatus: class=InMemoryAnnData, feature=test_set_var, status=done 159 | test_that("write to var", { 160 | ad <- AnnData( 161 | X = dummy$X, 162 | obs = dummy$obs, 163 | var = dummy$var 164 | ) 165 | 166 | var2 <- data.frame( 167 | foo = sample(letters, 20L, replace = TRUE), 168 | bar = sample.int(4, 20L, replace = TRUE), 169 | zing = sample(c(TRUE, FALSE), 20L, replace = TRUE) 170 | ) 171 | ad$var <- var2 172 | 173 | expect_equal(ncol(ad$var), 3) 174 | expect_equal(nrow(ad$var), 20) 175 | expect_equal(ad$var, var2) 176 | 177 | # change row in var 178 | var2row <- data.frame(foo = "a", bar = 3, zing = FALSE) 179 | ad$var[2, ] <- var2row 180 | expect_equal(ad$var[2, ], var2row, ignore_attr = TRUE) 181 | 182 | # change column in var 183 | ad$var[, 3] <- FALSE 184 | expect_equal(ad$var[, 3], rep(FALSE, 20L)) 185 | }) 186 | 187 | # trackstatus: class=InMemoryAnnData, feature=test_set_obs_names, status=done 188 | test_that("write to obs_names", { 189 | ad <- AnnData( 190 | X = dummy$X, 191 | obs = dummy$obs, 192 | var = dummy$var 193 | ) 194 | 195 | obs_names2 <- letters[1:10] 196 | ad$obs_names <- obs_names2 197 | 198 | expect_equal(ad$obs_names, obs_names2) 199 | 200 | # change value in obs_names 201 | ad$obs_names[2:3] <- c("foo", "bar") 202 | expect_equal(ad$obs_names[1:4], c("a", "foo", "bar", "d")) 203 | }) 204 | 205 | # trackstatus: class=InMemoryAnnData, feature=test_set_var_names, status=done 206 | test_that("write to var_names", { 207 | ad <- AnnData( 208 | X = dummy$X, 209 | obs = dummy$obs, 210 | var = dummy$var 211 | ) 212 | 213 | var_names2 <- LETTERS[1:20] 214 | ad$var_names <- var_names2 215 | 216 | expect_equal(ad$var_names, var_names2) 217 | 218 | # change value in var_names 219 | ad$var_names[2:3] <- c("foo", "bar") 220 | expect_equal(ad$var_names[1:4], c("A", "foo", "bar", "D")) 221 | }) 222 | 223 | # trackstatus: class=InMemoryAnnData, feature=test_set_layers, status=done 224 | test_that("write to layers", { 225 | ad <- AnnData( 226 | X = dummy$X, 227 | obs = dummy$obs, 228 | var = dummy$var, 229 | layers = dummy$layers 230 | ) 231 | 232 | ## element assignment 233 | ad$layers[["X2"]] <- dummy$X + 2 234 | expect_equal(ad$layers[["X2"]], dummy$X + 2) 235 | 236 | ad$layers[["X4"]] <- dummy$X + 4 237 | expect_equal(ad$layers[["X4"]], dummy$X + 4) 238 | 239 | ## list assignment 240 | ad$layers <- rev(dummy$layers) 241 | expect_equal(ad$layers, rev(dummy$layers)) 242 | 243 | ## remove 244 | ad$layers <- NULL 245 | expect_true(is.null(ad$layers)) 246 | ## add to NULL 247 | ad$layers <- dummy$layers 248 | expect_identical(ad$layers, dummy$layers) 249 | }) 250 | 251 | test_that("deprecated to_HDF5AnnData() works", { 252 | ad <- AnnData( 253 | X = dummy$X, 254 | obs = dummy$obs, 255 | var = dummy$var, 256 | layers = dummy$layers 257 | ) 258 | expect_warning(h5_ad <- ad$to_HDF5AnnData(file = tempfile())) 259 | expect_true(inherits(h5_ad, "HDF5AnnData")) 260 | }) 261 | -------------------------------------------------------------------------------- /tests/testthat/test-as_Seurat.R: -------------------------------------------------------------------------------- 1 | test_that("as_Seurat() fails gracefully", { 2 | expect_error(as_Seurat(), regexp = "adata.*is missing") 3 | expect_error(as_Seurat("foo"), regexp = "must be a ") 4 | }) 5 | 6 | skip_if_not_installed("Seurat") 7 | library(Seurat) 8 | 9 | known_issues <- read_known_issues() 10 | 11 | ad <- generate_dataset(n_obs = 10L, n_vars = 20L, format = "AnnData") 12 | ad$obsm[["X_pca"]] <- matrix(1:50, 10, 5) 13 | ad$varm[["PCs"]] <- matrix(1:100, 20, 5) 14 | 15 | layers_mapping <- c(NA, names(ad$layers)) 16 | names(layers_mapping) <- c("counts", names(ad$layers)) 17 | seu <- ad$as_Seurat(layers_mapping = layers_mapping) 18 | 19 | test_that("as_Seurat retains number of observations and features", { 20 | expect_equal(nrow(seu), 20) 21 | expect_equal(ncol(seu), 10) 22 | 23 | # trackstatus: class=Seurat, feature=test_get_var_names, status=done 24 | expect_equal(rownames(seu), rownames(ad$var)) 25 | # trackstatus: class=Seurat, feature=test_get_obs_names, status=done 26 | expect_equal(colnames(seu), rownames(ad$obs)) 27 | }) 28 | 29 | # trackstatus: class=Seurat, feature=test_get_obs, status=done 30 | for (obs_key in colnames(ad$obs)) { 31 | test_that(paste0("as_Seurat retains obs key: ", obs_key), { 32 | msg <- message_if_known( 33 | backend = "to_Seurat", 34 | slot = c("obs"), 35 | dtype = obs_key, 36 | process = "convert", 37 | known_issues = known_issues 38 | ) 39 | skip_if(!is.null(msg), message = msg) 40 | 41 | expect_true(obs_key %in% colnames(seu@meta.data)) 42 | expect_equal( 43 | seu@meta.data[[obs_key]], 44 | ad$obs[[obs_key]], 45 | info = paste0("obs_key: ", obs_key) 46 | ) 47 | }) 48 | } 49 | 50 | # trackstatus: class=Seurat, feature=test_get_var, status=done 51 | for (var_key in colnames(ad$var)) { 52 | test_that(paste0("as_Seurat retains var key: ", var_key), { 53 | msg <- message_if_known( 54 | backend = "to_Seurat", 55 | slot = c("var"), 56 | dtype = var_key, 57 | process = "convert", 58 | known_issues = known_issues 59 | ) 60 | skip_if(!is.null(msg), message = msg) 61 | 62 | active_assay <- seu[[DefaultAssay(seu)]] 63 | expect_true(var_key %in% colnames(active_assay@meta.data)) 64 | expect_equal(active_assay@meta.data[[var_key]], ad$var[[var_key]]) 65 | }) 66 | } 67 | 68 | 69 | # trackstatus: class=Seurat, feature=test_get_layers, status=done 70 | for (layer_key in names(ad$layers)) { 71 | test_that(paste0("as_Seurat retains layer: ", layer_key), { 72 | msg <- message_if_known( 73 | backend = "to_Seurat", 74 | slot = c("layers"), 75 | dtype = layer_key, 76 | process = "convert", 77 | known_issues = known_issues 78 | ) 79 | skip_if(!is.null(msg), message = msg) 80 | 81 | active_assay <- seu@assays[[seu@active.assay]] 82 | 83 | expect_true(layer_key %in% names(active_assay@layers)) 84 | expect_true( 85 | all.equal( 86 | as.matrix(t(active_assay@layers[[layer_key]])), 87 | as.matrix(ad$layers[[layer_key]]), 88 | check.attributes = FALSE 89 | ), 90 | info = paste0("layer_key: ", layer_key) 91 | ) 92 | }) 93 | } 94 | 95 | # trackstatus: class=Seurat, feature=test_get_uns, status=done 96 | for (uns_key in names(ad$uns)) { 97 | test_that(paste0("as_Seurat retains uns key: ", uns_key), { 98 | msg <- message_if_known( 99 | backend = "to_Seurat", 100 | slot = c("uns"), 101 | dtype = uns_key, 102 | process = "convert", 103 | known_issues = known_issues 104 | ) 105 | skip_if(!is.null(msg), message = msg) 106 | 107 | expect_true(uns_key %in% names(seu@misc)) 108 | expect_equal(seu@misc[[uns_key]], ad$uns[[uns_key]], ignore_attr = TRUE) 109 | }) 110 | } 111 | 112 | test_that("as_Seurat retains pca dimred", { 113 | msg <- message_if_known( 114 | backend = "to_Seurat", 115 | slot = c("obsm"), 116 | dtype = "pca", 117 | process = "convert", 118 | known_issues = known_issues 119 | ) 120 | 121 | skip_if(!is.null(msg), message = msg) 122 | 123 | # trackstatus: class=Seurat, feature=test_get_obsm, status=wip 124 | expect_true("X_pca" %in% names(seu@reductions)) 125 | expect_equal( 126 | Embeddings(seu, reduction = "X_pca"), 127 | ad$obsm[["X_pca"]], 128 | ignore_attr = TRUE 129 | ) 130 | 131 | # trackstatus: class=Seurat, feature=test_get_varm, status=wip 132 | expect_equal( 133 | Loadings(seu, reduction = "X_pca"), 134 | ad$varm[["PCs"]], 135 | ignore_attr = TRUE 136 | ) 137 | }) 138 | 139 | test_that("as_Seurat works with list mappings", { 140 | expect_no_error( 141 | ad$as_Seurat( 142 | x_mapping = "counts", 143 | object_metadata_mapping = as.list(.as_Seurat_guess_object_metadata(ad)), 144 | layers_mapping = as.list(.as_Seurat_guess_layers(ad)), 145 | assay_metadata_mapping = as.list(.as_Seurat_guess_assay_metadata(ad)), 146 | reduction_mapping = as.list(.as_Seurat_guess_reductions(ad)), 147 | graph_mapping = as.list(.as_Seurat_guess_graphs(ad)), 148 | misc_mapping = as.list(.as_Seurat_guess_misc(ad)) 149 | ) 150 | ) 151 | 152 | expect_error( 153 | ad$as_Seurat( 154 | reduction_mapping = list(numeric = "numeric_matrix") 155 | ) 156 | ) 157 | }) 158 | 159 | test_that("as_Seurat works with a vector reduction_mapping", { 160 | expect_no_error( 161 | ad$as_Seurat( 162 | x_mapping = "counts", 163 | reduction_mapping = c(numeric = "numeric_matrix") 164 | ) 165 | ) 166 | }) 167 | 168 | test_that("as_Seurat works with unnamed mappings", { 169 | expect_no_error( 170 | ad$as_Seurat( 171 | object_metadata_mapping = unname(.as_Seurat_guess_object_metadata(ad)), 172 | layers_mapping = c( 173 | na.omit(unname(.as_Seurat_guess_layers(ad))), 174 | counts = NA 175 | ), 176 | assay_metadata_mapping = unname(.as_Seurat_guess_assay_metadata(ad)), 177 | graph_mapping = unname(.as_Seurat_guess_graphs(ad)), 178 | misc_mapping = unname(.as_Seurat_guess_misc(ad)) 179 | ) 180 | ) 181 | }) 182 | 183 | test_that("deprecated to_Seurat() works", { 184 | expect_warning(seu <- ad$to_Seurat(x_mapping = "counts")) 185 | expect_s4_class(seu, "Seurat") 186 | }) 187 | -------------------------------------------------------------------------------- /tests/testthat/test-as_SingleCellExperiment.R: -------------------------------------------------------------------------------- 1 | skip_if_not_installed("SingleCellExperiment") 2 | library(SingleCellExperiment) 3 | 4 | known_issues <- read_known_issues() 5 | 6 | ad <- generate_dataset(n_obs = 10L, n_vars = 20L, format = "AnnData") 7 | ad$obsm[["X_pca"]] <- matrix(1:50, 10, 5) 8 | ad$varm[["PCs"]] <- matrix(1:100, 20, 5) 9 | 10 | sce <- ad$as_SingleCellExperiment() 11 | 12 | test_that("as_SCE retains nr of observations and features", { 13 | expect_equal(nrow(sce), 20) 14 | expect_equal(ncol(sce), 10) 15 | 16 | # trackstatus: class=SingleCellExperiment, feature=test_get_var_names, status=done 17 | expect_equal(rownames(sce), rownames(ad$var)) 18 | # trackstatus: class=SingleCellExperiment, feature=test_get_obs_names, status=done 19 | expect_equal(colnames(sce), rownames(ad$obs)) 20 | }) 21 | 22 | # trackstatus: class=SingleCellExperiment, feature=test_get_obs, status=done 23 | for (obs_key in colnames(ad$obs)) { 24 | test_that(paste0("as_SCE retains obs key: ", obs_key), { 25 | msg <- message_if_known( 26 | backend = "to_SCE", 27 | slot = c("obs"), 28 | dtype = obs_key, 29 | process = "convert", 30 | known_issues = known_issues 31 | ) 32 | skip_if(!is.null(msg), message = msg) 33 | 34 | expect_true(obs_key %in% colnames(colData(sce))) 35 | expect_equal( 36 | colData(sce)[[obs_key]], 37 | ad$obs[[obs_key]], 38 | info = paste0("obs_key: ", obs_key) 39 | ) 40 | }) 41 | } 42 | 43 | # trackstatus: class=SingleCellExperiment, feature=test_get_var, status=done 44 | for (var_key in colnames(ad$var)) { 45 | test_that(paste0("as_SCE retains var key: ", var_key), { 46 | msg <- message_if_known( 47 | backend = "to_SCE", 48 | slot = c("var"), 49 | dtype = var_key, 50 | process = "convert", 51 | known_issues = known_issues 52 | ) 53 | skip_if(!is.null(msg), message = msg) 54 | 55 | expect_true(var_key %in% colnames(rowData(sce))) 56 | expect_equal( 57 | rowData(sce)[[var_key]], 58 | ad$var[[var_key]], 59 | info = paste0("var_key: ", var_key) 60 | ) 61 | }) 62 | } 63 | 64 | # trackstatus: class=SingleCellExperiment, feature=test_get_layers, status=done 65 | for (layer_key in names(ad$layers)) { 66 | test_that(paste0("as_SCE retains layer: ", layer_key), { 67 | msg <- message_if_known( 68 | backend = "to_SCE", 69 | slot = c("layers"), 70 | dtype = layer_key, 71 | process = "convert", 72 | known_issues = known_issues 73 | ) 74 | skip_if(!is.null(msg), message = msg) 75 | 76 | expect_true(layer_key %in% assayNames(sce)) 77 | expect_true( 78 | all.equal( 79 | as.matrix(t(assay(sce, layer_key))), 80 | as.matrix(ad$layers[[layer_key]]), 81 | check.attributes = FALSE 82 | ), 83 | info = paste0("layer_key: ", layer_key) 84 | ) 85 | }) 86 | } 87 | 88 | test_that("as_SCE fails when providing duplicate assay names", { 89 | expect_error( 90 | ad$as_SingleCellExperiment( 91 | x_mapping = "counts", 92 | assays_mapping = c(counts = "numeric_matrix", integer = "integer_matrix") 93 | ), 94 | regexp = "duplicate names" 95 | ) 96 | }) 97 | 98 | test_that("as_SCE works when only providing x_mapping", { 99 | sce <- ad$as_SingleCellExperiment(x_mapping = "counts") 100 | assay_names <- assayNames(sce) 101 | expect_true("counts" %in% assay_names) 102 | expect_true(all(ad$layers_keys() %in% assay_names)) 103 | }) 104 | 105 | test_that("as_SCE works with assays_mapping and x_mapping", { 106 | sce <- ad$as_SingleCellExperiment( 107 | x_mapping = "counts", 108 | assays_mapping = c(data = "numeric_matrix", integer = "integer_matrix") 109 | ) 110 | assay_names <- assayNames(sce) 111 | expect_true("counts" %in% assay_names) 112 | expect_true("data" %in% assay_names) 113 | expect_true("integer" %in% assay_names) 114 | }) 115 | 116 | test_that("as_SCE works with no x_mapping and no layers_mapping", { 117 | sce <- ad$as_SingleCellExperiment() 118 | assay_names <- assayNames(sce) 119 | expect_true("X" %in% assay_names) 120 | expect_true(all(ad$layers_keys() %in% assay_names)) 121 | }) 122 | 123 | # trackstatus: class=SingleCellExperiment, feature=test_get_obsp, status=done 124 | for (obsp_key in names(ad$obsp)) { 125 | test_that(paste0("as_SCE retains obsp key: ", obsp_key), { 126 | msg <- message_if_known( 127 | backend = "to_SCE", 128 | slot = c("obsp"), 129 | dtype = obsp_key, 130 | process = "convert", 131 | known_issues = known_issues 132 | ) 133 | skip_if(!is.null(msg), message = msg) 134 | 135 | expect_true(obsp_key %in% names(colPairs(sce))) 136 | 137 | sce_matrix <- as.matrix(colPair(sce, obsp_key, asSparse = TRUE)) 138 | ad_matrix <- as.matrix(ad$obsp[[obsp_key]]) 139 | 140 | expect_equal(sce_matrix, ad_matrix, info = paste0("obsp_key: ", obsp_key)) 141 | }) 142 | } 143 | 144 | # trackstatus: class=SingleCellExperiment, feature=test_get_varp, status=done 145 | for (varp_key in names(ad$varp)) { 146 | test_that(paste0("as_SCE retains varp key: ", varp_key), { 147 | msg <- message_if_known( 148 | backend = "to_SCE", 149 | slot = c("obsp"), 150 | dtype = varp_key, 151 | process = "convert", 152 | known_issues = known_issues 153 | ) 154 | skip_if(!is.null(msg), message = msg) 155 | 156 | expect_true(varp_key %in% names(rowPairs(sce))) 157 | 158 | sce_matrix <- as.matrix(rowPair(sce, varp_key, asSparse = TRUE)) 159 | ad_matrix <- as.matrix(ad$varp[[varp_key]]) 160 | 161 | expect_equal(sce_matrix, ad_matrix, info = paste0("varp_key: ", varp_key)) 162 | }) 163 | } 164 | 165 | # trackstatus: class=SingleCellExperiment, feature=test_get_uns, status=done 166 | for (uns_key in names(ad$uns)) { 167 | test_that(paste0("as_SCE retains uns key: ", uns_key), { 168 | msg <- message_if_known( 169 | backend = "to_SCE", 170 | slot = c("uns"), 171 | dtype = uns_key, 172 | process = "convert", 173 | known_issues = known_issues 174 | ) 175 | skip_if(!is.null(msg), message = msg) 176 | 177 | expect_true(uns_key %in% names(metadata(sce))) 178 | expect_equal( 179 | metadata(sce)[[uns_key]], 180 | ad$uns[[uns_key]], 181 | info = paste0("uns_key: ", uns_key) 182 | ) 183 | }) 184 | } 185 | 186 | test_that("as_SCE retains pca dimred", { 187 | msg <- message_if_known( 188 | backend = "to_SCE", 189 | slot = c("obsm", "varm"), 190 | dtype = "pca", 191 | process = "convert", 192 | known_issues = known_issues 193 | ) 194 | skip_if(!is.null(msg), message = msg) 195 | 196 | # trackstatus: class=SingleCellExperiment, feature=test_get_obsm, status=wip 197 | expect_true("pca" %in% names(reducedDims(sce))) 198 | expect_equal( 199 | sampleFactors(reducedDims(sce)$pca), 200 | ad$obsm[["X_pca"]], 201 | ignore.attributes = TRUE 202 | ) 203 | 204 | # trackstatus: class=SingleCellExperiment, feature=test_get_varm, status=wip 205 | expect_equal( 206 | featureLoadings(reducedDims(sce)$pca), 207 | ad$varm[["PCs"]] 208 | ) 209 | }) 210 | 211 | test_that("as_SCE works with list mappings", { 212 | expect_no_error( 213 | ad$as_SingleCellExperiment( 214 | assays_mapping = as.list(.as_SCE_guess_all(ad, "layers")), 215 | colData_mapping = as.list(.as_SCE_guess_all(ad, "obs")), 216 | rowData_mapping = as.list(.as_SCE_guess_all(ad, "var")), 217 | reducedDims_mapping = as.list(.as_SCE_guess_reducedDims(ad)), 218 | colPairs_mapping = as.list(.as_SCE_guess_all(ad, "obsp")), 219 | rowPairs_mapping = as.list(.as_SCE_guess_all(ad, "varp")), 220 | metadata_mapping = as.list(.as_SCE_guess_all(ad, "uns")) 221 | ) 222 | ) 223 | 224 | expect_error( 225 | ad$as_SingleCellExperiment( 226 | reducedDims_mapping = list(numeric = "numeric_matrix") 227 | ) 228 | ) 229 | }) 230 | 231 | test_that("as_SCE works with a vector reducedDims_mapping", { 232 | expect_no_error( 233 | ad$as_SingleCellExperiment( 234 | reducedDims_mapping = c(numeric = "numeric_matrix") 235 | ) 236 | ) 237 | }) 238 | 239 | test_that("as_SCE works with unnamed mappings", { 240 | expect_no_error( 241 | ad$as_SingleCellExperiment( 242 | assays_mapping = unname(.as_SCE_guess_all(ad, "layers")), 243 | colData_mapping = unname(.as_SCE_guess_all(ad, "obs")), 244 | rowData_mapping = unname(.as_SCE_guess_all(ad, "var")), 245 | colPairs_mapping = unname(.as_SCE_guess_all(ad, "obsp")), 246 | rowPairs_mapping = unname(.as_SCE_guess_all(ad, "varp")), 247 | metadata_mapping = unname(.as_SCE_guess_all(ad, "uns")) 248 | ) 249 | ) 250 | }) 251 | 252 | test_that("deprecated to_SingleCellExperiment() works", { 253 | expect_warning(sce <- ad$to_SingleCellExperiment()) 254 | expect_s4_class(sce, "SingleCellExperiment") 255 | }) 256 | -------------------------------------------------------------------------------- /tests/testthat/test-from_Seurat.R: -------------------------------------------------------------------------------- 1 | skip_if_not_installed("Seurat") 2 | library(Seurat) 3 | 4 | known_issues <- read_known_issues() 5 | 6 | suppressWarnings({ 7 | counts <- matrix(rbinom(20000, 1000, .001), nrow = 100) 8 | obj <- CreateSeuratObject(counts = counts) 9 | obj <- NormalizeData(obj) 10 | obj <- FindVariableFeatures(obj) 11 | obj <- ScaleData(obj) 12 | obj <- RunPCA(obj, npcs = 10L) 13 | obj <- FindNeighbors(obj) 14 | obj <- RunUMAP(obj, dims = 1:10) 15 | }) 16 | 17 | active_assay <- obj@assays[[obj@active.assay]] 18 | 19 | ad <- as_AnnData(obj) 20 | 21 | test_that("as_AnnData (Seurat) retains number of observations and features", { 22 | expect_equal(ad$n_obs(), 200L) 23 | expect_equal(ad$n_vars(), 100L) 24 | 25 | # trackstatus: class=Seurat, feature=test_set_var_names, status=done 26 | expect_equal(rownames(ad$var), rownames(obj)) 27 | # trackstatus: class=Seurat, feature=test_set_obs_names, status=done 28 | expect_equal(rownames(ad$obs), colnames(obj)) 29 | }) 30 | 31 | # trackstatus: class=Seurat, feature=test_set_obs, status=done 32 | for (obs_key in colnames(obj@meta.data)) { 33 | test_that(paste0("as_AnnData (Seurat) retains obs key: ", obs_key), { 34 | msg <- message_if_known( 35 | backend = "from_Seurat", 36 | slot = c("obs"), 37 | dtype = obs_key, 38 | process = "convert", 39 | known_issues = known_issues 40 | ) 41 | skip_if(!is.null(msg), message = msg) 42 | 43 | expect_true(obs_key %in% colnames(ad$obs)) 44 | expect_equal( 45 | ad$obs[[obs_key]], 46 | obj@meta.data[[obs_key]], 47 | info = paste0("obs_key: ", obs_key) 48 | ) 49 | }) 50 | } 51 | 52 | # trackstatus: class=Seurat, feature=test_set_var, status=done 53 | for (var_key in colnames(active_assay@meta.data)) { 54 | test_that(paste0("as_AnnData (Seurat) retains var key: ", var_key), { 55 | msg <- message_if_known( 56 | backend = "from_Seurat", 57 | slot = c("var"), 58 | dtype = var_key, 59 | process = "convert", 60 | known_issues = known_issues 61 | ) 62 | skip_if(!is.null(msg), message = msg) 63 | 64 | expect_true(var_key %in% colnames(ad$var)) 65 | expect_equal(ad$var[[var_key]], active_assay@meta.data[[var_key]]) 66 | }) 67 | } 68 | 69 | # trackstatus: class=Seurat, feature=test_set_layers, status=done 70 | for (layer_key in names(active_assay@layers)) { 71 | test_that(paste0("as_AnnData (Seurat) retains layer: ", layer_key), { 72 | msg <- message_if_known( 73 | backend = "from_Seurat", 74 | slot = c("layers"), 75 | dtype = layer_key, 76 | process = "convert", 77 | known_issues = known_issues 78 | ) 79 | skip_if(!is.null(msg), message = msg) 80 | 81 | expect_true(layer_key %in% names(ad$layers)) 82 | expect_true( 83 | all.equal( 84 | as.matrix(t(active_assay@layers[[layer_key]])), 85 | as.matrix(ad$layers[[layer_key]]), 86 | check.attributes = FALSE 87 | ), 88 | info = paste0("layer_key: ", layer_key) 89 | ) 90 | }) 91 | } 92 | 93 | # trackstatus: class=Seurat, feature=test_set_uns, status=done 94 | for (uns_key in names(obj@misc)) { 95 | test_that(paste0("as_AnnData (Seurat) retains uns key: ", uns_key), { 96 | msg <- message_if_known( 97 | backend = "from_Seurat", 98 | slot = c("uns"), 99 | dtype = uns_key, 100 | process = "convert", 101 | known_issues = known_issues 102 | ) 103 | skip_if(!is.null(msg), message = msg) 104 | 105 | expect_true(uns_key %in% names(ad$uns)) 106 | expect_equal(ad$uns[[uns_key]], obj@misc[[uns_key]], ignore_attr = TRUE) 107 | }) 108 | } 109 | 110 | test_that("as_AnnData (Seurat) retains pca", { 111 | msg <- message_if_known( 112 | backend = "from_Seurat", 113 | slot = c("obsm"), 114 | dtype = "pca", 115 | process = "convert", 116 | known_issues = known_issues 117 | ) 118 | skip_if(!is.null(msg), message = msg) 119 | 120 | # trackstatus: class=Seurat, feature=test_set_obsm, status=wip 121 | expect_equal( 122 | ad$obsm[["pca"]], 123 | Embeddings(obj, reduction = "pca"), 124 | ignore_attr = TRUE 125 | ) 126 | 127 | # trackstatus: class=Seurat, feature=test_set_varm, status=wip 128 | expect_equal( 129 | ad$varm[["pca"]], 130 | Loadings(obj, reduction = "pca"), 131 | ignore_attr = TRUE 132 | ) 133 | }) 134 | 135 | test_that("as_AnnData (Seurat) retains umap", { 136 | msg <- message_if_known( 137 | backend = "from_Seurat", 138 | slot = c("obsm"), 139 | dtype = "umap", 140 | process = "convert", 141 | known_issues = known_issues 142 | ) 143 | 144 | skip_if(!is.null(msg), message = msg) 145 | 146 | expect_equal( 147 | ad$obsm[["umap"]], 148 | Embeddings(obj, reduction = "umap"), 149 | ignore_attr = TRUE 150 | ) 151 | }) 152 | 153 | # trackstatus: class=Seurat, feature=test_set_obsp, status=done 154 | test_that("as_AnnData (Seurat) retains connectivities", { 155 | msg <- message_if_known( 156 | backend = "from_Seurat", 157 | slot = c("graphs"), 158 | dtype = "connectivities", 159 | process = "convert", 160 | known_issues = known_issues 161 | ) 162 | 163 | skip_if(!is.null(msg), message = msg) 164 | 165 | expect_equal( 166 | as.matrix(ad$obsp[["nn"]]), 167 | as.matrix(obj@graphs[["RNA_nn"]]), 168 | ignore_attr = TRUE 169 | ) 170 | expect_equal( 171 | as.matrix(ad$obsp[["snn"]]), 172 | as.matrix(obj@graphs[["RNA_snn"]]), 173 | ignore_attr = TRUE 174 | ) 175 | }) 176 | 177 | test_that("as_AnnData (Seurat) works with v3 Assays", { 178 | obj_v3_assay <- obj 179 | expect_warning( 180 | obj_v3_assay[["RNA"]] <- as(Seurat::GetAssay(obj, "RNA"), "Assay") 181 | ) 182 | 183 | adata_v3_assay <- as_AnnData(obj_v3_assay) 184 | 185 | expect_identical( 186 | to_R_matrix(adata_v3_assay$layers$counts), 187 | SeuratObject::GetAssayData(obj_v3_assay, layer = "counts") 188 | ) 189 | }) 190 | 191 | test_that("as_AnnData (Seurat) works with list mappings", { 192 | active_assay <- SeuratObject::DefaultAssay(obj) 193 | expect_no_error( 194 | as_AnnData( 195 | obj, 196 | layers_mapping = as.list(.from_Seurat_guess_layers(obj, active_assay)), 197 | obs_mapping = as.list(.from_Seurat_guess_obs(obj, active_assay)), 198 | var_mapping = as.list(.from_Seurat_guess_var(obj, active_assay)), 199 | obsm_mapping = as.list(.from_Seurat_guess_obsms(obj, active_assay)), 200 | varm_mapping = as.list(.from_Seurat_guess_varms(obj, active_assay)), 201 | obsp_mapping = as.list(.from_Seurat_guess_obsps(obj, active_assay)), 202 | varp_mapping = as.list(.from_Seurat_guess_varps(obj)), 203 | uns_mapping = as.list(.from_Seurat_guess_uns(obj)) 204 | ) 205 | ) 206 | }) 207 | 208 | test_that("as_AnnData (Seurat) works with unnamed mappings", { 209 | active_assay <- SeuratObject::DefaultAssay(obj) 210 | expect_no_error( 211 | as_AnnData( 212 | obj, 213 | layers_mapping = unname(.from_Seurat_guess_layers(obj, active_assay)), 214 | obs_mapping = unname(.from_Seurat_guess_obs(obj, active_assay)), 215 | var_mapping = unname(.from_Seurat_guess_var(obj, active_assay)), 216 | obsm_mapping = unname(.from_Seurat_guess_obsms(obj, active_assay)), 217 | varm_mapping = unname(.from_Seurat_guess_varms(obj, active_assay)), 218 | obsp_mapping = unname(.from_Seurat_guess_obsps(obj, active_assay)), 219 | varp_mapping = unname(.from_Seurat_guess_varps(obj)), 220 | uns_mapping = unname(.from_Seurat_guess_uns(obj)) 221 | ) 222 | ) 223 | }) 224 | 225 | test_that("as_AnnData (Seurat) works with empty mappings", { 226 | expect_warning(as_AnnData(obj, layers_mapping = NULL), "argument is empty") 227 | expect_warning(as_AnnData(obj, layers_mapping = c()), "argument is empty") 228 | }) 229 | -------------------------------------------------------------------------------- /tests/testthat/test-generate_dataset.R: -------------------------------------------------------------------------------- 1 | test_that("generating dummy data works", { 2 | dataset <- generate_dataset() 3 | expect_type(dataset, "list") 4 | expect_setequal( 5 | names(dataset), 6 | .anndata_slots 7 | ) 8 | expect_identical(dim(dataset$X), c(10L, 20L)) 9 | }) 10 | 11 | test_that("generating dummy SingleCellExperiment works", { 12 | dummy <- generate_dataset(format = "SingleCellExperiment") 13 | expect_s4_class(dummy, "SingleCellExperiment") 14 | }) 15 | 16 | suppressPackageStartupMessages(library(SeuratObject)) 17 | 18 | test_that("generating dummy Seurat works", { 19 | dummy <- generate_dataset(format = "Seurat") 20 | expect_s4_class(dummy, "Seurat") 21 | }) 22 | 23 | 24 | args1 <- formals(generate_dataset) 25 | args2 <- formals(.generate_dataset_as_list) 26 | 27 | # If any of these tests are failing, the arguments of generate_dataset() 28 | # need to be updated to include the new generator types. 29 | for (arg in intersect(names(args1), names(args2))) { 30 | test_that( 31 | paste0( 32 | "generate_dataset(): argument '", 33 | arg, 34 | "' has correct default value" 35 | ), 36 | { 37 | expect_equal(eval(args1[[arg]]), eval(args2[[arg]])) 38 | } 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /tests/testthat/test-h5ad-fileclosure.R: -------------------------------------------------------------------------------- 1 | dummy <- generate_dataset( 2 | n_obs = 10L, 3 | n_vars = 20L, 4 | format = "AnnData", 5 | ) 6 | 7 | file <- tempfile(pattern = "h5ad_write_", fileext = ".h5ad") 8 | 9 | test_that("writing H5AD works", { 10 | expect_no_condition({ 11 | write_h5ad(dummy, file, mode = "w") 12 | }) 13 | }) 14 | 15 | test_that("reading H5AD to InMemoryAnnData closes file", { 16 | expect_no_condition({ 17 | read_h5ad(file) 18 | write_h5ad(dummy, file, mode = "w") 19 | }) 20 | }) 21 | 22 | test_that("closing HDF5AnnData file works", { 23 | expect_no_condition({ 24 | adata <- read_h5ad(file, as = "HDF5AnnData") 25 | adata$close() 26 | write_h5ad(dummy, file, mode = "w") 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test-h5ad-read.R: -------------------------------------------------------------------------------- 1 | file <- system.file("extdata", "example.h5ad", package = "anndataR") 2 | 3 | test_that("reading H5AD as SingleCellExperiment works", { 4 | skip_if_not_installed("SingleCellExperiment") 5 | 6 | sce <- read_h5ad(file, as = "SingleCellExperiment", mode = "r") 7 | expect_s4_class(sce, "SingleCellExperiment") 8 | }) 9 | 10 | test_that("reading H5AD as Seurat works", { 11 | skip_if_not_installed("SeuratObject") 12 | # TODO: remove this suppression when the to_seurat, from_seurat functions are updated. 13 | seurat <- suppressWarnings(read_h5ad(file, as = "Seurat")) 14 | expect_s4_class(seurat, "Seurat") 15 | }) 16 | 17 | test_that("reading H5AD as InMemoryAnnData works", { 18 | adata <- read_h5ad(file, as = "InMemoryAnnData", mode = "r") 19 | expect_equal(class(adata), c("InMemoryAnnData", "AbstractAnnData", "R6")) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/testthat/test-has_row_names.R: -------------------------------------------------------------------------------- 1 | test_that("has_row_names works on matrix", { 2 | expect_false(has_row_names(matrix(1:26, ncol = 1))) 3 | expect_true( 4 | has_row_names(matrix(1:26, ncol = 1, dimnames = list(letters, NULL))) 5 | ) 6 | }) 7 | 8 | test_that("has_row_names works on data.frame", { 9 | expect_false(has_row_names(data.frame(x = 1:26))) 10 | expect_true(has_row_names(data.frame(x = 1:26, row.names = letters))) 11 | }) 12 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-X.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- names(da$matrix_generators) 17 | 18 | for (name in test_names) { 19 | # first generate a python h5ad 20 | adata_py <- da$generate_dataset( 21 | x_type = name, 22 | obs_types = list(), 23 | var_types = list(), 24 | layer_types = list(), 25 | obsm_types = list(), 26 | varm_types = list(), 27 | obsp_types = list(), 28 | varp_types = list(), 29 | uns_types = list(), 30 | nested_uns_types = list() 31 | ) 32 | 33 | # create a couple of paths 34 | file_py <- withr::local_file( 35 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 36 | ) 37 | file_r <- withr::local_file( 38 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 39 | ) 40 | file_r2 <- withr::local_file( 41 | tempfile(paste0("anndata_r2_", name), fileext = ".h5ad") 42 | ) 43 | 44 | # write to file 45 | adata_py$write_h5ad(file_py) 46 | 47 | test_that(paste0("Reading an AnnData with X '", name, "' works"), { 48 | msg <- message_if_known( 49 | backend = "HDF5AnnData", 50 | slot = c("X"), 51 | dtype = name, 52 | process = "read", 53 | known_issues = known_issues 54 | ) 55 | skip_if(!is.null(msg), message = msg) 56 | 57 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 58 | expect_equal( 59 | adata_r$shape(), 60 | unlist(reticulate::py_to_r(adata_py$shape)) 61 | ) 62 | 63 | # check that the print output is the same 64 | str_r <- capture.output(print(adata_r)) 65 | str_py <- capture.output(print(adata_py)) 66 | expect_equal(str_r, str_py) 67 | }) 68 | 69 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 70 | test_that( 71 | paste0("Comparing an anndata with X '", name, "' with reticulate works"), 72 | { 73 | msg <- message_if_known( 74 | backend = "HDF5AnnData", 75 | slot = c("X"), 76 | dtype = name, 77 | process = c("read", "reticulate"), 78 | known_issues = known_issues 79 | ) 80 | skip_if(!is.null(msg), message = msg) 81 | 82 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 83 | 84 | expect_equal( 85 | adata_r$X, 86 | py_to_r(adata_py$X), 87 | tolerance = 1e-6 88 | ) 89 | } 90 | ) 91 | 92 | test_that(paste0("Writing an AnnData with X '", name, "' works"), { 93 | msg <- message_if_known( 94 | backend = "HDF5AnnData", 95 | slot = c("X"), 96 | dtype = name, 97 | process = c("read", "write"), 98 | known_issues = known_issues 99 | ) 100 | skip_if(!is.null(msg), message = msg) 101 | 102 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 103 | write_h5ad(adata_r, file_r) 104 | 105 | # read from file 106 | adata_py2 <- ad$read_h5ad(file_r) 107 | 108 | # expect that the objects are the same 109 | expect_equal_py( 110 | adata_py2$X, 111 | adata_py$X 112 | ) 113 | }) 114 | 115 | skip_if_no_h5diff() 116 | # Get all R datatypes that are equivalent to the python datatype (name) 117 | res <- Filter(function(x) x[[1]] == name, matrix_equivalences) 118 | r_datatypes <- sapply(res, function(x) x[[2]]) 119 | 120 | for (r_name in r_datatypes) { 121 | test_msg <- paste0( 122 | "Comparing a python generated .h5ad with X '", 123 | name, 124 | "' with an R generated .h5ad '", 125 | r_name, 126 | "' works" 127 | ) 128 | test_that(test_msg, { 129 | msg <- message_if_known( 130 | backend = "HDF5AnnData", 131 | slot = c("X"), 132 | dtype = c(name, r_name), 133 | process = c("h5diff"), 134 | known_issues = known_issues 135 | ) 136 | skip_if(!is.null(msg), message = msg) 137 | 138 | # generate an R h5ad 139 | adata_r <- r_generate_dataset(10L, 20L, x_type = list(r_name)) 140 | write_h5ad(adata_r, file_r2) 141 | 142 | # run h5diff 143 | res <- processx::run( 144 | "h5diff", 145 | c("-v", file_py, file_r2, "/X"), 146 | error_on_status = FALSE 147 | ) 148 | 149 | expect_equal(res$status, 0, info = res$stdout) 150 | }) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-empty.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | bi <- reticulate::import_builtins() 12 | 13 | known_issues <- read_known_issues() 14 | 15 | # first generate a python h5ad 16 | adata_py <- ad$AnnData() 17 | 18 | name <- "empty" 19 | 20 | # create a couple of paths 21 | file_py <- withr::local_file( 22 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 23 | ) 24 | file_r <- withr::local_file( 25 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 26 | ) 27 | 28 | # write to file 29 | adata_py$write_h5ad(file_py) 30 | 31 | test_that(paste0("Reading an AnnData with layer '", name, "' works"), { 32 | msg <- message_if_known( 33 | backend = "HDF5AnnData", 34 | slot = c("none"), 35 | dtype = name, 36 | process = "read", 37 | known_issues = known_issues 38 | ) 39 | skip_if(!is.null(msg), message = msg) 40 | 41 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 42 | expect_equal( 43 | adata_r$shape(), 44 | unlist(reticulate::py_to_r(adata_py$shape)) 45 | ) 46 | 47 | # check that the print output is the same 48 | str_r <- capture.output(print(adata_r)) 49 | str_py <- capture.output(print(adata_py)) 50 | expect_equal(str_r, str_py) 51 | }) 52 | 53 | test_that(paste0("Writing an AnnData with layer '", name, "' works"), { 54 | msg <- message_if_known( 55 | backend = "HDF5AnnData", 56 | slot = c("none"), 57 | dtype = name, 58 | process = c("read", "write"), 59 | known_issues = known_issues 60 | ) 61 | skip_if(!is.null(msg), message = msg) 62 | 63 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 64 | write_h5ad(adata_r, file_r) 65 | 66 | # read from file 67 | adata_py2 <- ad$read_h5ad(file_r) 68 | 69 | # check that the print output is the same 70 | expect_equal( 71 | unlist(reticulate::py_to_r(adata_py2$shape)), 72 | unlist(reticulate::py_to_r(adata_py$shape)) 73 | ) 74 | 75 | # check that the print output is the same 76 | str_py2 <- capture.output(print(adata_py2)) 77 | str_py <- capture.output(print(adata_py)) 78 | expect_equal(str_py2, str_py) 79 | }) 80 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-layers.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- names(da$matrix_generators) 17 | 18 | for (name in test_names) { 19 | # first generate a python h5ad 20 | adata_py <- da$generate_dataset( 21 | x_type = NULL, 22 | obs_types = list(), 23 | var_types = list(), 24 | layer_types = list(name), 25 | obsm_types = list(), 26 | varm_types = list(), 27 | obsp_types = list(), 28 | varp_types = list(), 29 | uns_types = list(), 30 | nested_uns_types = list() 31 | ) 32 | 33 | # create a couple of paths 34 | file_py <- withr::local_file( 35 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 36 | ) 37 | file_r <- withr::local_file( 38 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 39 | ) 40 | file_r2 <- withr::local_file( 41 | tempfile(paste0("anndata_r2_", name), fileext = ".h5ad") 42 | ) 43 | 44 | # write to file 45 | adata_py$write_h5ad(file_py) 46 | 47 | test_that(paste0("Reading an AnnData with layer '", name, "' works"), { 48 | msg <- message_if_known( 49 | backend = "HDF5AnnData", 50 | slot = c("layers"), 51 | dtype = name, 52 | process = "read", 53 | known_issues = known_issues 54 | ) 55 | skip_if(!is.null(msg), message = msg) 56 | 57 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 58 | expect_equal( 59 | adata_r$shape(), 60 | unlist(reticulate::py_to_r(adata_py$shape)) 61 | ) 62 | expect_equal( 63 | adata_r$layers_keys(), 64 | bi$list(adata_py$layers$keys()) 65 | ) 66 | 67 | # check that the print output is the same 68 | str_r <- capture.output(print(adata_r)) 69 | str_py <- capture.output(print(adata_py)) 70 | expect_equal(str_r, str_py) 71 | }) 72 | 73 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 74 | test_that( 75 | paste0( 76 | "Comparing an anndata with layer '", 77 | name, 78 | "' with reticulate works" 79 | ), 80 | { 81 | msg <- message_if_known( 82 | backend = "HDF5AnnData", 83 | slot = c("layers"), 84 | dtype = name, 85 | process = c("read", "reticulate"), 86 | known_issues = known_issues 87 | ) 88 | skip_if(!is.null(msg), message = msg) 89 | 90 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 91 | 92 | expect_equal( 93 | adata_r$layers[[name]], 94 | py_to_r(py_get_item(adata_py$layers, name)), 95 | tolerance = 1e-6 96 | ) 97 | } 98 | ) 99 | 100 | test_that(paste0("Writing an AnnData with layer '", name, "' works"), { 101 | msg <- message_if_known( 102 | backend = "HDF5AnnData", 103 | slot = c("layers"), 104 | dtype = name, 105 | process = c("read", "write"), 106 | known_issues = known_issues 107 | ) 108 | skip_if(!is.null(msg), message = msg) 109 | 110 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 111 | write_h5ad(adata_r, file_r) 112 | 113 | # read from file 114 | adata_py2 <- ad$read_h5ad(file_r) 115 | 116 | # expect name is one of the keys 117 | expect_contains( 118 | bi$list(adata_py2$layers$keys()), 119 | name 120 | ) 121 | 122 | # expect that the objects are the same 123 | expect_equal_py( 124 | py_get_item(adata_py2$layers, name), 125 | py_get_item(adata_py$layers, name) 126 | ) 127 | }) 128 | 129 | skip_if_no_h5diff() 130 | # Get all R datatypes that are equivalent to the python datatype (name) 131 | res <- Filter(function(x) x[[1]] == name, matrix_equivalences) 132 | r_datatypes <- sapply(res, function(x) x[[2]]) 133 | 134 | for (r_name in r_datatypes) { 135 | test_msg <- paste0( 136 | "Comparing a python generated .h5ad with layer '", 137 | name, 138 | "' with an R generated .h5ad '", 139 | r_name, 140 | "' works" 141 | ) 142 | test_that(test_msg, { 143 | msg <- message_if_known( 144 | backend = "HDF5AnnData", 145 | slot = c("X"), 146 | dtype = c(name, r_name), 147 | process = c("h5diff"), 148 | known_issues = known_issues 149 | ) 150 | 151 | skip_if(!is.null(msg), message = msg) 152 | 153 | # generate an R h5ad 154 | adata_r <- r_generate_dataset(10L, 20L, layer_types = list(r_name)) 155 | write_h5ad(adata_r, file_r2) 156 | 157 | # run h5diff 158 | res <- processx::run( 159 | "h5diff", 160 | c( 161 | "-v", 162 | file_py, 163 | file_r2, 164 | paste0("/layers/", name), 165 | paste0("/layers/", r_name) 166 | ), 167 | error_on_status = FALSE 168 | ) 169 | 170 | expect_equal(res$status, 0, info = res$stdout) 171 | }) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-obsmvarm.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- c( 17 | names(da$matrix_generators), 18 | names(da$vector_generators) 19 | ) 20 | 21 | # temporary workaround for 22 | # https://github.com/data-intuitive/dummy-anndata/issues/12 23 | test_names <- setdiff( 24 | test_names, 25 | c( 26 | "categorical", 27 | "categorical_missing_values", 28 | "categorical_ordered", 29 | "categorical_ordered_missing_values", 30 | "nullable_boolean_array", 31 | "nullable_integer_array" 32 | ) 33 | ) 34 | 35 | for (name in test_names) { 36 | # first generate a python h5ad 37 | adata_py <- da$generate_dataset( 38 | x_type = NULL, 39 | obs_types = list(), 40 | var_types = list(), 41 | layer_types = list(), 42 | obsm_types = list(name), 43 | varm_types = list(name), 44 | obsp_types = list(), 45 | varp_types = list(), 46 | uns_types = list(), 47 | nested_uns_types = list() 48 | ) 49 | 50 | # create a couple of paths 51 | file_py <- withr::local_file( 52 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 53 | ) 54 | file_r <- withr::local_file( 55 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 56 | ) 57 | file_r2 <- withr::local_file( 58 | tempfile(paste0("anndata_r2_", name), fileext = ".h5ad") 59 | ) 60 | 61 | # write to file 62 | adata_py$write_h5ad(file_py) 63 | 64 | test_that( 65 | paste0("Reading an AnnData with obsm and varm '", name, "' works"), 66 | { 67 | msg <- message_if_known( 68 | backend = "HDF5AnnData", 69 | slot = c("obsm", "varm"), 70 | dtype = name, 71 | process = "read", 72 | known_issues = known_issues 73 | ) 74 | skip_if(!is.null(msg), message = msg) 75 | 76 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 77 | expect_equal( 78 | adata_r$shape(), 79 | unlist(reticulate::py_to_r(adata_py$shape)) 80 | ) 81 | expect_equal( 82 | adata_r$obsm_keys(), 83 | bi$list(adata_py$obsm$keys()) 84 | ) 85 | expect_equal( 86 | adata_r$varm_keys(), 87 | bi$list(adata_py$varm$keys()) 88 | ) 89 | 90 | # check that the print output is the same 91 | str_r <- capture.output(print(adata_r)) 92 | str_py <- capture.output(print(adata_py)) 93 | expect_equal(str_r, str_py) 94 | } 95 | ) 96 | 97 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 98 | test_that( 99 | paste0( 100 | "Comparing an anndata with obsm and varm '", 101 | name, 102 | "' with reticulate works" 103 | ), 104 | { 105 | msg <- message_if_known( 106 | backend = "HDF5AnnData", 107 | slot = c("obsm", "varm"), 108 | dtype = name, 109 | process = c("read", "reticulate"), 110 | known_issues = known_issues 111 | ) 112 | skip_if(!is.null(msg), message = msg) 113 | 114 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 115 | 116 | expect_equal( 117 | adata_r$obsm[[name]], 118 | py_to_r(py_get_item(adata_py$obsm, name)), 119 | tolerance = 1e-6 120 | ) 121 | expect_equal( 122 | adata_r$varm[[name]], 123 | py_to_r(py_get_item(adata_py$varm, name)), 124 | tolerance = 1e-6 125 | ) 126 | } 127 | ) 128 | 129 | test_that( 130 | paste0("Writing an AnnData with obsm and varm '", name, "' works"), 131 | { 132 | msg <- message_if_known( 133 | backend = "HDF5AnnData", 134 | slot = c("obsm", "varm"), 135 | dtype = name, 136 | process = c("read", "write"), 137 | known_issues = known_issues 138 | ) 139 | skip_if(!is.null(msg), message = msg) 140 | 141 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 142 | write_h5ad(adata_r, file_r) 143 | 144 | # read from file 145 | adata_py2 <- ad$read_h5ad(file_r) 146 | 147 | # expect name is one of the keys 148 | expect_contains( 149 | bi$list(adata_py2$obsm$keys()), 150 | name 151 | ) 152 | expect_contains( 153 | bi$list(adata_py2$obsm$keys()), 154 | name 155 | ) 156 | 157 | # expect that the objects are the same 158 | expect_equal_py( 159 | py_get_item(adata_py2$obsm, name), 160 | py_get_item(adata_py$obsm, name) 161 | ) 162 | expect_equal_py( 163 | py_get_item(adata_py2$varm, name), 164 | py_get_item(adata_py$varm, name) 165 | ) 166 | } 167 | ) 168 | 169 | skip_if_no_h5diff() 170 | # Get all R datatypes that are equivalent to the python datatype (name) 171 | res <- Filter(function(x) x[[1]] == name, all_equivalences) 172 | r_datatypes <- sapply(res, function(x) x[[2]]) 173 | 174 | for (r_name in r_datatypes) { 175 | test_msg <- paste0( 176 | "Comparing a python generated .h5ad with obsm and varm '", 177 | name, 178 | "' with an R generated .h5ad '", 179 | r_name, 180 | "' works" 181 | ) 182 | test_that(test_msg, { 183 | msg <- message_if_known( 184 | backend = "HDF5AnnData", 185 | slot = c("obsm", "varm"), 186 | dtype = c(name, r_name), 187 | process = c("h5diff"), 188 | known_issues = known_issues 189 | ) 190 | 191 | skip_if(!is.null(msg), message = msg) 192 | # generate an R h5ad 193 | adata_r <- r_generate_dataset( 194 | 10L, 195 | 20L, 196 | obsm_types = list(r_name), 197 | varm_types = list(r_name) 198 | ) 199 | write_h5ad(adata_r, file_r2) 200 | 201 | # run h5diff 202 | res_obsm <- processx::run( 203 | "h5diff", 204 | c( 205 | "-v", 206 | file_py, 207 | file_r2, 208 | paste0("/obsm/", name), 209 | paste0("/obsm/", r_name) 210 | ), 211 | error_on_status = FALSE 212 | ) 213 | expect_equal(res_obsm$status, 0, info = res_obsm$stdout) 214 | 215 | res_varm <- processx::run( 216 | "h5diff", 217 | c( 218 | "-v", 219 | file_py, 220 | file_r2, 221 | paste0("/varm/", name), 222 | paste0("/varm/", r_name) 223 | ), 224 | error_on_status = FALSE 225 | ) 226 | expect_equal(res_varm$status, 0, info = res_varm$stdout) 227 | }) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-obspvarp.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- names(da$matrix_generators) 17 | 18 | for (name in test_names) { 19 | # first generate a python h5ad 20 | adata_py <- da$generate_dataset( 21 | x_type = NULL, 22 | obs_types = list(), 23 | var_types = list(), 24 | layer_types = list(), 25 | obsm_types = list(), 26 | varm_types = list(), 27 | obsp_types = list(name), 28 | varp_types = list(name), 29 | uns_types = list(), 30 | nested_uns_types = list() 31 | ) 32 | 33 | # create a couple of paths 34 | file_py <- withr::local_file( 35 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 36 | ) 37 | file_r <- withr::local_file( 38 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 39 | ) 40 | file_r2 <- withr::local_file( 41 | tempfile(paste0("anndata_r2_", name), fileext = ".h5ad") 42 | ) 43 | 44 | # write to file 45 | adata_py$write_h5ad(file_py) 46 | 47 | test_that( 48 | paste0("Reading an AnnData with obsp and varp '", name, "' works"), 49 | { 50 | msg <- message_if_known( 51 | backend = "HDF5AnnData", 52 | slot = c("obsp", "varp"), 53 | dtype = name, 54 | process = "read", 55 | known_issues = known_issues 56 | ) 57 | skip_if(!is.null(msg), message = msg) 58 | 59 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 60 | expect_equal( 61 | adata_r$shape(), 62 | unlist(reticulate::py_to_r(adata_py$shape)) 63 | ) 64 | expect_equal( 65 | adata_r$obsp_keys(), 66 | bi$list(adata_py$obsp$keys()) 67 | ) 68 | expect_equal( 69 | adata_r$varp_keys(), 70 | bi$list(adata_py$varp$keys()) 71 | ) 72 | 73 | # check that the print output is the same 74 | str_r <- capture.output(print(adata_r)) 75 | str_py <- capture.output(print(adata_py)) 76 | expect_equal(str_r, str_py) 77 | } 78 | ) 79 | 80 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 81 | test_that( 82 | paste0( 83 | "Comparing an anndata with obsp and varp '", 84 | name, 85 | "' with reticulate works" 86 | ), 87 | { 88 | msg <- message_if_known( 89 | backend = "HDF5AnnData", 90 | slot = c("obsp", "varp"), 91 | dtype = name, 92 | process = c("read", "reticulate"), 93 | known_issues = known_issues 94 | ) 95 | skip_if(!is.null(msg), message = msg) 96 | 97 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 98 | 99 | expect_equal( 100 | adata_r$obsp[[name]], 101 | py_to_r(py_get_item(adata_py$obsp, name)), 102 | tolerance = 1e-6 103 | ) 104 | expect_equal( 105 | adata_r$varp[[name]], 106 | py_to_r(py_get_item(adata_py$varp, name)), 107 | tolerance = 1e-6 108 | ) 109 | } 110 | ) 111 | 112 | test_that( 113 | paste0("Writing an AnnData with obsp and varp '", name, "' works"), 114 | { 115 | msg <- message_if_known( 116 | backend = "HDF5AnnData", 117 | slot = c("obsp", "varp"), 118 | dtype = name, 119 | process = c("read", "write"), 120 | known_issues = known_issues 121 | ) 122 | skip_if(!is.null(msg), message = msg) 123 | 124 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 125 | write_h5ad(adata_r, file_r) 126 | 127 | # read from file 128 | adata_py2 <- ad$read_h5ad(file_r) 129 | 130 | # expect name is one of the keys 131 | expect_contains( 132 | bi$list(adata_py2$obsp$keys()), 133 | name 134 | ) 135 | expect_contains( 136 | bi$list(adata_py2$varp$keys()), 137 | name 138 | ) 139 | 140 | # expect that the objects are the same 141 | expect_equal_py( 142 | py_get_item(adata_py2$obsp, name), 143 | py_get_item(adata_py$obsp, name) 144 | ) 145 | expect_equal_py( 146 | py_get_item(adata_py2$varp, name), 147 | py_get_item(adata_py$varp, name) 148 | ) 149 | } 150 | ) 151 | 152 | skip_if_no_h5diff() 153 | # Get all R datatypes that are equivalent to the python datatype (name) 154 | res <- Filter(function(x) x[[1]] == name, matrix_equivalences) 155 | r_datatypes <- sapply(res, function(x) x[[2]]) 156 | 157 | for (r_name in r_datatypes) { 158 | test_msg <- paste0( 159 | "Comparing a python generated .h5ad with obsp and varp '", 160 | name, 161 | "' with an R generated .h5ad '", 162 | r_name, 163 | "' works" 164 | ) 165 | test_that(test_msg, { 166 | msg <- message_if_known( 167 | backend = "HDF5AnnData", 168 | slot = c("obsp", "varp"), 169 | dtype = c(name, r_name), 170 | process = c("h5diff"), 171 | known_issues = known_issues 172 | ) 173 | skip_if(!is.null(msg), message = msg) 174 | # generate an R h5ad 175 | adata_r <- r_generate_dataset( 176 | 10L, 177 | 20L, 178 | obsp_types = list(r_name), 179 | varp_types = list(r_name) 180 | ) 181 | write_h5ad(adata_r, file_r2) 182 | 183 | # run h5diff 184 | res_obsp <- processx::run( 185 | "h5diff", 186 | c( 187 | "-v", 188 | file_py, 189 | file_r2, 190 | paste0("/obsp/", name), 191 | paste0("/obsp/", r_name) 192 | ), 193 | error_on_status = FALSE 194 | ) 195 | expect_equal(res_obsp$status, 0, info = res_obsp$stdout) 196 | 197 | res_varp <- processx::run( 198 | "h5diff", 199 | c( 200 | "-v", 201 | file_py, 202 | file_r2, 203 | paste0("/varp/", name), 204 | paste0("/varp/", r_name) 205 | ), 206 | error_on_status = FALSE 207 | ) 208 | expect_equal(res_varp$status, 0, info = res_varp$stdout) 209 | }) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-obsvar.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- names(da$vector_generators) 17 | 18 | for (name in test_names) { 19 | # first generate a python h5ad 20 | adata_py <- da$generate_dataset( 21 | x_type = NULL, 22 | obs_types = list(name), 23 | var_types = list(name), 24 | layer_types = list(), 25 | obsm_types = list(), 26 | varm_types = list(), 27 | obsp_types = list(), 28 | varp_types = list(), 29 | uns_types = list(), 30 | nested_uns_types = list() 31 | ) 32 | 33 | # create a couple of paths 34 | file_py <- withr::local_file( 35 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 36 | ) 37 | file_r <- withr::local_file( 38 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 39 | ) 40 | file_r2 <- withr::local_file( 41 | tempfile(paste0("anndata_r2_", name), fileext = ".h5ad") 42 | ) 43 | 44 | # write to file 45 | adata_py$write_h5ad(file_py) 46 | 47 | test_that(paste0("reading an AnnData with obs and var '", name, "' works"), { 48 | msg <- message_if_known( 49 | backend = "HDF5AnnData", 50 | slot = c("obs", "var"), 51 | dtype = name, 52 | process = "read", 53 | known_issues = known_issues 54 | ) 55 | skip_if(!is.null(msg), message = msg) 56 | 57 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 58 | expect_equal( 59 | adata_r$shape(), 60 | unlist(reticulate::py_to_r(adata_py$shape)) 61 | ) 62 | expect_equal( 63 | adata_r$obs_keys(), 64 | bi$list(adata_py$obs_keys()) 65 | ) 66 | expect_equal( 67 | adata_r$var_keys(), 68 | bi$list(adata_py$var_keys()) 69 | ) 70 | 71 | # check that the print output is the same 72 | str_r <- capture.output(print(adata_r)) 73 | str_py <- capture.output(print(adata_py)) 74 | expect_equal(str_r, str_py) 75 | }) 76 | 77 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 78 | test_that( 79 | paste0( 80 | "Comparing an anndata with obs and var '", 81 | name, 82 | "' with reticulate works" 83 | ), 84 | { 85 | msg <- message_if_known( 86 | backend = "HDF5AnnData", 87 | slot = c("obs", "var"), 88 | dtype = name, 89 | process = c("read", "reticulate"), 90 | known_issues = known_issues 91 | ) 92 | skip_if(!is.null(msg), message = msg) 93 | 94 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 95 | 96 | expect_equal( 97 | adata_r$obs[[name]], 98 | py_to_r(adata_py$obs)[[name]], 99 | tolerance = 1e-6 100 | ) 101 | expect_equal( 102 | adata_r$var[[name]], 103 | py_to_r(adata_py$var)[[name]], 104 | tolerance = 1e-6 105 | ) 106 | } 107 | ) 108 | 109 | test_that(paste0("Writing an AnnData with obs and var '", name, "' works"), { 110 | msg <- message_if_known( 111 | backend = "HDF5AnnData", 112 | slot = c("obsp", "varp"), 113 | dtype = name, 114 | process = c("read", "write"), 115 | known_issues = known_issues 116 | ) 117 | skip_if(!is.null(msg), message = msg) 118 | 119 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 120 | write_h5ad(adata_r, file_r) 121 | 122 | # read from file 123 | adata_py2 <- ad$read_h5ad(file_r) 124 | 125 | # expect name is one of the keys 126 | expect_contains( 127 | bi$list(adata_py2$obs$keys()), 128 | name 129 | ) 130 | expect_contains( 131 | bi$list(adata_py2$var$keys()), 132 | name 133 | ) 134 | 135 | # expect that the objects are the same 136 | expect_equal_py(adata_py2$obs, adata_py$obs) 137 | expect_equal_py(adata_py2$var, adata_py$var) 138 | }) 139 | 140 | skip_if_no_h5diff() 141 | # Get all R datatypes that are equivalent to the python datatype (name) 142 | res <- Filter(function(x) x[[1]] == name, vector_equivalences) 143 | r_datatypes <- sapply(res, function(x) x[[2]]) 144 | 145 | for (r_name in r_datatypes) { 146 | test_msg <- paste0( 147 | "Comparing a python generated .h5ad with obs and var '", 148 | name, 149 | "' with an R generated .h5ad '", 150 | r_name, 151 | "' works" 152 | ) 153 | test_that(test_msg, { 154 | msg <- message_if_known( 155 | backend = "HDF5AnnData", 156 | slot = c("obs", "var"), 157 | dtype = c(name, r_name), 158 | process = c("h5diff"), 159 | known_issues = known_issues 160 | ) 161 | skip_if(!is.null(msg), message = msg) 162 | # generate an R h5ad 163 | adata_r <- r_generate_dataset( 164 | 10L, 165 | 20L, 166 | obs_types = list(r_name), 167 | var_types = list(r_name) 168 | ) 169 | write_h5ad(adata_r, file_r2) 170 | 171 | # run h5diff 172 | res_obs <- processx::run( 173 | "h5diff", 174 | c( 175 | "-v", 176 | file_py, 177 | file_r2, 178 | paste0("/obs/", name), 179 | paste0("/obs/", r_name) 180 | ), 181 | error_on_status = FALSE 182 | ) 183 | expect_equal(res_obs$status, 0, info = res_obs$stdout) 184 | 185 | res_var <- processx::run( 186 | "h5diff", 187 | c( 188 | "-v", 189 | file_py, 190 | file_r2, 191 | paste0("/var/", name), 192 | paste0("/var/", r_name) 193 | ), 194 | error_on_status = FALSE 195 | ) 196 | expect_equal(res_var$status, 0, info = res_var$stdout) 197 | }) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-uns-nested.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- c( 17 | names(da$matrix_generators), 18 | names(da$vector_generators), 19 | names(da$scalar_generators) 20 | ) 21 | 22 | for (name in test_names) { 23 | # first generate a python h5ad 24 | adata_py <- da$generate_dataset( 25 | x_type = NULL, 26 | obs_types = list(), 27 | var_types = list(), 28 | layer_types = list(), 29 | obsm_types = list(), 30 | varm_types = list(), 31 | obsp_types = list(), 32 | varp_types = list(), 33 | uns_types = list(), 34 | nested_uns_types = list(name) 35 | ) 36 | 37 | # create a couple of paths 38 | file_py <- withr::local_file( 39 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 40 | ) 41 | file_r <- withr::local_file( 42 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 43 | ) 44 | 45 | # write to file 46 | adata_py$write_h5ad(file_py) 47 | 48 | test_that(paste0("Reading an AnnData with uns_nested '", name, "' works"), { 49 | msg <- message_if_known( 50 | backend = "HDF5AnnData", 51 | slot = c("uns_nested"), 52 | dtype = name, 53 | process = "read", 54 | known_issues = known_issues 55 | ) 56 | skip_if(!is.null(msg), message = msg) 57 | 58 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 59 | expect_equal( 60 | names(adata_r$uns$nested), 61 | bi$list(adata_py$uns$nested$keys()) 62 | ) 63 | 64 | # check that the print output is the same 65 | str_r <- capture.output(print(adata_r)) 66 | str_py <- capture.output(print(adata_py)) 67 | expect_equal(str_r, str_py) 68 | }) 69 | 70 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 71 | test_that( 72 | paste0( 73 | "Comparing an anndata with uns_nested '", 74 | name, 75 | "' with reticulate works" 76 | ), 77 | { 78 | msg <- message_if_known( 79 | backend = "HDF5AnnData", 80 | slot = c("uns_nested"), 81 | dtype = name, 82 | process = c("read", "reticulate"), 83 | known_issues = known_issues 84 | ) 85 | skip_if(!is.null(msg), message = msg) 86 | 87 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 88 | 89 | expect_equal( 90 | adata_r$uns[["nested"]][[name]], 91 | reticulate::py_to_r(adata_py$uns$nested[[name]]) 92 | ) 93 | } 94 | ) 95 | 96 | test_that(paste0("Writing an AnnData with uns_nested '", name, "' works"), { 97 | msg <- message_if_known( 98 | backend = "HDF5AnnData", 99 | slot = c("uns_nested"), 100 | dtype = name, 101 | process = c("read", "write"), 102 | known_issues = known_issues 103 | ) 104 | skip_if(!is.null(msg), message = msg) 105 | 106 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 107 | write_h5ad(adata_r, file_r) 108 | 109 | # read from file 110 | adata_py2 <- ad$read_h5ad(file_r) 111 | 112 | # expect name is one of the keys 113 | expect_contains( 114 | bi$list(adata_py2$uns$nested$keys()), 115 | name 116 | ) 117 | 118 | # expect that the objects are the same 119 | expect_equal_py( 120 | py_get_item(adata_py2$uns$nested, name), 121 | py_get_item(adata_py$uns$nested, name) 122 | ) 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-uns.R: -------------------------------------------------------------------------------- 1 | skip_if_no_anndata() 2 | skip_if_not_installed("reticulate") 3 | 4 | library(reticulate) 5 | testthat::skip_if_not( 6 | reticulate::py_module_available("dummy_anndata"), 7 | message = "Python dummy_anndata module not available for testing" 8 | ) 9 | 10 | ad <- reticulate::import("anndata", convert = FALSE) 11 | da <- reticulate::import("dummy_anndata", convert = FALSE) 12 | bi <- reticulate::import_builtins() 13 | 14 | known_issues <- read_known_issues() 15 | 16 | test_names <- c( 17 | names(da$matrix_generators), 18 | names(da$vector_generators), 19 | names(da$scalar_generators) 20 | ) 21 | 22 | for (name in test_names) { 23 | # first generate a python h5ad 24 | adata_py <- da$generate_dataset( 25 | x_type = NULL, 26 | obs_types = list(), 27 | var_types = list(), 28 | layer_types = list(), 29 | obsm_types = list(), 30 | varm_types = list(), 31 | obsp_types = list(), 32 | varp_types = list(), 33 | uns_types = list(name), 34 | nested_uns_types = list() 35 | ) 36 | 37 | # create a couple of paths 38 | file_py <- withr::local_file( 39 | tempfile(paste0("anndata_py_", name), fileext = ".h5ad") 40 | ) 41 | file_r <- withr::local_file( 42 | tempfile(paste0("anndata_r_", name), fileext = ".h5ad") 43 | ) 44 | 45 | # write to file 46 | adata_py$write_h5ad(file_py) 47 | 48 | test_that(paste0("Reading an AnnData with uns '", name, "' works"), { 49 | msg <- message_if_known( 50 | backend = "HDF5AnnData", 51 | slot = c("uns"), 52 | dtype = name, 53 | process = "read", 54 | known_issues = known_issues 55 | ) 56 | skip_if(!is.null(msg), message = msg) 57 | 58 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 59 | expect_equal( 60 | names(adata_r$uns), 61 | bi$list(adata_py$uns$keys()) 62 | ) 63 | 64 | # check that the print output is the same 65 | str_r <- capture.output(print(adata_r)) 66 | str_py <- capture.output(print(adata_py)) 67 | expect_equal(str_r, str_py) 68 | }) 69 | 70 | # maybe this test simply shouldn't be run if there is a known issue with reticulate 71 | test_that( 72 | paste0("Comparing an anndata with uns '", name, "' with reticulate works"), 73 | { 74 | msg <- message_if_known( 75 | backend = "HDF5AnnData", 76 | slot = c("uns"), 77 | dtype = name, 78 | process = c("read", "reticulate"), 79 | known_issues = known_issues 80 | ) 81 | skip_if(!is.null(msg), message = msg) 82 | 83 | adata_r <- read_h5ad(file_py, as = "HDF5AnnData") 84 | 85 | expect_equal( 86 | adata_r$uns[[name]], 87 | reticulate::py_to_r(adata_py$uns[[name]]) 88 | ) 89 | } 90 | ) 91 | 92 | test_that(paste0("Writing an AnnData with uns '", name, "' works"), { 93 | msg <- message_if_known( 94 | backend = "HDF5AnnData", 95 | slot = c("uns"), 96 | dtype = name, 97 | process = c("read", "write"), 98 | known_issues = known_issues 99 | ) 100 | skip_if(!is.null(msg), message = msg) 101 | 102 | adata_r <- read_h5ad(file_py, as = "InMemoryAnnData") 103 | write_h5ad(adata_r, file_r) 104 | 105 | # read from file 106 | adata_py2 <- ad$read_h5ad(file_r) 107 | 108 | # expect name is one of the keys 109 | expect_contains( 110 | bi$list(adata_py2$uns$keys()), 111 | name 112 | ) 113 | 114 | # expect that the objects are the same 115 | expect_equal_py( 116 | py_get_item(adata_py2$uns, name), 117 | py_get_item(adata_py$uns, name) 118 | ) 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | col_matrix <- Matrix::rsparsematrix(nrow = 10, ncol = 20, density = 0.1) 2 | row_matrix <- as(col_matrix, "RsparseMatrix") 3 | 4 | # we expect to receive a transposed dgRMatrix from to_py_matrix 5 | test_that("to_py_matrix works with dgCMatrix", { 6 | result <- to_py_matrix(col_matrix) 7 | expect_s4_class(result, "dgRMatrix") 8 | expect_equal(result, Matrix::t(row_matrix)) 9 | }) 10 | 11 | test_that("to_py_matrix works with dgRMatrix", { 12 | result <- to_py_matrix(row_matrix) 13 | expect_s4_class(result, "dgRMatrix") 14 | expect_equal(result, Matrix::t(row_matrix)) 15 | }) 16 | 17 | # we expect to receive a transposed dgCMatrix from to_R_matrix 18 | test_that("to_R_matrix works with dgRMatrix", { 19 | result <- to_R_matrix(row_matrix) 20 | expect_s4_class(result, "dgCMatrix") 21 | expect_equal(result, Matrix::t(col_matrix)) 22 | }) 23 | 24 | test_that("to_R_matrix works with dgCMatrix", { 25 | result <- to_R_matrix(col_matrix) 26 | expect_s4_class(result, "dgCMatrix") 27 | expect_equal(result, Matrix::t(col_matrix)) 28 | }) 29 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/anndataR.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Using anndataR" 3 | author: 4 | - name: Robrecht Cannoodt 5 | - name: Luke Zappia 6 | - name: Martin Morgan 7 | - name: Louise Deconinck 8 | package: anndataR 9 | output: BiocStyle::html_document 10 | vignette: > 11 | %\VignetteIndexEntry{Using anndataR to read and convert} 12 | %\VignetteEngine{knitr::rmarkdown} 13 | %\VignetteEncoding{UTF-8} 14 | --- 15 | 16 | ```{r, include = FALSE} 17 | knitr::opts_chunk$set( 18 | collapse = TRUE, 19 | comment = "#>" 20 | ) 21 | 22 | # already load SeuratObject and SingleCellExperiment 23 | # so warnings generated by these packages are not shown 24 | library(SeuratObject) 25 | library(SingleCellExperiment) 26 | ``` 27 | 28 | **{anndataR}** allows users to work with `.h5ad` files, access various slots in the datasets and convert these files to `SingleCellExperiment` objects and `SeuratObject`s, and vice versa. 29 | 30 | Check out `?anndataR` for a full list of the functions provided by this package. 31 | 32 | ## Installation 33 | 34 | Install using: 35 | ```{r, eval = FALSE} 36 | if (!require("pak", quietly = TRUE)) { 37 | install.packages("pak") 38 | } 39 | pak::pak("scverse/anndataR") 40 | ``` 41 | 42 | ## Usage 43 | 44 | Here's a quick example of how to use **{anndataR}**. 45 | 46 | First, we fetch an example `.h5ad` file included in the package: 47 | 48 | ```{r} 49 | library(anndataR) 50 | 51 | h5ad_path <- system.file("extdata", "example.h5ad", package = "anndataR") 52 | ``` 53 | 54 | Read an h5ad file in memory: 55 | 56 | ```{r} 57 | adata <- read_h5ad(h5ad_path) 58 | ``` 59 | 60 | Read an h5ad file on disk: 61 | 62 | ```{r} 63 | adata <- read_h5ad(h5ad_path, as = "HDF5AnnData") 64 | ``` 65 | 66 | View structure: 67 | 68 | ```{r} 69 | adata 70 | ``` 71 | 72 | Access AnnData slots: 73 | 74 | ```{r} 75 | dim(adata$X) 76 | adata$obs[1:5, 1:6] 77 | adata$var[1:5, 1:6] 78 | ``` 79 | 80 | ## Interoperability 81 | 82 | Convert the AnnData object to a SingleCellExperiment object: 83 | 84 | ```{r} 85 | sce <- adata$as_SingleCellExperiment() 86 | sce 87 | ``` 88 | 89 | Convert the AnnData object to a Seurat object: 90 | 91 | ```{r as-Seurat} 92 | obj <- adata$as_Seurat() 93 | obj 94 | ``` 95 | 96 | ## Manually create an object 97 | 98 | ```{r} 99 | adata <- AnnData( 100 | X = matrix(rnorm(100), nrow = 10), 101 | obs = data.frame( 102 | cell_type = factor(rep(c("A", "B"), each = 5)) 103 | ), 104 | var = data.frame( 105 | gene_name = paste0("gene_", 1:10) 106 | ) 107 | ) 108 | 109 | adata 110 | ``` 111 | 112 | ## Session info 113 | 114 | ```{r} 115 | sessionInfo() 116 | ``` 117 | -------------------------------------------------------------------------------- /vignettes/development_status.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Development status 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Development status} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | The following tables show the status of the implementation of each feature in the package: 11 | 12 | ```{r include=FALSE} 13 | library(tibble) 14 | library(knitr) 15 | library(rprojroot) 16 | library(stringr) 17 | library(purrr) 18 | library(dplyr) 19 | library(tidyr) 20 | 21 | proj_root <- find_root(has_file("DESCRIPTION")) 22 | source <- list.files(proj_root, pattern = "*.R$", recursive = TRUE) 23 | 24 | # look for trackstatus comments 25 | status_lines <- map_df(source, function(path) { 26 | lines <- readLines(paste0(proj_root, "/", path)) 27 | line_numbers <- grep("# trackstatus:", lines) 28 | 29 | map_df(line_numbers, function(line_number) { 30 | tryCatch( 31 | { 32 | line_stripped <- gsub(" *# trackstatus: *", "", lines[[line_number]]) 33 | line_split <- str_split_1(line_stripped, ", *") 34 | vals_split <- str_split(line_split, " *= *") 35 | names <- sapply(vals_split, function(x) x[[1]]) 36 | values <- lapply(vals_split, function(x) x[[2]]) 37 | df <- data.frame(setNames(values, names), check.names = FALSE) 38 | df$source_file <- path 39 | df$line_number <- line_number 40 | df 41 | }, 42 | error = function(e) { 43 | message("Error in ", path, " at line ", line_number, ": ", e$message) 44 | NULL 45 | } 46 | ) 47 | }) 48 | }) 49 | 50 | # split feature id into prefix and slot columns 51 | strip_prefix <- c( 52 | "get_" = "Getter", 53 | "test_get_" = "Getter test", 54 | "set_" = "Setter", 55 | "test_set_" = "Setter test" 56 | ) 57 | status_lines_proc <- status_lines %>% 58 | mutate( 59 | prefix = str_extract(feature, "^(test_)?[gs]et_"), 60 | slot = str_replace(feature, "^(test_)?[gs]et_", "") 61 | ) |> 62 | select(-feature) 63 | 64 | # combine with missing fields 65 | status_lines_required <- crossing( 66 | class = c("InMemoryAnnData", "HDF5AnnData", "Seurat", "SingleCellExperiment"), 67 | prefix = c("get_", "test_get_", "set_", "test_set_"), 68 | slot = c( 69 | "X", 70 | "layers", 71 | "obs", 72 | "var", 73 | "obs_names", 74 | "var_names", 75 | "obsm", 76 | "varm", 77 | "obsp", 78 | "varp", 79 | "uns", 80 | "raw" 81 | ), 82 | status = "missing" 83 | ) 84 | status_required <- bind_rows( 85 | status_lines_proc, 86 | anti_join( 87 | status_lines_required, 88 | status_lines_proc, 89 | by = c("class", "prefix", "slot") 90 | ) 91 | ) 92 | 93 | # Fill in non-required slots 94 | status <- status_required |> 95 | group_by(class) |> 96 | complete(prefix, slot, fill = list(status = "missing")) |> 97 | ungroup() 98 | 99 | # check duplicated status 100 | status_dup <- status |> 101 | group_by(class, prefix, slot) |> 102 | filter(n() > 1) |> 103 | ungroup() |> 104 | mutate( 105 | str = paste0( 106 | source_file, "#", line_number, " -- ", class, " ", prefix, slot 107 | ) 108 | ) 109 | if (nrow(status_dup) > 0) { 110 | stop( 111 | "Duplicated status lines found:\n", paste(status_dup$str, collapse = "\n") 112 | ) 113 | } 114 | 115 | # create formatted data frame for printing 116 | status_formatted <- status |> 117 | mutate( 118 | prefix_formatted = strip_prefix[prefix], 119 | status_formatted = ifelse( 120 | status == "missing", 121 | "", 122 | paste0( 123 | "[", c("done" = "✅", "wip" = "🚧", "missing" = "")[status], 124 | "](https://github.com/scverse/anndataR/blob/main/", source_file, 125 | "#L", line_number, ")" 126 | ) 127 | ) 128 | ) |> 129 | select(class, Slot = slot, prefix_formatted, status_formatted) |> 130 | spread(prefix_formatted, status_formatted) 131 | ``` 132 | 133 | ## Objects 134 | 135 | ```{r echo=FALSE, results="asis"} 136 | # loop over each of the classes and print the table as markdown 137 | all_classes <- unique(status_formatted$class) 138 | object_classes <- all_classes[!all_classes %in% c("Seurat", "SingleCellExperiment")] 139 | 140 | for (class_name in object_classes) { 141 | cat("### ", class_name, "\n\n", sep = "") 142 | 143 | df <- status_formatted |> 144 | filter(class == class_name) |> 145 | select(-class) |> 146 | knitr::kable(escape = FALSE, align = "lcccc") 147 | 148 | cat(paste(as.character(df), collapse = "\n")) 149 | cat("\n\n") 150 | } 151 | ``` 152 | 153 | ## Conversion 154 | 155 | ```{r echo=FALSE, results="asis"} 156 | # loop over each of the classes and print the table as markdown 157 | for (class_name in c("SingleCellExperiment", "Seurat")) { 158 | cat("### ", class_name, "\n\n", sep = "") 159 | 160 | df <- status_formatted |> 161 | filter(class == class_name) |> 162 | select( 163 | Slot, 164 | From = Setter, 165 | `From test` = `Setter test`, 166 | To = Getter, 167 | `To test` = `Getter test` 168 | ) |> 169 | knitr::kable(escape = FALSE, align = "lcccc") 170 | 171 | cat(paste(as.character(df), collapse = "\n")) 172 | cat("\n\n") 173 | } 174 | ``` 175 | -------------------------------------------------------------------------------- /vignettes/diagrams/class_diagram.mmd: -------------------------------------------------------------------------------- 1 | 2 | classDiagram 3 | class AbstractAnnData { 4 | *X: Matrix 5 | *layers: List[Matrix] 6 | *obs: DataFrame 7 | *var: DataFrame 8 | *obsp: List[Matrix] 9 | *varp: List[Matrix] 10 | *obsm: List[Matrix] 11 | *varm: List[Matrix] 12 | *uns: List 13 | *n_obs: int 14 | *n_vars: int 15 | *obs_names: Array[String] 16 | *var_names: Array[String] 17 | *subset(...): AbstractAnnData 18 | *write_h5ad(): Unit 19 | 20 | as_SingleCellExperiment(): SingleCellExperiment 21 | as_Seurat(): Seurat 22 | 23 | as_HDF5AnnData(): HDF5AnnData 24 | as_ZarrAnnData(): ZarrAnnData 25 | as_InMemoryAnnData(): InMemoryAnnData 26 | } 27 | 28 | AbstractAnnData <|-- HDF5AnnData 29 | class HDF5AnnData { 30 | init(h5file): HDF5AnnData 31 | } 32 | 33 | AbstractAnnData <|-- ZarrAnnData 34 | class ZarrAnnData { 35 | init(zarrFile): ZarrAnnData 36 | } 37 | 38 | AbstractAnnData <|-- InMemoryAnnData 39 | class InMemoryAnnData { 40 | init(X, obs, var, shape, ...): InMemoryAnnData 41 | } 42 | 43 | AbstractAnnData <|-- ReticulateAnnData 44 | class ReticulateAnnData { 45 | init(pyobj): ReticulateAnnData 46 | } 47 | 48 | class anndataR { 49 | read_h5ad(path, backend): Either[AbstractAnnData, SingleCellExperiment, Seurat] 50 | } 51 | anndataR --> AbstractAnnData 52 | -------------------------------------------------------------------------------- /vignettes/diagrams/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Convert mermaid diagrams to different formats 4 | # because RMarkdown doesn't support mermaid diagrams 5 | 6 | docker run --rm -w /pwd -u `id -u`:`id -g` \ 7 | -v `pwd`:/pwd minlag/mermaid-cli \ 8 | -i vignettes/diagrams/class_diagram.mmd -o vignettes/diagrams/class_diagram.svg 9 | -------------------------------------------------------------------------------- /vignettes/known_issues.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Known issues 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Known issues} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | This document lists known issues with the package and suggests possible solutions. 11 | 12 | ```{r echo=FALSE, results="asis"} 13 | # how to get a file from a package 14 | data <- yaml::read_yaml(system.file("known_issues.yaml", package = "anndataR"))$known_issues 15 | 16 | for (i in seq_along(data)) { 17 | str <- paste0( 18 | "## Issue: ", data[[i]]$description, "\n\n", 19 | " * Affected backend: ", paste0("`", data[[i]]$backend, "`", collapse = ", "), "\n", 20 | " * Affected slot(s): ", paste0("`", data[[i]]$slot, "`", collapse = ", "), "\n", 21 | " * Affected dtype(s): ", paste0("`", data[[i]]$dtype, "`", collapse = ", "), "\n", 22 | " * Probable cause: ", data[[i]]$process, "\n", 23 | " * To investigate: ", data[[i]]$to_investigate, "\n", 24 | " * To fix: ", data[[i]]$to_fix, "\n\n", 25 | "### Error message\n\n", 26 | paste(paste0(" ", strsplit(data[[i]]$error_message, "\n")[[1]], "\n"), collapse = ""), "\n\n", 27 | "### Proposed solution\n\n", 28 | data[[i]]$proposed_solution, "\n\n" 29 | ) 30 | cat(str) 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /vignettes/software_design.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Software design 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Software design} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "" 15 | ) 16 | ``` 17 | 18 | 19 | `{anndataR}` is designed to offer the combined functionality of the following packages: 20 | 21 | * [theislab/zellkonverter](https://github.com/theislab/zellkonverter): Convert AnnData files to/from `SingleCellExperiment` objects. 22 | * [mtmorgan/h5ad](https://github.com/mtmorgan/h5ad/): Read/write `*.h5ad` files natively using `rhdf5`. 23 | * [dynverse/anndata](https://github.com/dynverse/anndata): An R implementation of the AnnData data structures, uses `reticulate` to read/write `*.h5ad` files. 24 | 25 | Ideally, this package will be a complete replacement for all of these packages, and will be the go-to package for working with AnnData files in R. 26 | 27 | ## Desired feature list 28 | 29 | * Provide an `R6` class to work with AnnData objects in R (either in-memory or on-disk). 30 | * Read/write `*.h5ad` files natively 31 | * Convert to/from `SingleCellExperiment` objects 32 | * Convert to/from `Seurat` objects 33 | 34 | ## Class diagram 35 | 36 | Here is a diagram of the main R6 classes provided by the package: 37 | 38 | ![](diagrams/class_diagram.svg) 39 | 40 | Notation: 41 | 42 | - `X: Matrix` - variable `X` is of type `Matrix` 43 | - `*X: Matrix` - variable `X` is abstract 44 | - `as_SingleCellExperiment(): SingleCellExperiment` - function `as_SingleCellExperiment` returns object of type `SingleCellExperiment` 45 | - `*as_SingleCellExperiment()` - function `as_SingleCellExperiment` is abstract 46 | -------------------------------------------------------------------------------- /vignettes/usage_h5ad.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Read/write H5AD files 3 | package: anndataR 4 | output: BiocStyle::html_document 5 | vignette: > 6 | %\VignetteIndexEntry{Read/write H5AD files} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>" 15 | ) 16 | ``` 17 | 18 | This vignette demonstrates how to read and write `.h5ad` files using the **{anndataR}** package. 19 | 20 | Check out `?anndataR` for a full list of the functions provided by this package. 21 | 22 | ## Example data 23 | 24 | A great place for finding single-cell datasets as `.h5ad` files is the [CELLxGENE](https://cellxgene.cziscience.com/datasets) website. 25 | 26 | We will use an example file included in the package for demonstration. 27 | 28 | ```{r} 29 | library(anndataR) 30 | 31 | h5ad_path <- system.file("extdata", "example.h5ad", package = "anndataR") 32 | ``` 33 | 34 | ## Reading in memory 35 | 36 | To read an h5ad file into memory, use the `read_h5ad` function. By default, the data will be read entirely into memory: 37 | 38 | ```{r} 39 | adata <- read_h5ad(h5ad_path) 40 | ``` 41 | 42 | This reads the entire `.h5ad` file into memory as an `AnnData` object. You can then inspect its structure: 43 | 44 | ```{r} 45 | adata 46 | ``` 47 | 48 | ## Reading backed by HDF5 49 | 50 | For large datasets that do not fit into memory, you can read the h5ad file in a "backed" mode. This means that the data remains on disk, and only parts that are actively being used are loaded into memory. 51 | 52 | To do this, set the `to` parameter in the `read_h5ad` to `HDF5AnnData`: 53 | 54 | ```{r} 55 | adata <- read_h5ad(h5ad_path, as = "HDF5AnnData") 56 | ``` 57 | The structure of the object will look similar to the in-memory representation, but the data is stored on disk. 58 | 59 | ```{r} 60 | adata 61 | ``` 62 | 63 | Note that any changes made to the object will be reflected in the `.h5ad` file! 64 | 65 | ## Writing h5ad files 66 | 67 | You can write an `AnnData` object to an h5ad file using the `write_h5ad` function: 68 | 69 | ``` r 70 | # Create a temporary file for demonstration 71 | temp_h5ad <- tempfile(fileext = ".h5ad") 72 | 73 | adata$write_h5ad(temp_h5ad) 74 | ``` 75 | 76 | ## Accessing AnnData slots 77 | 78 | The `AnnData` object is a list-like object containing various slots. Here's how you can access some of them: 79 | 80 | ```{r} 81 | dim(adata$X) 82 | adata$obs[1:5, 1:6] 83 | adata$var[1:5, 1:6] 84 | ``` 85 | 86 | You can also access other slots like `layers`, `uns`, `obsm`, `varm`, and `obsp` in a similar way. 87 | 88 | ## Session info 89 | 90 | ```{r} 91 | sessionInfo() 92 | ``` 93 | -------------------------------------------------------------------------------- /vignettes/usage_seurat.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Read/write Seurat objects using anndataR 3 | package: anndataR 4 | output: BiocStyle::html_document 5 | vignette: > 6 | %\VignetteIndexEntry{Read/write Seurat objects using anndataR} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>", 15 | warning = FALSE 16 | ) 17 | ``` 18 | 19 | This vignette demonstrates how to read and write `Seurat` objects using the **{anndataR}** package, leveraging the interoperability between `Seurat` and the `AnnData` format. 20 | 21 | Check out `?anndataR` for a full list of the functions provided by this package. 22 | 23 | ## Introduction 24 | 25 | Seurat is a widely used toolkit for single-cell analysis in R. 26 | **{anndataR}** enables conversion between `Seurat` objects and `AnnData` objects, 27 | allowing you to leverage the strengths of both the scverse and Seurat ecosystems. 28 | 29 | ## Prerequisites 30 | 31 | Before you begin, make sure you have both Seurat and **{anndataR}** installed. You can install them using the following code: 32 | 33 | ```r 34 | if (!requireNamespace("pak", quietly = TRUE)) { 35 | install.packages("pak") 36 | } 37 | pak::pak("Seurat") 38 | pak::pak("scverse/anndataR") 39 | ``` 40 | 41 | ## Converting an AnnData Object to a Seurat Object 42 | 43 | Using an example `.h5ad` file included in the package, we will demonstrate how to read an `.h5ad` file and convert it to a `Seurat` object. 44 | 45 | ```{r prep_file} 46 | library(anndataR) 47 | library(Seurat) 48 | 49 | h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") 50 | ``` 51 | 52 | Read the `.h5ad` file and convert it to a `Seurat` object: 53 | 54 | ```{r read_data} 55 | seurat_obj <- read_h5ad(h5ad_file, as = "Seurat") 56 | seurat_obj 57 | ``` 58 | 59 | This is equivalent to reading in the `.h5ad` file and explicitly converting. 60 | 61 | ```{r convert_seurat} 62 | adata <- read_h5ad(h5ad_file) 63 | seurat_obj <- adata$as_Seurat() 64 | seurat_obj 65 | ``` 66 | 67 | Note that there is no one-to-one mapping possible between the AnnData and SeuratObject data structures, 68 | so some information might be lost during conversion. It is recommended to carefully inspect the converted object 69 | to ensure that all necessary information has been transferred. 70 | 71 | See `?as_Seurat` for more details on how to customize the conversion process. For instance: 72 | 73 | ```{r customize_seurat_conversion} 74 | adata$as_Seurat( 75 | assay_name = "ADT", 76 | layers_mapping = c(counts = "dense_counts", data = "dense_X") 77 | ) 78 | ``` 79 | 80 | ## Convert a Seurat Object to an AnnData Object 81 | 82 | Here's an example demonstrating how to create a `Seurat` object from scratch, then convert it to `AnnData` and save it as `.h5ad` 83 | 84 | ```{r create_seurat} 85 | counts <- matrix(rbinom(20000, 1000, .001), nrow = 100) 86 | seurat_obj <- CreateSeuratObject(counts = counts) |> 87 | NormalizeData() |> 88 | FindVariableFeatures() |> 89 | ScaleData() |> 90 | RunPCA(npcs = 10) |> 91 | FindNeighbors() |> 92 | RunUMAP(dims = 1:10) 93 | seurat_obj 94 | ``` 95 | 96 | You can write out the `Seurat` object to an `.h5ad` file using the `write_h5ad` function: 97 | 98 | ```r 99 | write_h5ad(seurat_obj, "seurat_obj.h5ad") 100 | ``` 101 | 102 | If you need more customized conversion, you first need to convert the `Seurat` object to an `AnnData` object. 103 | Again note that there is no one-to-one mapping possible between the AnnData and SeuratObject data structures, 104 | so some information might be lost during conversion. It is recommended to carefully inspect the converted object 105 | to ensure that all necessary information has been transferred. 106 | 107 | See `?as_AnnData` for more details on how to customize the conversion process. Example: 108 | 109 | ```{r customize_as_AnnData} 110 | as_AnnData( 111 | seurat_obj, 112 | assay_name = "RNA", 113 | x_mapping = "data", 114 | layers_mapping = c(foo = "counts") 115 | ) 116 | ``` 117 | 118 | ## Session info 119 | 120 | ```{r} 121 | sessionInfo() 122 | ``` 123 | -------------------------------------------------------------------------------- /vignettes/usage_singlecellexperiment.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Read/write SingleCellExperiment objects using anndataR 3 | package: anndataR 4 | output: BiocStyle::html_document 5 | vignette: > 6 | %\VignetteIndexEntry{Read/write SingleCellExperiment objects using anndataR} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>", 15 | warning = FALSE 16 | ) 17 | ``` 18 | 19 | This vignette demonstrates how to read and write `SingleCellExperiment` objects using the **{anndataR}** package, leveraging the interoperability between `SingleCellExperiment` and the `AnnData` format. 20 | 21 | Check out `?anndataR` for a full list of the functions provided by this package. 22 | 23 | ## Introduction 24 | 25 | SingleCellExperiment is a widely used class for storing single-cell data in R, especially within the Bioconductor ecosystem. 26 | **{anndataR}** enables conversion between `SingleCellExperiment` objects and `AnnData` objects, allowing you to leverage the strengths of both the scverse and Bioconductor ecosystems. 27 | 28 | ## Prerequisites 29 | 30 | Before you begin, make sure you have both SingleCellExperiment and **{anndataR}** installed. You can install them using the following code: 31 | 32 | ```r 33 | if (!requireNamespace("pak", quietly = TRUE)) { 34 | install.packages("pak") 35 | } 36 | pak::pak(c("SingleCellExperiment", "SummarizedExperiment")) 37 | pak::pak("scverse/anndataR") 38 | ``` 39 | 40 | ## Converting an AnnData Object to a SingleCellExperiment Object 41 | 42 | Using an example `.h5ad` file included in the package, we will demonstrate how to read an `.h5ad` file and convert it to a `SingleCellExperiment` object. 43 | 44 | ```{r prep_h5ad_file} 45 | library(anndataR) 46 | library(SingleCellExperiment) 47 | 48 | h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR") 49 | ``` 50 | 51 | Read the `.h5ad` file: 52 | 53 | ```{r read_h5ad} 54 | adata <- read_h5ad(h5ad_file) 55 | adata 56 | ``` 57 | 58 | Convert to a `SingleCellExperiment` object: 59 | 60 | ```{r convert_implicit} 61 | sce_obj <- adata$as_SingleCellExperiment() 62 | sce_obj 63 | ``` 64 | 65 | Note that there is no one-to-one mapping possible between the AnnData and SingleCellExperiment data structures, so some information might be lost during conversion. It is recommended to carefully inspect the converted object to ensure that all necessary information has been transferred. 66 | 67 | See the [in-depth vignette on conversion to and from SingleCellExperiment objects](singlecellexperiment.html) for more details on how to customize the conversion process. For instance: 68 | 69 | ```{r ex_mapping, eval=FALSE} 70 | adata$as_SingleCellExperiment( 71 | assays_mapping = FALSE, 72 | colData_mapping = list(coldata1 = "integer", coldata2 = "numeric"), 73 | rowData_mapping = list(rowdata1 = "character", rowdata2 = "logical") 74 | ) 75 | ``` 76 | 77 | ## Convert a SingleCellExperiment Object to an AnnData Object 78 | 79 | Here's an example demonstrating how to create a `SingleCellExperiment` object from scratch, then convert it to `AnnData` and save it as `.h5ad` 80 | 81 | ```{r construct_sce} 82 | counts <- matrix(rbinom(20000, 1000, .001), nrow = 100) 83 | sce_obj <- SingleCellExperiment(list(counts = counts)) 84 | 85 | sce_obj 86 | ``` 87 | 88 | You can convert the `SingleCellExperiment` object to an `AnnData` object using the `as_AnnData` function: 89 | 90 | ```{r from_SCE} 91 | adata <- as_AnnData(sce_obj) 92 | adata 93 | ``` 94 | 95 | Again note that there is no one-to-one mapping possible between the AnnData and SingleCellExperiment data structures, so some information might be lost during conversion. It is recommended to carefully inspect the converted object to ensure that all necessary information has been transferred. 96 | 97 | See the [in-depth vignette on conversion to and from SingleCellExperiment objects](singlecellexperiment.html) for more details on how to customize the conversion process. Example: 98 | 99 | ```{r from_SCE_ex} 100 | as_AnnData( 101 | sce_obj, 102 | x_mapping = "counts", 103 | layers_mapping = FALSE 104 | ) 105 | ``` 106 | 107 | ## Session info 108 | 109 | ```{r} 110 | sessionInfo() 111 | ``` 112 | --------------------------------------------------------------------------------