Clustering via BANKSY matrix construction

By default, runBanksyPCA uses a lazy linear operator that computes PCA without materializing the full BANKSY matrix. This is memory-efficient and recommended for most use cases.

However, some workflows require explicit construction of the BANKSY matrix — for example, when using the azimuthal Gabor filter (AGF), when access to the intermediate matrices is needed, or when using higher harmonics (M > 0). This vignette demonstrates the standard workflow using computeBanksy followed by runBanksyPCA(lazy=FALSE).

library(Banksy)

library(SummarizedExperiment)
library(SpatialExperiment)
library(scuttle)

library(scater)
library(cowplot)
library(ggplot2)

Data preprocessing

We use the mouse hippocampus dataset provided with Banksy.

data(hippocampus)
gcm <- hippocampus$expression
locs <- as.matrix(hippocampus$locations)

Initialize a SpatialExperiment object and perform quality control and normalization.

se <- SpatialExperiment(assay = list(counts = gcm), spatialCoords = locs)

# QC based on total counts
qcstats <- perCellQCMetrics(se)
#> Warning in .per_cell_qc_metrics(assay(x, assay.type), subsets = subsets, : 'perCellQCMetrics' is deprecated.
#> Use 'scrapper::computeRnaQcMetrics' instead.
#> See help("Deprecated")
thres <- quantile(qcstats$total, c(0.05, 0.98))
keep <- (qcstats$total > thres[1]) & (qcstats$total < thres[2])
se <- se[, keep]

# Normalization to mean library size
se <- computeLibraryFactors(se)
#> Warning in .library_size_factors(assay(x, assay.type), ...): 'librarySizeFactors' is deprecated.
#> Use 'scrapper::centerSizeFactors' instead.
#> See help("Deprecated")
aname <- "normcounts"
assay(se, aname) <- normalizeCounts(se, log = FALSE)
#> Warning in .local(x, ...): 'normalizeCounts' is deprecated.
#> Use 'scrapper::normalizeCounts' instead.
#> See help("Deprecated")

Computing the BANKSY neighborhood matrices

computeBanksy computes the neighborhood matrices and stores them as assays in the SpatialExperiment object. Setting compute_agf=TRUE computes both the weighted neighborhood mean (\(\mathcal{M}\)) and the azimuthal Gabor filter (\(\mathcal{G}\)). The number of spatial neighbors used to compute \(\mathcal{M}\) and \(\mathcal{G}\) are k_geom[1]=15 and k_geom[2]=30 respectively.

k_geom <- c(15, 30)
se <- computeBanksy(se, assay_name = aname, compute_agf = TRUE, k_geom = k_geom)
#> Computing neighbors...
#> Spatial mode is kNN_median
#> Parameters: k_geom=15
#> Done
#> Computing neighbors...
#> Spatial mode is kNN_median
#> Parameters: k_geom=30
#> Done
#> Computing harmonic m = 0 with 15 neighbors
#> Done
#> Computing harmonic m = 1 with 30 neighbors
#> Centering
#> Done

After running computeBanksy, the neighborhood matrices are accessible as assays:

assayNames(se)
#> [1] "counts"     "normcounts" "H0"         "H1"

PCA, clustering, and visualization

We run PCA with lazy=FALSE to use the standard path, which constructs the full BANKSY matrix via getBanksyMatrix. Setting use_agf=TRUE uses both \(\mathcal{M}\) and \(\mathcal{G}\). We compare non-spatial clustering (lambda=0) with BANKSY cell-typing (lambda=0.2).

lambda <- c(0, 0.2)
set.seed(1000)
se <- runBanksyPCA(se, use_agf = TRUE, lambda = lambda, lazy = FALSE)
se <- runBanksyUMAP(se, use_agf = TRUE, lambda = lambda)
se <- clusterBanksy(se, use_agf = TRUE, lambda = lambda, resolution = 1.2)
se <- connectClusters(se)
#> clust_M1_lam0.2_k50_res1.2 --> clust_M1_lam0_k50_res1.2

Visualise the clustering output:

cnames <- colnames(colData(se))
cnames <- cnames[grep("^clust", cnames)]
colData(se) <- cbind(colData(se), spatialCoords(se))

plot_nsp <- plotColData(se,
    x = "sdimx", y = "sdimy",
    point_size = 0.6, colour_by = cnames[1]
)
plot_bank <- plotColData(se,
    x = "sdimx", y = "sdimy",
    point_size = 0.6, colour_by = cnames[2]
)

plot_grid(plot_nsp + coord_equal(), plot_bank + coord_equal(), ncol = 2)

