GSVA on spatial omics data

License: Artistic-2.0

Introduction

GSVA provides now specific support for spatial omics data in the algorithm that runs through the gsvaParam() parameter constructor, and originally described in the publication by Hänzelmann et al. (2013). At the moment, this specific support consists of the following features:

  • The currently available container for spatial omics data that allows one to input additional row and column metadata, and spatial coordinates, is a SpatialExperiment object.
  • While the input spatial omics data is always sparse, the output of enrichment scores will be always dense, and therefore, the container storing those scores will be different from the input data, typically a matrix or a dense DelayedMatrix object using an HDF5Matrix backend. The latter will be particularly used when the total number of values exceeds 2^31, which is the largest 32-bit standard integer value in R.
  • By default, when the input expression data is stored in a sparse data container, as it typically happens with spatial omics data, then a sparse regime of the GSVA algorithm will run, if GSVA is the chosen method, by which nonzero values are treated differently from zero values, leading to slightly different results than those obtained by applying the classical GSVA algorithm. If we set the parameter sparse=FALSE in the call to gsvaParam(), the classical GSVA algorithm will be used, which for a typical spatial omics data set will result in longer running times and larger memory consumption than running it in the default sparse regime for this type of data.
  • The GSVA algorithm can be run either at once through a call to gsva() with a parameter object or in three steps: (1) row normalization with gsvaRowNorm(); (2) column rank transformation with gsvaColRanks(); and (3) column enrichment scores calculation with gsvaColScores(). Splitting the GSVA algorithm into these three steps allows one to distribute and balance the computational load of the algorithm in a high-performance computing (HPC) environment with multiple nodes, and to reuse the output of the first two steps, which are independent of the gene sets, to calculate enrichment scores for different collections of gene sets, without having to repeat the first two steps.

In what follows, we will illustrate the use of GSVA on a publicly available spatial transcriptomics transcriptomics data set published by Maynard et al. (2021), which is available through the spatialLIBD package, and which contains 12 samples of human dorsolateral prefrontal cortex (DLPFC) tissue.

Import data

We import the DLPFC data using the spatialLIBD package, as a SpatialExperiment object, following the instructions at http://research.libd.org/spatialLIBD/articles/spatialLIBD.html.

library(SpatialExperiment)
library(ExperimentHub)
library(spatialLIBD)

ehub <- ExperimentHub()
spe <- fetch_data(type="spe", eh=ehub)

Note that this data set contains 12 samples.

table(spe$sample_id)

151507 151508 151509 151510 151669 151670 151671 151672 151673 151674 151675 
  4226   4384   4789   4634   3661   3498   4110   4015   3639   3673   3592 
151676 
  3460 

For the purpose of speeding up processing this vignette, we will analyze here only the sample with identifier 151673.

spe <- spe[, spe$sample_id == "151673"]
dim(spe)
[1] 33538  3639

There is an indicator column called in_tissue for identifying spots overlapping the tissue, but apparently all spots have such a condition.

stopifnot(all(colData(spe)$in_tissue))

The column data includes a column called spatialLIBD with anatomically annotated groups of spots (six cortical layers from the grey matter and the white matter). This annotation is apparently missing for a small number of spots, we are going to discard those spots.

table(colData(spe)$spatialLIBD, useNA="always")

  L1   L2   L3   L4   L5   L6   WM <NA> 
 273  253  989  218  673  692  513   28 
spe <- spe[, !is.na(colData(spe)$spatialLIBD)]
dim(spe)
[1] 33538  3611
table(colData(spe)$spatialLIBD, useNA="always")

  L1   L2   L3   L4   L5   L6   WM <NA> 
 273  253  989  218  673  692  513    0 

Quality control and pre-processing

Here, we perform a quality control (QC) and pre-processing steps using the package scrapper (Lun and Kancherla 2022). We start identifying mitochondrial genes.

library(scrapper)

is_mito <- grepl("^MT-", rowData(spe)$gene_name)
table(is_mito)
is_mito
FALSE  TRUE 
33525    13 

Calculate QC metrics and tally the number of low-quality spots.

spe <- quickRnaQc.se(spe, subsets=list(mito=is_mito))
spe$discard <- !spe$keep
table(spe$discard)

FALSE  TRUE 
 3566    45 

Plot detected low-quality spots to ensure they are not located in a specific region of the tissue.

library(ggspavis)

plotObsQC(spe, plot_type="spot", annotate="discard")
Detected low-quality spots in the DLPFC sample 151673.

Detected low-quality spots in the DLPFC sample 151673.

Filter out low-quality spots.

spe <- spe[, spe$keep]
dim(spe)
[1] 33538  3566

Filter out genes that are expressed in less than 1% of the spots.

