Date: 2026-04-28

# spoon Tutorial

Kinnary H. Shah1, Boyi Guo1 and Stephanie C. Hicks1

1Johns Hopkins Bloomberg School of Public Health, Baltimore, MD, USA

#### 28 April 2026

#### Package

spoon 1.9.0

# 1 Introduction

`spoon` is a method to address the mean-variance relationship in spatially resolved transcriptomics data. Current approaches rank spatially variable genes based on either p-values or some effect size, such as the proportion of spatially variable genes. However, previous work in RNA-sequencing has shown that a technical bias, referred to as the “mean-variance relationship”, exists in these data in that the gene-level variance is correlated with mean RNA expression. We found that there is a “mean-variance relationship” in spatial transcriptomics data, and so we propose `spoon`, a statistical framework to prioritize spatially variable genes that is not confounded by this relationship. We fit a spline curve to estimate the mean-variance relationship. Then, similar to using weights in a weighted least squares model, we used weights that we plugged into a Gaussian Process Regression model fit with a nearest-neighbor Gaussian process model to the preprocessed expression measurements for each gene, i.e. one model per gene. `spoon` removes the bias and leads to a more informative set of spatially variable genes.

The `generate_weights()` function calculates individual observation weights, where an individual observation is a UMI (unique molecular identifier) count value for a specific gene and sample. If the desired SVG detection method accepts weights, then the individual observation weights can be used as inputs. If the desired SVG detection method does not accept weights, then the Delta method is leveraged to rescale the data and covariates by the weights. These scaled data and covariates are used as inputs into the desired SVG detection function.

Bioconductor houses the infrastructure to store and analyze spatially resolved transcriptomics data for R users, including many SVG detection methods. This method addresses the mean-variance relationship confounding SVG detection, which is related to these other Bioconductor packages. Additionally, `spoon` is inspired by `limma::voom()` , which is a popular Bioconductor package.

# 2 Installation

The following code will install the latest release version of the `spoon` package from Bioconductor. Additional details are shown on the Bioconductor page.

    install.packages("BiocManager")
    BiocManager::install("spoon")

The latest development version can also be installed from the `devel` version of Bioconductor or from GitHub.

# 3 Input data format

We recommend the input data be provided as a SpatialExperiment Bioconductor object. The outputs are stored in the `rowData` of the `SpatialExperiment` object. The examples below use this input data format.

The inputs can also be provided as a numeric matrix of raw counts and a numeric matrix of spatial coordinates.

# 4 Tutorial