Accessing the BANKSY matrix

The full BANKSY matrix can be retrieved with getBanksyMatrix. This is useful for custom downstream analyses.

banksy_matrix <- getBanksyMatrix(se, M = 1, lambda = 0.2,
                                  assay_name = aname, scale = TRUE)
dim(banksy_matrix)
#> [1]   360 10205
Session information
options(width = 120)
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.6.1 (2026-06-24)
#>  os       Ubuntu 26.04.1 LTS
#>  system   x86_64, linux-gnu
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Etc/UTC
#>  date     2026-09-30
#>  pandoc   3.8.3 @ /usr/local/bin/ (via rmarkdown)
#>  quarto   1.9.37 @ /usr/local/bin/quarto
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────────────────────────────────────────────
#>  package              * version  date (UTC) lib source
#>  abind                  1.4-8    2024-09-12 [2] RSPM (R 4.6.0)
#>  aricode                1.1.0    2026-05-13 [2] RSPM (R 4.6.0)
#>  Banksy               * 1.9.4    2026-09-29 [1] https://bioc.r-universe.dev (R 4.6.1)
#>  beachmat               2.29.3   2026-09-23 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  beeswarm               0.4.0    2021-06-01 [2] RSPM (R 4.6.0)
#>  Biobase              * 2.73.2   2026-07-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  BiocGenerics         * 0.59.12  2026-08-10 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  BiocManager            1.30.27  2025-11-14 [2] RSPM (R 4.6.0)
#>  BiocNeighbors          2.7.3    2026-08-21 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  BiocParallel           1.47.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  BiocSingular           1.29.1   2026-08-27 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  BiocStyle            * 2.41.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  bslib                  0.12.0   2026-08-04 [2] RSPM (R 4.6.0)
#>  buildtools             1.0.0    2026-09-15 [3] local (/pkg)
#>  cachem                 1.1.0    2024-05-16 [2] RSPM (R 4.6.0)
#>  cli                    3.6.6    2026-04-09 [2] RSPM (R 4.6.0)
#>  codetools              0.2-20   2024-03-31 [2] RSPM (R 4.6.0)
#>  cowplot              * 1.2.0    2025-07-07 [2] RSPM (R 4.6.0)
#>  data.table             1.18.6.1 2026-08-24 [2] RSPM (R 4.6.0)
#>  dbscan                 1.2.6    2026-08-25 [2] RSPM (R 4.6.0)
#>  DelayedArray           0.39.7   2026-09-23 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  dichromat              2.0-1    2026-07-22 [2] RSPM (R 4.6.0)
#>  digest                 0.6.39   2025-11-19 [2] RSPM (R 4.6.0)
#>  dplyr                  1.2.1    2026-04-03 [2] RSPM (R 4.6.0)
#>  evaluate               1.0.5    2025-08-27 [2] RSPM (R 4.6.0)
#>  farver                 2.1.2    2024-05-13 [2] RSPM (R 4.6.0)
#>  fastmap                1.2.0    2024-05-15 [2] RSPM (R 4.6.0)
#>  generics             * 0.1.4    2025-05-09 [2] RSPM (R 4.6.0)
#>  GenomicRanges        * 1.65.4   2026-09-02 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  ggbeeswarm             0.7.3    2025-11-29 [2] RSPM (R 4.6.0)
#>  ggplot2              * 4.0.3    2026-04-22 [2] RSPM (R 4.6.0)
#>  ggrepel                0.9.8    2026-03-17 [2] RSPM (R 4.6.0)
#>  glue                   1.8.1    2026-04-17 [2] RSPM (R 4.6.0)
#>  gridExtra              2.3.1    2026-06-25 [2] RSPM (R 4.6.0)
#>  gtable                 0.3.6    2024-10-25 [2] RSPM (R 4.6.0)
#>  htmltools              0.5.9    2025-12-04 [2] RSPM (R 4.6.0)
#>  igraph                 2.3.3    2026-06-26 [2] RSPM (R 4.6.0)
#>  IRanges              * 2.47.5   2026-08-27 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  irlba                  2.3.7    2026-01-30 [2] RSPM (R 4.6.0)
#>  jquerylib              0.1.4    2021-04-26 [2] RSPM (R 4.6.0)
#>  jsonlite               2.0.0    2025-03-27 [2] RSPM (R 4.6.0)
#>  knitr                  1.52     2026-09-06 [2] RSPM (R 4.6.0)
#>  labeling               0.4.3    2023-08-29 [2] RSPM (R 4.6.0)
#>  lattice                0.23-1   2026-08-12 [2] RSPM (R 4.6.0)
#>  leidenAlg              1.1.8    2026-05-31 [2] RSPM (R 4.6.0)
#>  lifecycle              1.0.5    2026-01-08 [2] RSPM (R 4.6.0)
#>  magick                 2.9.1    2026-02-28 [2] RSPM (R 4.6.0)
#>  magrittr               2.0.5    2026-04-04 [2] RSPM (R 4.6.0)
#>  maketools              1.3.2    2025-01-25 [3] RSPM (R 4.6.0)
#>  Matrix                 1.7-6    2026-07-25 [2] RSPM (R 4.6.0)
#>  MatrixGenerics       * 1.25.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  matrixStats          * 1.5.0    2025-01-07 [2] RSPM (R 4.6.0)
#>  mclust                 6.1.3    2026-07-05 [2] RSPM (R 4.6.0)
#>  otel                   0.2.0    2025-08-29 [2] RSPM (R 4.6.0)
#>  pillar                 1.11.1   2025-09-17 [2] RSPM (R 4.6.0)
#>  pkgconfig              2.0.3    2019-09-22 [2] RSPM (R 4.6.0)
#>  R6                     2.6.1    2025-02-15 [2] RSPM (R 4.6.0)
#>  RColorBrewer           1.1-3    2022-04-03 [2] RSPM (R 4.6.0)
#>  Rcpp                   1.1.2    2026-07-05 [2] RSPM (R 4.6.0)
#>  RcppAnnoy              0.0.23   2026-01-12 [2] RSPM (R 4.6.0)
#>  RcppHungarian          0.3      2023-09-05 [2] RSPM (R 4.6.0)
#>  rjson                  0.2.23   2024-09-16 [2] RSPM (R 4.6.0)
#>  rlang                  1.3.0    2026-07-05 [2] RSPM (R 4.6.0)
#>  rmarkdown              2.32     2026-09-01 [2] RSPM (R 4.6.0)
#>  RSpectra               0.16-2   2024-07-18 [2] RSPM (R 4.6.0)
#>  rsvd                   1.0.5    2021-04-16 [2] RSPM (R 4.6.0)
#>  S4Arrays               1.13.1   2026-09-22 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  S4Vectors            * 0.51.10  2026-09-15 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  S7                     0.2.2    2026-04-22 [2] RSPM (R 4.6.0)
#>  sass                   0.4.10   2025-04-11 [2] RSPM (R 4.6.0)
#>  ScaledMatrix           1.21.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  scales                 1.4.0    2025-04-24 [2] RSPM (R 4.6.0)
#>  scater               * 1.41.2   2026-06-30 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  sccore                 1.0.7    2026-04-06 [2] RSPM (R 4.6.0)
#>  scuttle              * 1.23.2   2026-08-27 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  Seqinfo              * 1.3.2    2026-08-26 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  sessioninfo            1.2.4    2026-06-04 [2] RSPM (R 4.6.0)
#>  SingleCellExperiment * 1.35.2   2026-07-16 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  SparseArray            1.13.3   2026-09-24 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  SpatialExperiment    * 1.23.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  SummarizedExperiment * 1.43.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  sys                    3.4.3    2024-10-04 [2] RSPM (R 4.6.0)
#>  tibble                 3.3.1    2026-01-11 [2] RSPM (R 4.6.0)
#>  tidyselect             1.2.1    2024-03-11 [2] RSPM (R 4.6.0)
#>  uwot                   0.2.5    2026-08-29 [2] RSPM (R 4.6.0)
#>  vctrs                  0.7.3    2026-04-11 [2] RSPM (R 4.6.0)
#>  vipor                  0.4.7    2023-12-18 [2] RSPM (R 4.6.0)
#>  viridis                0.6.5    2024-01-29 [2] RSPM (R 4.6.0)
#>  viridisLite            0.4.3    2026-02-04 [2] RSPM (R 4.6.0)
#>  withr                  3.0.3    2026-06-19 [2] RSPM (R 4.6.0)
#>  xfun                   0.61     2026-09-16 [2] RSPM (R 4.6.0)
#>  XVector                0.53.0   2026-04-28 [2] https://bioc.r-universe.dev (R 4.6.1)
#>  yaml                   2.3.12   2025-12-10 [2] RSPM (R 4.6.0)
#> 
#>  [1] /tmp/Rtmpe0t8Qn/Rinst279a59bf5c95
#>  [2] /github/workspace/pkglib
#>  [3] /usr/local/lib/R/site-library
#>  [4] /usr/lib/R/site-library
#>  [5] /usr/lib/R/library
#>  * ── Packages attached to the search path.
#> 
#> ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────