Introduction
This vignette demonstrates how to read and write Seurat
objects using the anndataR
package, leveraging the interoperability between Seurat
and
the AnnData
format.
Seurat is a
widely used toolkit for single-cell analysis in R. anndataR
enables conversion between Seurat
objects and
AnnData
objects, allowing you to leverage the strengths of
both the scverse and Seurat ecosystems.
Prerequisites
This vignette requires the Seurat package in addition to anndataR. You can install them using the following code:
install.packages("Seurat")
Reading H5AD files to a Seurat
Object
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.
library(anndataR)
library(Seurat)
#> Loading required package: SeuratObject
#> Loading required package: sp
#> 'SeuratObject' was built under R 4.5.0 but the current version is
#> 4.5.1; it is recomended that you reinstall 'SeuratObject' as the ABI
#> for R may have changed
#>
#> Attaching package: 'SeuratObject'
#> The following objects are masked from 'package:base':
#>
#> intersect, t
h5ad_file <- system.file("extdata", "example.h5ad", package = "anndataR")
Read the .h5ad
file as a Seurat
object:
seurat_obj <- read_h5ad(h5ad_file, as = "Seurat")
seurat_obj
#> An object of class Seurat
#> 100 features across 50 samples within 1 assay
#> Active assay: RNA (100 features, 0 variable features)
#> 5 layers present: counts, csc_counts, dense_X, dense_counts, X
#> 2 dimensional reductions calculated: X_pca, X_umap
This is equivalent to reading in the .h5ad
file as an
AnnData
and explicitly converting:
adata <- read_h5ad(h5ad_file)
seurat_obj <- adata$as_Seurat()
seurat_obj
#> An object of class Seurat
#> 100 features across 50 samples within 1 assay
#> Active assay: RNA (100 features, 0 variable features)
#> 5 layers present: counts, csc_counts, dense_X, dense_counts, X
#> 2 dimensional reductions calculated: X_pca, X_umap
Mapping between AnnData
and Seurat
Figure @ref(fig:mapping) shows the structures of the
AnnData
and Seurat
objects and how anndataR
maps between them. It is important to note that matrices in the two
objects are transposed relative to each other.