Load packages and data

    library(nnSVG)
    library(STexampleData)

    ## Loading required package: ExperimentHub

    ## Loading required package: BiocGenerics

    ## Loading required package: generics

    ##
    ## Attaching package: 'generics'

    ## The following objects are masked from 'package:base':
    ##
    ##     as.difftime, as.factor, as.ordered, intersect, is.element, setdiff,
    ##     setequal, union

    ##
    ## Attaching package: 'BiocGenerics'

    ## The following objects are masked from 'package:stats':
    ##
    ##     IQR, mad, sd, var, xtabs

    ## The following objects are masked from 'package:base':
    ##
    ##     Filter, Find, Map, Position, Reduce, anyDuplicated, aperm, append,
    ##     as.data.frame, basename, cbind, colnames, dirname, do.call,
    ##     duplicated, eval, evalq, get, grep, grepl, is.unsorted, lapply,
    ##     mapply, match, mget, order, paste, pmax, pmax.int, pmin, pmin.int,
    ##     rank, rbind, rownames, sapply, saveRDS, table, tapply, unique,
    ##     unsplit, which.max, which.min

    ## Loading required package: AnnotationHub

    ## Loading required package: BiocFileCache

    ## Loading required package: dbplyr

    ## Loading required package: SingleCellExperiment

    ## Loading required package: SummarizedExperiment

    ## Loading required package: MatrixGenerics

    ## Loading required package: matrixStats

    ##
    ## Attaching package: 'MatrixGenerics'

    ## The following objects are masked from 'package:matrixStats':
    ##
    ##     colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
    ##     colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
    ##     colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
    ##     colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
    ##     colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
    ##     colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
    ##     colWeightedMeans, colWeightedMedians, colWeightedSds,
    ##     colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
    ##     rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
    ##     rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
    ##     rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
    ##     rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
    ##     rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
    ##     rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
    ##     rowWeightedSds, rowWeightedVars

    ## Loading required package: GenomicRanges

    ## Loading required package: stats4

    ## Loading required package: S4Vectors

    ##
    ## Attaching package: 'S4Vectors'

    ## The following object is masked from 'package:utils':
    ##
    ##     findMatches

    ## The following objects are masked from 'package:base':
    ##
    ##     I, expand.grid, unname

    ## Loading required package: IRanges

    ## Loading required package: Seqinfo

    ## Loading required package: Biobase

    ## Welcome to Bioconductor
    ##
    ##     Vignettes contain introductory material; view with
    ##     'browseVignettes()'. To cite Bioconductor, see
    ##     'citation("Biobase")', and for packages 'citation("pkgname")'.

    ##
    ## Attaching package: 'Biobase'

    ## The following object is masked from 'package:MatrixGenerics':
    ##
    ##     rowMedians

    ## The following objects are masked from 'package:matrixStats':
    ##
    ##     anyMissing, rowMedians

    ## The following object is masked from 'package:ExperimentHub':
    ##
    ##     cache

    ## The following object is masked from 'package:AnnotationHub':
    ##
    ##     cache

    ## Loading required package: SpatialExperiment

    library(SpatialExperiment)
    library(BRISC)

    ## Loading required package: RANN

    ## Loading required package: parallel

    ## Loading required package: rdist

    ## Loading required package: pbapply

    ## The ordering of inputs x (covariates) and y (response) in BRISC_estimation has been changed BRISC 1.0.0 onwards.
    ##   Please check the new documentation with ?BRISC_estimation.

    library(BiocParallel)
    library(scuttle)
    library(Matrix)

    ##
    ## Attaching package: 'Matrix'

    ## The following object is masked from 'package:S4Vectors':
    ##
    ##     expand

    library(spoon)

    spe <- Visium_mouseCoronal()

    ## see ?STexampleData and browseVignettes('STexampleData') for documentation

    ## loading from cache

Preprocessing

    # keep spots over tissue
    spe <- spe[, colData(spe)$in_tissue == 1]

    # filter out low quality genes
    spe <- filter_genes(spe)

    ## Gene filtering: removing mitochondrial genes

    ## removed 13 mitochondrial genes

    ## Gene filtering: retaining genes with at least 3 counts in at least 0.5% (n = 14) of spatial locations

    ## removed 21883 out of 32272 genes due to low expression

    # calculate logcounts (log-transformed normalized counts) using scran package
    spe <- computeLibraryFactors(spe)

    ## Warning in .library_size_factors(assay(x, assay.type), ...): 'librarySizeFactors' is deprecated.
    ## Use 'scrapper::centerSizeFactors' instead.
    ## See help("Deprecated")

    spe <- logNormCounts(spe)

    ## Warning in .local(x, ...): 'normalizeCounts' is deprecated.
    ## Use 'scrapper::normalizeCounts' instead.
    ## See help("Deprecated")

    # choose a small number of genes for this example to run quickly
    set.seed(3)
    ix_random <- sample(seq_len(nrow(spe)), 10)
    spe <- spe[ix_random, ]

    # remove spots with zero counts
    spe <- spe[, colSums(logcounts(spe)) > 0]

Step 1: generate weights

    weights <- generate_weights(input = spe,
                                stabilize = TRUE,
                                BPPARAM = MulticoreParam(workers = 1,
                                                         RNGseed = 4))

    ## 21.9222222222222% of observations had their weight stabilized

