14 Spatial Context and Niche Analysis

This mainline extends the analysis map from dissociated single cells back into tissue context. The user question is no longer only which states exist, but where they sit, which compositions change across space, and how niche structure constrains interpretation.

sclet now provides RunSpatialWorkflow() as the semantic shell, with RunSpatialDeconvolution() as the primary backend and RunSpatialColocalization() and RunSpatialNiche() reserved for future integration. All spatial functions now follow the same state contract as other sclet modules: each runs records a name identifier, writes into the typed state record layer, and logs the command for downstream provenance tracking.

14.1 Spatial Mainline Workflow

The simplest entry point is the workflow shell. It dispatches deconvolution through cell2location and reserves colocalization and niche slots.

library(sclet)
# sce_spatial: spatial SingleCellExperiment with 'counts' assay
# sce_ref: reference scRNA-seq SCE with cell type labels

sce_spatial <- RunSpatialWorkflow(
    sce_spatial,
    sce_ref = sce_ref,
    ref_group_key = "cell_type",
    run_deconvolution = TRUE,
    name = "my_spatial_analysis"
)

# Check the active spatial state
has_spatial(sce_spatial)
get_spatial(sce_spatial)

14.2 High-Resolution Spatial Deconvolution: Cell2location

cell2location is widely recognized as the “gold standard” for spatial deconvolution. It uses a negative binomial regression model to map scRNA-seq reference data onto spatial spots, inferring the true absolute abundance of each cell type within a given location.

In sclet, this complex Bayesian inference is simplified into a single line of code:

To run this locally, the minimum prerequisites are a spatial object with a counts assay, a reference SCE with the same gene space, and a biologically meaningful cell-type column in colData(sce_ref). The name parameter lets you tag each run so that multiple deconvolution results can coexist in the same object.

library(sclet)
# Assume 'sce_spatial' is your spatial transcriptomics object, 
# and 'sce_ref' is your single-cell reference object with a "cell_type" column.

sce_spatial <- RunSpatialDeconvolution(
  sce_spatial, 
  sce_ref, 
  ref_group_key = "cell_type",
  max_epochs = 50,
  name = "my_deconv"
)

Under the Hood: The function invokes the GPU (if available) via the basilisk environment to train the model. Once complete, the predicted abundances for each cell type are directly appended to colData(sce_spatial), and the spatial state is activated through sclet_set_analysis_state(). Each run is also logged via sclet_log_command(), so CommandLog(sce_spatial) will show the parameters and timestamp. You can immediately use spatial plotting functions to visualize the distribution of specific cell types across the tissue slice.

14.3 Spatial Colocalization with SVP

RunSpatialColocalization() uses the SVP package’s global bivariate spatial autocorrelation (runGLOBALBV()) to test whether pairs of features tend to co-occur in the same spatial regions. The cached object below already contains the precomputed result, so bookdown only reads the saved object.

spe <- readRDS("data/visium-dlpfc-sub.rds")
top_features <- S4Vectors::metadata(spe)$sclet_bookdown_cache$top_features
## Loading required namespace: SpatialExperiment
top_features
## [1] "ENSG00000186468" "ENSG00000228253" "ENSG00000157152"
## [4] "ENSG00000133083" "ENSG00000058404"
has_colocalization(spe)
## [1] FALSE

14.4 Spatial Niche Detection with SVP LISA

RunSpatialNiche() uses SVP’s Local Indicators of Spatial Association (LISA). The cached object below already contains the precomputed LISA result.

has_spatial_niche(spe)
## [1] FALSE

14.5 Inspecting Spatial Results Across Layers

Because all spatial functions now register through the unified state layer, a single accessor pattern works for deconvolution, colocalization, and niche results alike. When you keep multiple spatial records, use id to address a specific one.

# The default spatial record (active state)
has_spatial(sce)
get_spatial(sce, element = "columns")

# A specific spatial record by id
get_spatial(sce, id = "my_deconv")

# Colocalization and niche records have their own accessors
has_colocalization(sce)
get_colocalization(sce, element = "status")

has_spatial_niche(sce)

14.6 In-Silico Gene Perturbation: CellOracle

Differential expression tells you “what changed,” SCENIC tells you “who is controlling,” but CellOracle allows you to conduct thought experiments (In-silico Perturbation).

Imagine asking: If I knockout the GATA2 gene in hematopoietic stem cells, how will their developmental trajectory shift? Performing this in vitro is costly and time-consuming. In sclet, it takes only a few minutes of simulation.

This simulation only makes sense after you have already constructed a compatible GRN prior and a dimensional representation that captures the state manifold you want to perturb.

# Assume 'sce' has already been processed through SCENIC (to obtain a GRN), 
# has an active dimensional reduction space, and you have exported a base GRN file.
base_grn_path <- "path/to/celloracle_base_grn.csv"

sce <- RunInSilicoPerturbation(
  sce, 
  target_gene = "GATA2", 
  perturbation_value = 0.0, # Simulate a full knockout
  base_grn_path = base_grn_path,
  cluster_key = "cell_type",
  reduction = "PCA"
)

# Inspect the perturbation state
get_perturbation(sce)

The Impact: The inferred cellular shift vectors are carefully recorded. When combined with streamplots or vector field visualizations, you can intuitively observe on the UMAP space: in a parallel universe without GATA2, which terminal state are these cells “fleeing” from, and which incorrect lineage are they “rushing” toward?