spotsxgene <- rowSums(counts(spe) > 0)
spe <- spe[spotsxgene > floor(ncol(spe)*0.01), ]
dim(spe)
[1] 13842  3566

Calculate library size factors and normalized units of expression in logarithmic scale.

spe <- normalizeRnaCounts.se(spe, size.factors=spe$sum)
assayNames(spe)
[1] "counts"    "logcounts"

Spot cell-type annotation using GSVA

Here we use the a collection of brain cell-type marker genes derived from the single-nuclei RNA-seq (snRNA-seq) data set published by Tran et al. (2021), to estimate the most abundant cell type in each spot of the DLPFC sample 151673. This collection of marker gene sets is available in the GSVAdata package, which contains the script that generated the marker gene sets, and can be imported as follows.

library(GSEABase)
library(GSVA)

fname <- file.path(system.file("extdata", package="GSVAdata"),
                   "human_brain_snRNAseq_cellType_markers.gmt.gz")
gsets <- readGMT(fname)
gsets
GeneSetCollection
  names: Astro, Excit_A, ..., Tcell (19 total)
  unique identifiers: ENSG00000164199, ENSG00000234377, ..., ENSG00000089335 (1188 total)
  types in collection:
    geneIdType: ENSEMBLIdentifier (1 total)
    collectionType: NullCollection (1 total)

We first build a parameter object using the function gsvaParam(). By default, the expression values in the logcounts assay will be selected for downstream analysis.

gsvapar <- gsvaParam(spe, gsets)
gsvapar
class: GSVA::gsvaParam
expression data dim: 13842 3566
number of gene sets: 19
details: use 'details(object)'

Second, we call the gsva() function to calculate the GSVA scores for each spot and each gene set. The output will be a SpatialExperiment object with the GSVA scores stored in a new assay called es. When the input spatial omics data is very large, you can use the three-step approach described in the single-cell RNA-seq vignette.

es <- gsva(gsvapar)
es
class: SpatialExperiment 
dim: 19 3566 
metadata(2): qc gsvaParam
assays(1): es
rownames(19): Astro Excit_A ... OPC Tcell
rowData names(1): gs
colnames(3566): AAACAAGTATCTCCCA-1 AAACAATCTACTAGCA-1 ...
  TTGTTTGTATTACACG-1 TTGTTTGTGTAAATTC-1
colData names(74): sample_id Cluster ... keep sizeFactor
reducedDimNames(6): PCA TSNE_perplexity50 ... TSNE_perplexity80
  UMAP_neighbors15
mainExpName: NULL
altExpNames(0):
spatialCoords names(2) : pxl_col_in_fullres pxl_row_in_fullres
imgData names(4): sample_id image_id data scaleFactor

Finally, we assign to each spot the gene set with highest GSVA score, and store that assignment as a new metadata column into the spe object.

whmax <- apply(assay(es), 2, which.max)
es$cellType <- factor(rownames(es)[whmax])

Figure @ref(fig:dlpfcgsva) shows the annotated layers of the DLPFC data set and the spatial distribution of the most likely cell type in each spot, based on the GSVA scores.

library(ggspavis)
library(patchwork)
library(RColorBrewer)

ctpal <- colorRampPalette(brewer.pal(9, "Set1"))(nlevels(es$cellType))
plts <- list(plotVisium(spe, point_size=0.60, facets=NULL, annotate="spatialLIBD",
                        pal=brewer.pal(nlevels(spe$spatialLIBD), "Set1")) +
             labs(tag="a"),
             plotVisium(es, assay="es", point_size=0.60, facets=NULL,
                        annotate="cellType",
                        pal=ctpal) +
             labs(tag="b")
             )
wrap_plots(plts, nrow=1)
(a) Annotated layers of the DLPFC sample 151673 and (b) spatial distribution of the most likely cell type in each spot, based on GSVA scores.

  1. Annotated layers of the DLPFC sample 151673 and (b) spatial distribution of the most likely cell type in each spot, based on GSVA scores.

We can see that the white matter layer is enriched in oligodendrocytes, while the gray matter layers are enriched in excitatory and inhibitory neurons, and astrocytes. While in this case it is clear that oligodendrocytes are the most abundant cell type in the white matter layer, we can also verify this observation by calculating the Moran’s I spatial autocorrelation statistic for the GSVA scores of every gene set and rank them, using the function spatCor() implemented also in the GSVA package, which can take as input the SpatialExperiment object with the GSVA scores obtained in the previous step.
I <- spatCor(es)
I[order(I$observed, decreasing=TRUE), ]
      gene_id    observed      expected          sd      p.value sample_id
