MultiAssaySpatialExperiment 0.99.12
library(MultiAssaySpatialExperiment)
library(SummarizedExperiment)
library(S4Vectors)
This vignette is for readers who want to know how a MultiAssaySpatialExperiment
is put together: which slots exist, what the mapping tables mean, and which
invariants the class maintains. For day-to-day use start with
Introduction to MultiAssaySpatialExperiment, which works through a complete
example, and Working with MultiAssaySpatialExperiment for construction and
subsetting recipes.
A MASE object has three parts. It inherits the assays, specimen metadata and
sampleMap of a MultiAssayExperiment. It adds four spatial element layers:
points, shapes, images and labels. And it carries one extra mapping table,
spatialMap, that says where in space each assay column sits, alongside imgData,
which associates specimens with images.
The quickest way to see all three is to build a small object and take it apart.
mat <- matrix(rnorm(20), nrow = 5, ncol = 4,
dimnames = list(paste0("Gene", 1:5), paste0("Cell", 1:4)))
pts <- DataFrame(
x = c(1.2, 2.5, 3.1, 4.8),
y = c(1.5, 2.3, 3.7, 4.2),
instance_id = paste0("Cell", 1:4))
mase <- MultiAssaySpatialExperiment(
experiments = ExperimentList(rna = mat),
colData = DataFrame(row.names = paste0("Cell", 1:4)),
sampleMap = DataFrame(
assay = factor("rna", "rna"),
primary = paste0("Cell", 1:4),
colname = paste0("Cell", 1:4)
),
points = PointsLayerList(coords = pts),
spatialMap = DataFrame(
assay = factor("rna", "rna"),
colname = paste0("Cell", 1:4),
element_type = "points",
region = factor("coords", "coords"),
instance_id = paste0("Cell", 1:4)
)
)
mase
#> A MultiAssaySpatialExperiment object of 1 listed
#> experiment with a user-defined name and respective class.
#> Containing an ExperimentList class object of length 1:
#> [1] rna: matrix with 5 rows and 4 columns
#> Functionality:
#> experiments() - obtain the ExperimentList instance
#> colData() - the primary/phenotype DataFrame
#> sampleMap() - the sample coordination DataFrame
#> `$`, `[`, `[[` - extract colData columns, subset, or experiment
#> *Format() - convert into a long or wide DataFrame
#> assays() - convert ExperimentList to a SimpleList of matrices
#> exportClass() - save data to flat files
#> Spatial elements:
#> spatialImages: 0 elements
#> spatialLabels: 0 elements
#> spatialPoints: 1 element
#> spatialShapes: 0 elements
#> imgData: NULL
#> spatialMap: present
The assays live in an ExperimentList, one element per assay. Elements may be plain
matrices, or any class supporting [ and colnames(): SummarizedExperiment,
SingleCellExperiment, SpatialExperiment, or SpatialFeatureExperiment.
experiments(mase)
#> ExperimentList class object of length 1:
#> [1] rna: matrix with 5 rows and 4 columns
colData(mase)
#> DataFrame with 4 rows and 0 columns
sampleMap(mase)
#> DataFrame with 4 rows and 3 columns
#> assay primary colname
#> <factor> <character> <character>
#> 1 rna Cell1 Cell1
#> 2 rna Cell2 Cell2
#> 3 rna Cell3 Cell3
#> 4 rna Cell4 Cell4
colData holds one row per specimen, the biological unit, and its row names are
the primary identifiers. An observation is an assay column: a cell, spot or bin.
Those are different things, and sampleMap is what relates them:
| Column | Type | Description |
|---|---|---|
assay |
factor | Name of the assay in ExperimentList |
primary |
character | Row name in colData (specimen identifier) |
colname |
character | Column name in the assay |
Each row reads “column X of assay Y belongs to specimen Z”. Several assay columns may map to one specimen, which is how replicates and multi-assay specimens are expressed.
Each spatial slot is a named list, so a single object can carry several layers of the same kind: cell centroids and transcript locations as two points layers, say, or cell boundaries and tissue regions as two shapes layers.
| Slot | Accessor | Class | Each element is |
|---|---|---|---|
points |
spatialPoints() |
PointsLayerList |
a DataFrame with x, y and instance_id (plus z or annotations) |
shapes |
spatialShapes() |
ShapesLayerList |
a DataFrame with an sf geometry column and instance_id |
images |
spatialImages() |
RasterLayerList |
a raster: an array, a terra SpatRaster, or a path on disk |
labels |
spatialLabels() |
RasterLayerList |
a raster whose pixel values are instance identifiers |
spatialPoints(mase)
#> PointsLayerList of length 1
#> [1] coords: DFrame (4 x 3)
spatialPoints(mase)[["coords"]]
#> DataFrame with 4 rows and 3 columns
#> x y instance_id
#> <numeric> <numeric> <character>
#> 1 1.2 1.5 Cell1
#> 2 2.5 2.3 Cell2
#> 3 3.1 3.7 Cell3
#> 4 4.8 4.2 Cell4
# empty in this minimal object, but the accessors always exist
spatialShapes(mase)
#> ShapesLayerList of length 0
spatialImages(mase)
#> RasterLayerList of length 0
spatialMap is the piece that makes the container more than a bag of slots. It is a
five-column DataFrame:
| Column | Type | Description |
|---|---|---|
assay |
factor | Name of the assay in ExperimentList |
colname |
character | Column name in the assay |
element_type |
character | Which spatial slot: "points" or "shapes" |
region |
character | Layer name within that slot (e.g. "coords", "cells") |
instance_id |
character | Row identifier within that layer |
spatialMap(mase)
#> DataFrame with 4 rows and 5 columns
#> assay colname element_type region instance_id
#> <factor> <character> <character> <factor> <character>
#> 1 rna Cell1 points coords Cell1
#> 2 rna Cell2 points coords Cell2
#> 3 rna Cell3 points coords Cell3
#> 4 rna Cell4 points coords Cell4
Each row reads “column X of assay Y is located at row Z of layer W”. Because the link is a table rather than a slot on the assay, several assays can share one set of geometries, and subsetting the object rewrites this table so the links stay consistent instead of leaving orphaned geometry behind.
The triple (element_type, region, instance_id) resolves to a row of
slot(mase, element_type)[[region]], and validity requires that every spatialMap row
matches a sampleMap row on (assay, colname) and that every instance_id exists in
the layer it names.
imgData plays the same role for images, with one row per image-specimen pair:
sample_id (a colData row name), image_id (a name in the images slot),
scaleFactor for pixel-to-coordinate conversion, and, once loaded, the raster payload
or file reference in data, width, height and path.
Putting those together, the object is a small relational schema:
(#fig:mase_erd)Entity-relationship diagram of the MASE schema. Tan entities are inherited from MultiAssayExperiment; blue entities are added by MASE.
Each box is one accessor’s return value, and its rows are that object’s columns, with
PK marking an identifying column and FK one that points at another box. The line
endings are standard crow’s-foot notation:
a double bar means exactly one, a circle-and-bar means zero or one, a crow’s foot with a circle means zero or more, and a crow’s foot with a bar means one or more.
So the line from colData to sampleMap reads “one specimen has zero or more
sampleMap rows”, which is the replicate case described above.
For construction beyond this minimal example, including multi-assay objects, shapes, images and labels, and the validity errors you are likely to hit first, see the Working with MultiAssaySpatialExperiment vignette.
sessionInfo()
#> R version 4.6.1 Patched (2026-06-24 r90190)
#> Platform: x86_64-apple-darwin20
#> Running under: macOS Ventura 13.7.8
#>
#> Matrix products: default
#> BLAS: /Library/Frameworks/R.framework/Versions/4.6-x86_64/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.6-x86_64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
#>
#> locale:
#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#>
#> time zone: America/New_York
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] MultiAssaySpatialExperiment_0.99.12 MultiAssayExperiment_1.39.1
#> [3] SummarizedExperiment_1.43.0 Biobase_2.73.2
#> [5] GenomicRanges_1.65.4 Seqinfo_1.3.2
#> [7] IRanges_2.47.5 S4Vectors_0.51.9
#> [9] BiocGenerics_0.59.12 generics_0.1.4
#> [11] MatrixGenerics_1.25.0 matrixStats_1.5.0
#> [13] BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.10 class_7.3-24
#> [3] SparseArray_1.13.2 KernSmooth_2.23-27
#> [5] lattice_0.23-1 digest_0.6.39
#> [7] magrittr_2.0.5 evaluate_1.0.5
#> [9] grid_4.6.1 bookdown_0.48
#> [11] fastmap_1.2.0 jsonlite_2.0.0
#> [13] Matrix_1.7-6 e1071_1.7-17
#> [15] DBI_1.3.0 BiocManager_1.30.27
#> [17] SingleCellExperiment_1.35.2 jquerylib_0.1.4
#> [19] abind_1.4-8 cli_3.6.6
#> [21] rlang_1.3.0 units_1.0-1
#> [23] XVector_0.53.0 cachem_1.1.0
#> [25] DelayedArray_0.39.6 yaml_2.3.12
#> [27] otel_0.2.0 S4Arrays_1.13.0
#> [29] tools_4.6.1 SpatialExperiment_1.23.0
#> [31] R6_2.6.1 proxy_0.4-29
#> [33] lifecycle_1.0.5 classInt_0.4-11
#> [35] magick_2.9.1 bslib_0.12.0
#> [37] Rcpp_1.1.2 sf_1.1-2
#> [39] xfun_0.60 knitr_1.51
#> [41] rjson_0.2.23 htmltools_0.5.9
#> [43] rmarkdown_2.32 compiler_4.6.1