Step 2: weighted SVG detection

    spe <- weighted_nnSVG(input = spe,
                          w = weights,
                          BPPARAM = MulticoreParam(workers = 1, RNGseed = 5))

    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.
    ## Warning in nnSVG(spe[i, ], X = matrix(w[, i]), assay_name =
    ## "weighted_logcounts"): Rows (genes) and/or columns (spots) containing all zero
    ## counts have been found. Please see examples in tutorial for code to filter out
    ## zeros and/or low-expressed genes to avoid errors.

Show results

    # display results
    rowData(spe)

    ## DataFrame with 10 rows and 11 columns
    ##                               gene_id   gene_name    feature_type weighted_mean
    ##                           <character> <character>     <character>     <numeric>
    ## ENSMUSG00000030282 ENSMUSG00000030282        Cmas Gene Expression      2.629505
    ## ENSMUSG00000022601 ENSMUSG00000022601      Zbtb11 Gene Expression      0.954450
    ## ENSMUSG00000040220 ENSMUSG00000040220        Gas8 Gene Expression      0.406293
    ## ENSMUSG00000020704 ENSMUSG00000020704       Asic2 Gene Expression      1.316374
    ## ENSMUSG00000019173 ENSMUSG00000019173       Rab5c Gene Expression      2.813597
    ## ENSMUSG00000042156 ENSMUSG00000042156       Dzip1 Gene Expression      0.921943
    ## ENSMUSG00000050856 ENSMUSG00000050856       Atp5k Gene Expression     40.351649
    ## ENSMUSG00000033594 ENSMUSG00000033594     Spata2l Gene Expression      0.768428
    ## ENSMUSG00000037003 ENSMUSG00000037003        Tns2 Gene Expression      0.366651
    ## ENSMUSG00000026817 ENSMUSG00000026817         Ak1 Gene Expression      2.444117
    ##                    weighted_LR_stat weighted_sigma.sq weighted_tau.sq
    ##                           <numeric>         <numeric>       <numeric>
    ## ENSMUSG00000030282         502.4160         1.2690910        1.478019
    ## ENSMUSG00000022601         186.3100         0.8040484        1.095260
    ## ENSMUSG00000040220           7.3721         0.0168625        0.767206
    ## ENSMUSG00000020704         419.1237         1.4912687        1.197602
    ## ENSMUSG00000019173          33.7229         0.1442682        1.364638
    ## ENSMUSG00000042156          85.8067         0.1382300        1.329137
    ## ENSMUSG00000050856         140.5636         3.4536099       11.766867
    ## ENSMUSG00000033594         351.0450         0.8248543        1.028462
    ## ENSMUSG00000037003         241.3679         0.8982394        0.591474
    ## ENSMUSG00000026817         184.7452         0.2980856        1.477745
    ##                    weighted_prop_sv weighted_phi weighted_padj weighted_rank
    ##                           <numeric>    <numeric>     <numeric>     <numeric>
    ## ENSMUSG00000030282        0.4619732     6.668920   0.00000e+00             1
    ## ENSMUSG00000022601        0.4233374     1.925716   0.00000e+00             5
    ## ENSMUSG00000040220        0.0215065     1.924299   2.50709e-02            10
    ## ENSMUSG00000020704        0.5546078     0.736000   0.00000e+00             2
    ## ENSMUSG00000019173        0.0956111    25.562667   4.75515e-08             9
    ## ENSMUSG00000042156        0.0942028     3.766895   0.00000e+00             8
    ## ENSMUSG00000050856        0.2269055    26.561750   0.00000e+00             7
    ## ENSMUSG00000033594        0.4450694     1.166944   0.00000e+00             3
    ## ENSMUSG00000037003        0.6029612     0.130171   0.00000e+00             4
    ## ENSMUSG00000026817        0.1678570    12.605205   0.00000e+00             6

Other SVG detection tools

We provided a function to use the weights with nnSVG for more accurate spatially variable gene detection. The weights can also be used with other spatially variable gene detection tools using the following procedure:

    assay_name <- "logcounts"
    weighted_logcounts <- t(weights)*assays(spe)[[assay_name]]
    assay(spe, "weighted_logcounts") <- weighted_logcounts

