Here, we demonstrate a grid search
of clustering parameters with a mouse hippocampus VeraFISH dataset.
BANKSY currently provides four algorithms for clustering the
BANKSY matrix with clusterBanksy: Leiden (default), Louvain,
k-means, and model-based clustering. In this vignette, we run only
Leiden clustering. See ?clusterBanksy
for more details on
the parameters for different clustering methods.
The dataset comprises gene expression for 10,944 cells and 120 genes
in 2 spatial dimensions. See ?Banksy::hippocampus
for more
details.
# Load libs
library(Banksy)
library(SummarizedExperiment)
library(SpatialExperiment)
library(scuttle)
library(scater)
library(cowplot)
library(ggplot2)
# Load data
data(hippocampus)
gcm <- hippocampus$expression
locs <- as.matrix(hippocampus$locations)
Here, gcm
is a gene by cell matrix, and
locs
is a matrix specifying the coordinates of the centroid
for each cell.
head(gcm[,1:5])
#> cell_1276 cell_8890 cell_691 cell_396 cell_9818
#> Sparcl1 45 0 11 22 0
#> Slc1a2 17 0 6 5 0
#> Map 10 0 12 16 0
#> Sqstm1 26 0 0 2 0
#> Atp1a2 0 0 4 3 0
#> Tnc 0 0 0 0 0
head(locs)
#> sdimx sdimy
#> cell_1276 -13372.899 15776.37
#> cell_8890 8941.101 15866.37
#> cell_691 -14882.899 15896.37
#> cell_396 -15492.899 15835.37
#> cell_9818 11308.101 15846.37
#> cell_11310 14894.101 15810.37
Initialize a SpatialExperiment object and perform basic quality control. We keep cells with total transcript count within the 5th and 98th percentile:
se <- SpatialExperiment(assay = list(counts = gcm), spatialCoords = locs)
colData(se) <- cbind(colData(se), spatialCoords(se))
# QC based on total counts
qcstats <- perCellQCMetrics(se)
thres <- quantile(qcstats$total, c(0.05, 0.98))
keep <- (qcstats$total > thres[1]) & (qcstats$total < thres[2])
se <- se[, keep]
Next, perform normalization of the data.
BANKSY has a few key parameters. We describe these below.
For characterising neighborhoods, BANKSY computes the
weighted neighborhood mean (H_0
) and the azimuthal Gabor
filter (H_1
), which estimates gene expression gradients.
Setting compute_agf=TRUE
computes both H_0
and
H_1
.
k_geom
specifies the number of neighbors used to compute
each H_m
for m=0,1
. If a single value is
specified, the same k_geom
will be used for each feature
matrix. Alternatively, multiple values of k_geom
can be
provided for each feature matrix. Here, we use k_geom[1]=15
and k_geom[2]=30
for H_0
and H_1
respectively. More neighbors are used to compute gradients.
We compute the neighborhood feature matrices using normalized
expression (normcounts
in the se
object).
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
#> Using 15 neighbors
#> Done
#> Computing harmonic m = 1
#> Using 30 neighbors
#> Centering
#> Done
computeBanksy
populates the assays
slot
with H_0
and H_1
in this instance:
se
#> class: SpatialExperiment
#> dim: 120 10205
#> metadata(1): BANKSY_params
#> assays(4): counts normcounts H0 H1
#> rownames(120): Sparcl1 Slc1a2 ... Notch3 Egfr
#> rowData names(0):
#> colnames(10205): cell_1276 cell_691 ... cell_11635 cell_10849
#> colData names(4): sample_id sdimx sdimy sizeFactor
#> reducedDimNames(0):
#> mainExpName: NULL
#> altExpNames(0):
#> spatialCoords names(2) : sdimx sdimy
#> imgData names(1): sample_id
The lambda
parameter is a mixing parameter in
[0,1]
which determines how much spatial information is
incorporated for downstream analysis. With smaller values of
lambda
, BANKY operates in cell-typing mode, while
at higher levels of lambda
, BANKSY operates in
domain-finding mode. As a starting point, we recommend
lambda=0.2
for cell-typing and lambda=0.8
for
zone-finding. Here, we run lambda=0
which corresponds to
non-spatial clustering, and lambda=0.2
for
spatially-informed cell-typing. We compute PCs with and without the AGF
(H_1
).
lambda <- c(0, 0.2)
se <- runBanksyPCA(se, use_agf = c(FALSE, TRUE), lambda = lambda, seed = 1000)
#> Using seed=1000
#> Using seed=1000
#> Using seed=1000
#> Using seed=1000
runBanksyPCA
populates the reducedDims
slot, with each combination of use_agf
and
lambda
provided.
Next, we cluster the BANKSY embedding with Leiden graph-based
clustering. This admits two parameters: k_neighbors
and
resolution
. k_neighbors
determines the number
of k nearest neighbors used to construct the shared nearest neighbors
graph. Leiden clustering is then performed on the resultant graph with
resolution resolution
. For reproducibiltiy we set a seed
for each parameter combination.
k <- 50
res <- 1
se <- clusterBanksy(se, use_agf = c(FALSE, TRUE), lambda = lambda, k_neighbors = k, resolution = res, seed = 1000)
#> Using seed=1000
#> Using seed=1000
#> Using seed=1000
#> Using seed=1000
clusterBanksy
populates colData(se)
with
cluster labels:
To compare clustering runs visually, different runs can be relabeled
to minimise their differences with connectClusters
:
se <- connectClusters(se)
#> clust_M1_lam0_k50_res1 --> clust_M0_lam0_k50_res1
#> clust_M0_lam0.2_k50_res1 --> clust_M1_lam0_k50_res1
#> clust_M1_lam0.2_k50_res1 --> clust_M0_lam0.2_k50_res1
Visualise spatial coordinates with cluster labels.
cnames <- colnames(colData(se))
cnames <- cnames[grep("^clust", cnames)]
cplots <- lapply(cnames, function(cnm) {
plotColData(se, x = "sdimx", y = "sdimy", point_size = 0.1, colour_by = cnm) +
coord_equal() +
labs(title = cnm) +
theme(legend.title = element_blank()) +
guides(colour = guide_legend(override.aes = list(size = 2)))
})
plot_grid(plotlist = cplots, ncol = 2)
Compare all cluster outputs with compareClusters
. This
function computes pairwise cluster comparison metrics between the
clusters in colData(se)
based on adjusted Rand index
(ARI):
compareClusters(se, func = "ARI")
#> clust_M0_lam0_k50_res1 clust_M0_lam0.2_k50_res1
#> clust_M0_lam0_k50_res1 1.000 0.67
#> clust_M0_lam0.2_k50_res1 0.670 1.00
#> clust_M1_lam0_k50_res1 1.000 0.67
#> clust_M1_lam0.2_k50_res1 0.747 0.87
#> clust_M1_lam0_k50_res1 clust_M1_lam0.2_k50_res1
#> clust_M0_lam0_k50_res1 1.000 0.747
#> clust_M0_lam0.2_k50_res1 0.670 0.870
#> clust_M1_lam0_k50_res1 1.000 0.747
#> clust_M1_lam0.2_k50_res1 0.747 1.000
or normalized mutual information (NMI):
compareClusters(se, func = "NMI")
#> clust_M0_lam0_k50_res1 clust_M0_lam0.2_k50_res1
#> clust_M0_lam0_k50_res1 1.000 0.741
#> clust_M0_lam0.2_k50_res1 0.741 1.000
#> clust_M1_lam0_k50_res1 1.000 0.741
#> clust_M1_lam0.2_k50_res1 0.782 0.915
#> clust_M1_lam0_k50_res1 clust_M1_lam0.2_k50_res1
#> clust_M0_lam0_k50_res1 1.000 0.782
#> clust_M0_lam0.2_k50_res1 0.741 0.915
#> clust_M1_lam0_k50_res1 1.000 0.782
#> clust_M1_lam0.2_k50_res1 0.782 1.000
See ?compareClusters
for the full list of comparison
measures.
Vignette runtime:
#> Time difference of 30.7257 secs
sessionInfo()
#> R version 4.4.2 (2024-10-31)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.1 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=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 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: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] ExperimentHub_2.15.0 AnnotationHub_3.15.0
#> [3] BiocFileCache_2.15.0 dbplyr_2.5.0
#> [5] spatialLIBD_1.18.0 cowplot_1.1.3
#> [7] scater_1.35.0 ggplot2_3.5.1
#> [9] harmony_1.2.3 Rcpp_1.0.13-1
#> [11] data.table_1.16.2 scran_1.35.0
#> [13] scuttle_1.17.0 Seurat_5.1.0
#> [15] SeuratObject_5.0.2 sp_2.1-4
#> [17] SpatialExperiment_1.17.0 SingleCellExperiment_1.29.1
#> [19] SummarizedExperiment_1.37.0 Biobase_2.67.0
#> [21] GenomicRanges_1.59.1 GenomeInfoDb_1.43.2
#> [23] IRanges_2.41.1 S4Vectors_0.45.2
#> [25] BiocGenerics_0.53.3 generics_0.1.3
#> [27] MatrixGenerics_1.19.0 matrixStats_1.4.1
#> [29] Banksy_1.3.0 BiocStyle_2.35.0
#>
#> loaded via a namespace (and not attached):
#> [1] bitops_1.0-9 spatstat.sparse_3.1-0 doParallel_1.0.17
#> [4] httr_1.4.7 RColorBrewer_1.1-3 tools_4.4.2
#> [7] sctransform_0.4.1 DT_0.33 utf8_1.2.4
#> [10] R6_2.5.1 lazyeval_0.2.2 uwot_0.2.2
#> [13] withr_3.0.2 gridExtra_2.3 progressr_0.15.1
#> [16] cli_3.6.3 spatstat.explore_3.3-3 fastDummies_1.7.4
#> [19] labeling_0.4.3 sass_0.4.9 spatstat.data_3.1-4
#> [22] ggridges_0.5.6 pbapply_1.7-2 Rsamtools_2.23.1
#> [25] dbscan_1.2-0 aricode_1.0.3 dichromat_2.0-0.1
#> [28] sessioninfo_1.2.2 parallelly_1.39.0 attempt_0.3.1
#> [31] maps_3.4.2.1 limma_3.63.2 pals_1.9
#> [34] RSQLite_2.3.8 BiocIO_1.17.1 ica_1.0-3
#> [37] spatstat.random_3.3-2 dplyr_1.1.4 Matrix_1.7-1
#> [40] ggbeeswarm_0.7.2 fansi_1.0.6 abind_1.4-8
#> [43] lifecycle_1.0.4 yaml_2.3.10 edgeR_4.5.0
#> [46] SparseArray_1.7.2 Rtsne_0.17 paletteer_1.6.0
#> [49] grid_4.4.2 blob_1.2.4 promises_1.3.2
#> [52] dqrng_0.4.1 crayon_1.5.3 miniUI_0.1.1.1
#> [55] lattice_0.22-6 beachmat_2.23.2 mapproj_1.2.11
#> [58] KEGGREST_1.47.0 magick_2.8.5 sys_3.4.3
#> [61] maketools_1.3.1 pillar_1.9.0 knitr_1.49
#> [64] metapod_1.15.0 rjson_0.2.23 future.apply_1.11.3
#> [67] codetools_0.2-20 leiden_0.4.3.1 glue_1.8.0
#> [70] spatstat.univar_3.1-1 vctrs_0.6.5 png_0.1-8
#> [73] spam_2.11-0 gtable_0.3.6 rematch2_2.1.2
#> [76] cachem_1.1.0 xfun_0.49 S4Arrays_1.7.1
#> [79] mime_0.12 survival_3.7-0 RcppHungarian_0.3
#> [82] iterators_1.0.14 fields_16.3 statmod_1.5.0
#> [85] bluster_1.17.0 fitdistrplus_1.2-1 ROCR_1.0-11
#> [88] nlme_3.1-166 bit64_4.5.2 filelock_1.0.3
#> [91] RcppAnnoy_0.0.22 bslib_0.8.0 irlba_2.3.5.1
#> [94] vipor_0.4.7 KernSmooth_2.23-24 colorspace_2.1-1
#> [97] DBI_1.2.3 tidyselect_1.2.1 bit_4.5.0
#> [100] compiler_4.4.2 curl_6.0.1 BiocNeighbors_2.1.1
#> [103] DelayedArray_0.33.2 plotly_4.10.4 rtracklayer_1.67.0
#> [106] scales_1.3.0 lmtest_0.9-40 rappdirs_0.3.3
#> [109] stringr_1.5.1 digest_0.6.37 goftest_1.2-3
#> [112] spatstat.utils_3.1-1 rmarkdown_2.29 benchmarkmeData_1.0.4
#> [115] RhpcBLASctl_0.23-42 XVector_0.47.0 htmltools_0.5.8.1
#> [118] pkgconfig_2.0.3 fastmap_1.2.0 rlang_1.1.4
#> [121] htmlwidgets_1.6.4 UCSC.utils_1.3.0 shiny_1.9.1
#> [124] farver_2.1.2 jquerylib_0.1.4 zoo_1.8-12
#> [127] jsonlite_1.8.9 BiocParallel_1.41.0 mclust_6.1.1
#> [130] config_0.3.2 RCurl_1.98-1.16 BiocSingular_1.23.0
#> [133] magrittr_2.0.3 GenomeInfoDbData_1.2.13 dotCall64_1.2
#> [136] patchwork_1.3.0 munsell_0.5.1 viridis_0.6.5
#> [139] reticulate_1.40.0 leidenAlg_1.1.4 stringi_1.8.4
#> [142] zlibbioc_1.52.0 MASS_7.3-61 plyr_1.8.9
#> [145] parallel_4.4.2 listenv_0.9.1 ggrepel_0.9.6
#> [148] deldir_2.0-4 Biostrings_2.75.1 sccore_1.0.5
#> [151] splines_4.4.2 tensor_1.5 locfit_1.5-9.10
#> [154] igraph_2.1.1 spatstat.geom_3.3-4 RcppHNSW_0.6.0
#> [157] buildtools_1.0.0 reshape2_1.4.4 ScaledMatrix_1.15.0
#> [160] XML_3.99-0.17 BiocVersion_3.21.1 evaluate_1.0.1
#> [163] golem_0.5.1 BiocManager_1.30.25 foreach_1.5.2
#> [166] httpuv_1.6.15 RANN_2.6.2 tidyr_1.3.1
#> [169] purrr_1.0.2 polyclip_1.10-7 benchmarkme_1.0.8
#> [172] future_1.34.0 scattermore_1.2 rsvd_1.0.5
#> [175] xtable_1.8-4 restfulr_0.0.15 RSpectra_0.16-2
#> [178] later_1.4.1 viridisLite_0.4.2 tibble_3.2.1
#> [181] GenomicAlignments_1.43.0 memoise_2.0.1 beeswarm_0.4.0
#> [184] AnnotationDbi_1.69.0 cluster_2.1.6 shinyWidgets_0.8.7
#> [187] globals_0.16.3