Mapping between AnnData
and Seurat
objects
By default, all items in most slots are converted using the same
names. An exception is the varp
slot which doesn’t have a
corresponding slot in Seurat
. Items in the
varm
slot are only converted when they are specified in a
mapping argument. The Neighbors
and Images
slots are not mapped when converting from Seurat
. See
?as_Seurat
for more details on the default mapping.
Customizing the conversion
You can customize the conversion process by providing specific
mappings for each slot in the Seurat
object.
Each of the mapping arguments can be provided with one of the following:
-
TRUE
: all items in the slot will be copied using the default mapping -
FALSE
: the slot will not be copied - A (named) character vector: the names are the names of the slot in
the
Seurat
object, the values are the names of the slot in theAnnData
object.
See ?as_Seurat
for more details on how to customize the
conversion process. For instance:
seurat_obj <- adata$as_Seurat(
layers_mapping = c("counts", "dense_counts"),
object_metadata_mapping = c(metadata1 = "Int", metadata2 = "Float"),
assay_metadata_mapping = FALSE,
reduction_mapping = list(
pca = c(key = "PC_", embeddings = "X_pca", loadings = "PCs"),
umap = c(key = "UMAP_", embeddings = "X_umap")
),
graph_mapping = TRUE,
misc_mapping = c(misc1 = "Bool", misc2 = "IntScalar")
)
seurat_obj
#> An object of class Seurat
#> 100 features across 50 samples within 1 assay
#> Active assay: RNA (100 features, 0 variable features)
#> 3 layers present: counts, dense_counts, X
#> 2 dimensional reductions calculated: pca, umap
The mapping arguments can also be passed directly to
read_h5ad()
.
Writing a Seurat
object to a H5AD file
The reverse conversion is also possible, allowing you to convert the
Seurat
object back to an AnnData
object, or to
just write out the Seurat
object as an .h5ad
file.
write_h5ad(seurat_obj, tempfile(fileext = ".h5ad"))
This is equivalent to converting the Seurat
object to an
AnnData
object and then writing it out:
adata <- as_AnnData(seurat_obj)
adata$write_h5ad(tempfile(fileext = ".h5ad"))
You can again customize the conversion process by providing specific
mappings for each slot in the AnnData
object. For more
details, see ?as_AnnData
.
Here’s an example:
adata <- as_AnnData(
seurat_obj,
assay_name = "RNA",
x_mapping = "counts",
layers_mapping = c("dense_counts"),
obs_mapping = c(RNA_count = "nCount_RNA", metadata1 = "metadata1"),
var_mapping = FALSE,
obsm_mapping = list(X_pca = "pca", X_umap = "umap"),
obsp_mapping = TRUE,
uns_mapping = c("misc1", "misc2")
)
adata
#> InMemoryAnnData object with n_obs × n_vars = 50 × 100
#> obs: 'RNA_count', 'metadata1'
#> uns: 'misc1', 'misc2'
#> obsm: 'X_pca', 'X_umap'
#> layers: 'dense_counts'
#> obsp: 'connectivities', 'distances'
The mapping arguments can also be passed directly to
write_h5ad()
.
Session info
sessionInfo()
#> R version 4.5.1 (2025-06-13)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.3 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
#> [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
#> [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
#> [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] Seurat_5.3.0 SeuratObject_5.2.0 sp_2.2-0 anndataR_0.99.2
#> [5] BiocStyle_2.36.0
#>
#> loaded via a namespace (and not attached):
#> [1] deldir_2.0-4 pbapply_1.7-4 gridExtra_2.3
#> [4] rlang_1.1.6 magrittr_2.0.4 RcppAnnoy_0.0.22
#> [7] spatstat.geom_3.6-0 matrixStats_1.5.0 ggridges_0.5.7
#> [10] compiler_4.5.1 reshape2_1.4.4 png_0.1-8
#> [13] systemfonts_1.3.1 vctrs_0.6.5 stringr_1.5.2
#> [16] pkgconfig_2.0.3 fastmap_1.2.0 promises_1.3.3
#> [19] rmarkdown_2.30 ragg_1.5.0 purrr_1.1.0
#> [22] xfun_0.53 cachem_1.1.0 jsonlite_2.0.0
#> [25] goftest_1.2-3 later_1.4.4 rhdf5filters_1.20.0
#> [28] Rhdf5lib_1.30.0 spatstat.utils_3.2-0 irlba_2.3.5.1
#> [31] parallel_4.5.1 cluster_2.1.8.1 R6_2.6.1
#> [34] ica_1.0-3 spatstat.data_3.1-8 stringi_1.8.7
#> [37] bslib_0.9.0 RColorBrewer_1.1-3 reticulate_1.43.0
#> [40] spatstat.univar_3.1-4 parallelly_1.45.1 scattermore_1.2
#> [43] lmtest_0.9-40 jquerylib_0.1.4 Rcpp_1.1.0
#> [46] bookdown_0.45 knitr_1.50 tensor_1.5.1
#> [49] future.apply_1.20.0 zoo_1.8-14 sctransform_0.4.2
#> [52] httpuv_1.6.16 Matrix_1.7-3 splines_4.5.1
#> [55] igraph_2.1.4 tidyselect_1.2.1 abind_1.4-8
#> [58] yaml_2.3.10 spatstat.random_3.4-2 spatstat.explore_3.5-3
#> [61] codetools_0.2-20 miniUI_0.1.2 listenv_0.9.1
#> [64] plyr_1.8.9 lattice_0.22-7 tibble_3.3.0
#> [67] shiny_1.11.1 S7_0.2.0 ROCR_1.0-11
#> [70] evaluate_1.0.5 Rtsne_0.17 future_1.67.0
#> [73] fastDummies_1.7.5 desc_1.4.3 survival_3.8-3
#> [76] polyclip_1.10-7 fitdistrplus_1.2-4 pillar_1.11.1
#> [79] BiocManager_1.30.26 KernSmooth_2.23-26 plotly_4.11.0
#> [82] generics_0.1.4 RcppHNSW_0.6.0 ggplot2_4.0.0
#> [85] scales_1.4.0 globals_0.18.0 xtable_1.8-4
#> [88] glue_1.8.0 lazyeval_0.2.2 tools_4.5.1
#> [91] data.table_1.17.8 RSpectra_0.16-2 RANN_2.6.2
#> [94] fs_1.6.6 dotCall64_1.2 rhdf5_2.52.1
#> [97] cowplot_1.2.0 grid_4.5.1 tidyr_1.3.1
#> [100] nlme_3.1-168 patchwork_1.3.2 cli_3.6.5
#> [103] spatstat.sparse_3.1-0 textshaping_1.0.4 spam_2.11-1
#> [106] viridisLite_0.4.2 dplyr_1.1.4 uwot_0.2.3
#> [109] gtable_0.3.6 sass_0.4.10 digest_0.6.37
#> [112] progressr_0.16.0 ggrepel_0.9.6 htmlwidgets_1.6.4
#> [115] farver_2.1.2 htmltools_0.5.8.1 pkgdown_2.1.3
#> [118] lifecycle_1.0.4 httr_1.4.7 mime_0.13
#> [121] MASS_7.3-65