`weighted_logcounts` can be accessed from `assay(spe, "weighted_logcounts")`. Then, `weighted_logcounts` should be used as the input counts matrix and `weights` as the input covariate matrix in a spatially variable detection tool.

# 5 Session information

    sessionInfo()

    ## R version 4.6.0 RC (2026-04-17 r89917)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.4 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
    ## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
    ##
    ## locale:
    ##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
    ##  [3] LC_TIME=en_GB              LC_COLLATE=C
    ##  [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: America/New_York
    ## tzcode source: system (glibc)
    ##
    ## attached base packages:
    ## [1] parallel  stats4    stats     graphics  grDevices utils     datasets
    ## [8] methods   base
    ##
    ## other attached packages:
    ##  [1] spoon_1.9.0                 Matrix_1.7-5
    ##  [3] scuttle_1.23.0              BiocParallel_1.47.0
    ##  [5] BRISC_1.0.6                 pbapply_1.7-4
    ##  [7] rdist_0.0.5                 RANN_2.6.2
    ##  [9] STexampleData_1.19.0        SpatialExperiment_1.23.0
    ## [11] SingleCellExperiment_1.35.0 SummarizedExperiment_1.43.0
    ## [13] Biobase_2.73.0              GenomicRanges_1.65.0
    ## [15] Seqinfo_1.3.0               IRanges_2.47.0
    ## [17] S4Vectors_0.51.0            MatrixGenerics_1.25.0
    ## [19] matrixStats_1.5.0           ExperimentHub_3.3.0
    ## [21] AnnotationHub_4.3.0         BiocFileCache_3.3.0
    ## [23] dbplyr_2.5.2                BiocGenerics_0.59.0
    ## [25] generics_0.1.4              nnSVG_1.17.0
    ## [27] BiocStyle_2.41.0
    ##
    ## loaded via a namespace (and not attached):
    ##  [1] tidyselect_1.2.1     dplyr_1.2.1          blob_1.3.0
    ##  [4] filelock_1.0.3       Biostrings_2.81.0    fastmap_1.2.0
    ##  [7] digest_0.6.39        lifecycle_1.0.5      KEGGREST_1.53.0
    ## [10] RSQLite_2.4.6        magrittr_2.0.5       compiler_4.6.0
    ## [13] rlang_1.2.0          sass_0.4.10          tools_4.6.0
    ## [16] yaml_2.3.12          knitr_1.51           S4Arrays_1.13.0
    ## [19] bit_4.6.0            curl_7.1.0           DelayedArray_0.39.0
    ## [22] abind_1.4-8          withr_3.0.2          purrr_1.2.2
    ## [25] grid_4.6.0           beachmat_2.29.0      cli_3.6.6
    ## [28] rmarkdown_2.31       crayon_1.5.3         otel_0.2.0
    ## [31] httr_1.4.8           rjson_0.2.23         DBI_1.3.0
    ## [34] cachem_1.1.0         AnnotationDbi_1.75.0 BiocManager_1.30.27
    ## [37] XVector_0.53.0       vctrs_0.7.3          jsonlite_2.0.0
    ## [40] bookdown_0.46        bit64_4.8.0          magick_2.9.1
    ## [43] jquerylib_0.1.4      glue_1.8.1           codetools_0.2-20
    ## [46] BiocVersion_3.24.0   tibble_3.3.1         pillar_1.11.1
    ## [49] rappdirs_0.3.4       htmltools_0.5.9      R6_2.6.1
    ## [52] httr2_1.2.2          evaluate_1.0.5       lattice_0.22-9
    ## [55] png_0.1-9            memoise_2.0.1        bslib_0.10.0
    ## [58] Rcpp_1.1.1-1.1       SparseArray_1.13.0   xfun_0.57
    ## [61] pkgconfig_2.0.3