17      Oligo 0.612102836 -0.0002805049 0.002605108 0.0000000000    151673
3     Excit_B 0.407928577 -0.0002805049 0.002606096 0.0000000000    151673
4     Excit_C 0.359016955 -0.0002805049 0.002606089 0.0000000000    151673
19      Tcell 0.342641318 -0.0002805049 0.002606179 0.0000000000    151673
15      Micro 0.325299838 -0.0002805049 0.002606098 0.0000000000    151673
6     Excit_E 0.318354412 -0.0002805049 0.002606208 0.0000000000    151673
18        OPC 0.312038618 -0.0002805049 0.002606022 0.0000000000    151673
2     Excit_A 0.298156172 -0.0002805049 0.002606154 0.0000000000    151673
7     Excit_F 0.265523623 -0.0002805049 0.002606211 0.0000000000    151673
5     Excit_D 0.190184460 -0.0002805049 0.002606038 0.0000000000    151673
8     Inhib_A 0.186672620 -0.0002805049 0.002605990 0.0000000000    151673
16      Mural 0.181292594 -0.0002805049 0.002606086 0.0000000000    151673
9     Inhib_B 0.169324164 -0.0002805049 0.002605999 0.0000000000    151673
1       Astro 0.152319427 -0.0002805049 0.002605916 0.0000000000    151673
11    Inhib_D 0.137978863 -0.0002805049 0.002605837 0.0000000000    151673
10    Inhib_C 0.136515026 -0.0002805049 0.002605912 0.0000000000    151673
14 Macrophage 0.126416993 -0.0002805049 0.002606060 0.0000000000    151673
12    Inhib_E 0.095574372 -0.0002805049 0.002606035 0.0000000000    151673
13    Inhib_F 0.008786699 -0.0002805049 0.002606099 0.0005028671    151673

We can observe that the Oligo gene set has the highest Moran’s I statistic, which follows from the spatial distribution of the GSVA scores for that gene set, which is shown in Figure @ref(fig:dlpfcgsvaoligo).

plotVisium(es, assay="es", point_size=1, facets=NULL, annotate="Oligo")
Spatial distribution of the GSVA scores for the Oligodendrocytes gene set in the DLPFC sample 151673.

Spatial distribution of the GSVA scores for the Oligodendrocytes gene set in the DLPFC sample 151673.

Benchmarking

We are still benchmarking and testing this version of GSVA for spatial omics data. If you encounter problems or have suggestions, do not hesitate to contact us by opening an issue in the GSVA GitHub repo.

Session information

Here is the output of sessionInfo() on the system on which this document was compiled running pandoc 3.8.3:

sessionInfo()
R version 4.6.1 (2026-06-24)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 26.04 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.32.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] patchwork_1.3.2             ggspavis_1.19.1            
 [3] ggplot2_4.0.3               spatialLIBD_1.25.2         
 [5] ExperimentHub_3.3.1         AnnotationHub_4.3.2        
 [7] BiocFileCache_3.3.0         dbplyr_2.6.0               
 [9] SpatialExperiment_1.23.0    RColorBrewer_1.1-3         
[11] igraph_2.3.3                bluster_1.23.0             
[13] scrapper_1.7.3              TENxPBMCData_1.31.0        
[15] HDF5Array_1.41.0            h5mread_1.5.0              
[17] rhdf5_2.57.3                DelayedArray_0.39.3        
[19] SparseArray_1.13.2          S4Arrays_1.13.0            
[21] abind_1.4-8                 Matrix_1.7-6               
[23] SingleCellExperiment_1.35.2 org.Hs.eg.db_3.23.1        
[25] GSVAdata_1.49.2             GSEABase_1.75.0            
[27] graph_1.91.0                annotate_1.91.0            
[29] XML_3.99-0.23               AnnotationDbi_1.75.2       
[31] GSVA_2.7.12                 SummarizedExperiment_1.43.0
[33] Biobase_2.73.2              GenomicRanges_1.65.1       
[35] Seqinfo_1.3.0               IRanges_2.47.2             
[37] S4Vectors_0.51.6            BiocGenerics_0.59.10       
[39] generics_0.1.4              MatrixGenerics_1.25.0      
[41] matrixStats_1.5.0           BiocStyle_2.41.0           

loaded via a namespace (and not attached):
  [1] later_1.4.8               BiocIO_1.23.3            
  [3] bitops_1.0-9              filelock_1.0.3           
  [5] tibble_3.3.1              lifecycle_1.0.5          
  [7] httr2_1.3.0               edgeR_4.11.4             
  [9] doParallel_1.0.17         lattice_0.22-9           
 [11] magrittr_2.0.5            limma_3.69.2             
 [13] plotly_4.12.1             sass_0.4.10              
 [15] rmarkdown_2.31            jquerylib_0.1.4          
 [17] yaml_2.3.12               httpuv_1.6.17            
 [19] otel_0.2.0                ggside_0.4.1             
 [21] sessioninfo_1.2.4         cowplot_1.2.0            
 [23] DBI_1.3.0                 buildtools_1.0.0         
 [25] golem_1.0.1               purrr_1.2.2              
 [27] RCurl_1.98-1.19           rappdirs_0.3.4           
 [29] circlize_0.4.18           ggrepel_0.9.8            
 [31] irlba_2.3.7               maketools_1.3.2          
 [33] DelayedMatrixStats_1.35.0 codetools_0.2-20         
 [35] scuttle_1.23.1            DT_0.34.0                
 [37] tidyselect_1.2.1          shape_1.4.6.1            
 [39] memuse_4.2-3              farver_2.1.2             
 [41] viridis_0.6.5             ScaledMatrix_1.21.0      
 [43] shinyWidgets_0.9.1        GenomicAlignments_1.49.1 
 [45] jsonlite_2.0.0            GetoptLong_1.1.1         
 [47] BiocNeighbors_2.7.2       scater_1.41.2            
 [49] iterators_1.0.14          foreach_1.5.2            
 [51] tools_4.6.1               Rcpp_1.1.2               
 [53] glue_1.8.1                gridExtra_2.3.1          
 [55] BiocBaseUtils_1.15.1      xfun_0.60                
 [57] dplyr_1.2.1               withr_3.0.3              
 [59] BiocManager_1.30.27       fastmap_1.2.0            
 [61] rhdf5filters_1.25.3       digest_0.6.39            
 [63] rsvd_1.0.5                R6_2.6.1                 
 [65] mime_0.13                 colorspace_2.1-3         
 [67] RSQLite_3.53.3            cigarillo_1.3.1          
 [69] config_0.3.2              tidyr_1.3.2              
 [71] data.table_1.18.4         rtracklayer_1.73.0       
 [73] httr_1.4.8                htmlwidgets_1.6.4        
 [75] pkgconfig_2.0.3           gtable_0.3.6             
 [77] blob_1.3.0                ComplexHeatmap_2.29.0    
 [79] S7_0.2.2                  XVector_0.53.0           
 [81] sys_3.4.3                 htmltools_0.5.9          
 [83] clue_0.3-68               scales_1.4.0             
 [85] png_0.1-9                 attempt_0.3.1            
 [87] knitr_1.51                rjson_0.2.23             
 [89] curl_7.1.0                cachem_1.1.0             
 [91] GlobalOptions_0.1.4       stringr_1.6.0            
 [93] BiocVersion_3.24.0        vipor_0.4.7              
 [95] parallel_4.6.1            restfulr_0.0.17          
 [97] pillar_1.11.1             grid_4.6.1               
 [99] vctrs_0.7.3               promises_1.5.0           
[101] BiocSingular_1.29.0       beachmat_2.29.0          
[103] xtable_1.8-8              cluster_2.1.8.2          
[105] beeswarm_0.4.0            paletteer_1.7.0          
[107] evaluate_1.0.5            magick_2.9.1             
[109] Rsamtools_2.29.0          cli_3.6.6                
[111] locfit_1.5-9.12           compiler_4.6.1           
[113] rlang_1.3.0               crayon_1.5.3             
[115] labeling_0.4.3            rematch2_2.1.2           
[117] ggbeeswarm_0.7.3          stringi_1.8.7            
[119] viridisLite_0.4.3         BiocParallel_1.47.0      
[121] Biostrings_2.81.6         benchmarkme_1.0.8        
[123] sparseMatrixStats_1.25.0  bit64_4.8.2              
[125] Rhdf5lib_2.1.0            KEGGREST_1.53.6          
[127] statmod_1.5.2             shiny_1.14.0             
[129] memoise_2.0.1             bslib_0.11.0             
[131] benchmarkmeData_2.0.0     bit_4.6.0                

References

Hänzelmann, Sonja, Robert Castelo, and Justin Guinney. 2013. GSVA: Gene Set Variation Analysis for Microarray and RNA-Seq Data.” BMC Bioinformatics 14: 7. https://doi.org/10.1186/1471-2105-14-7.
Lun, Aaron, and Jayaram Kancherla. 2022. “Powering Single-Cell Analyses in the Browser with WebAssembly.” bioRxiv, 2022–03.
Maynard, Kristen R, Leonardo Collado-Torres, Lukas M Weber, et al. 2021. “Transcriptome-Scale Spatial Gene Expression in the Human Dorsolateral Prefrontal Cortex.” Nature Neuroscience 24 (3): 425–36.
Tran, Matthew N, Kristen R Maynard, Abby Spangler, et al. 2021. “Single-Nucleus Transcriptome Analysis Reveals Cell-Type-Specific Molecular Signatures Across Reward Circuitry in the Human Brain.” Neuron 109 (19): 3088–103.