Spatial analysis

Spatial neighborhood-graph statistics and message passing over the physical-distance graph.

class phenocoder.spatial.SpatialGraphAnalyzer(adata, cluster_key, spatial_key, radii, index, stats=None, chull_min_nds=10, chull_min_degree=3)[source]

Bases: object

Analyzer for computing spatial graph statistics on spatial omics data.

This class provides comprehensive analysis of spatial relationships between cells and cell clusters in spatial transcriptomics or imaging data. It constructs spatial neighborhood graphs at multiple radii and computes various statistics including interaction matrices, spatial autocorrelation, centrality scores, connectivity metrics, and convex hull properties.

Typical workflow:
  1. Initialize with AnnData object and analysis parameters

  2. Call run() to compute all statistics across all specified radii

  3. Call to_df() to get results as a single DataFrame

Parameters:
  • adata (ad.AnnData)

  • cluster_key (str)

  • spatial_key (str)

  • radii (tuple[int])

  • index (str)

  • stats (list[str] | None)

  • chull_min_nds (int)

  • chull_min_degree (int)

adata

Annotated data object with spatial coordinates and cluster labels.

Type:

ad.AnnData

cluster_key

Key in adata.obs containing cluster assignments.

Type:

str

spatial_key

Key in adata.obsm containing spatial coordinates.

Type:

str

radii

Tuple of radii (in spatial units) for neighborhood calculations.

Type:

tuple[int]

index

Identifier for this sample/analysis (used as DataFrame index).

Type:

str

stats

Stat groups to compute (subset of VALID_STATS); defaults to all.

Type:

set[str]

chull_min_nds

Minimum nodes per connected component for convex-hull stats.

Type:

int

chull_min_degree

Minimum node degree before extracting convex-hull components.

Type:

int

results

Computed statistics, populated after run() is called.

Type:

dict

Example

>>> analyzer = SpatialGraphAnalyzer(
...     adata=adata_sample,
...     cluster_key='leiden',
...     spatial_key='spatial',
...     radii=(25, 50, 100),
...     index='sample_001'
... )
>>> analyzer.run()
>>> df_stats = analyzer.to_df()
VALID_STATS = ('interactions', 'centrality', 'connectivity', 'moran_features', 'moran_clusters', 'chull')

Stat groups that can be selected via the stats argument.

get_chull(radius=100, degree_threshold=5)[source]

Calculate convex hull volume, area, and density for spatial data.

Operates on self.adata (uses the ‘z’, ‘centroid-0’, ‘centroid-1’ obs columns).

Parameters:
  • radius (int) – Radius for neighbor graph construction. Defaults to 100.

  • degree_threshold (int) – Minimum degree threshold for filtering points. Defaults to 5.

Returns:

DataFrame containing convex hull volume, area, and density metrics.

Return type:

pd.DataFrame

get_chulls_connected_components(clusters, radius=100, min_nds=10, min_degree=4)[source]

Calculate convex hull for connected components in subset of spatial graph.

Parameters:
  • clusters (list) – List of cluster identifiers to include (matched against self.cluster_key).

  • radius (int) – Radius for neighbor graph construction. Defaults to 100.

  • min_nds (int) – Minimum number of nodes for connected components. Defaults to 10.

  • min_degree (int) – Drop nodes with fewer than this many connections before extracting components. Defaults to 3.

Returns:

DataFrame containing convex hull metrics for each connected component.

Return type:

pd.DataFrame

get_interactions()[source]

Calculate interaction matrices between clusters.

Computes both normalized and raw cluster-cluster interaction counts on self.adata (requires spatial neighbors to have been computed).

Returns:

DataFrame containing normalized and raw interaction counts, one row indexed

by self.index.

Return type:

pd.DataFrame

get_moran()[source]

Calculate Moran’s I spatial autocorrelation for features.

Operates on self.adata.var features using the precomputed spatial_connectivities; returns zeros if the graph has no edges.

Returns:

DataFrame containing Moran’s I values for each feature, one row indexed

by self.index.

Return type:

pd.DataFrame

get_moran_cluster()[source]

Calculate Moran’s I spatial autocorrelation for cluster assignments.

One-hot encodes self.cluster_key and computes Moran’s I per cluster using the precomputed spatial_connectivities; returns zeros if the graph has no edges.

Returns:

DataFrame containing Moran’s I values for each cluster, one row indexed

by self.index.

Return type:

pd.DataFrame

get_centrality()[source]

Calculate centrality scores between clusters.

Computes pairwise centrality scores that measure how central each cluster is relative to other clusters in the spatial graph.

Returns:

DataFrame containing centrality scores for each cluster pair, with one row

indexed by self.index and columns named ‘centrality_{from}_{to}’.

Return type:

pd.DataFrame

get_connectivity()[source]

Calculate connectivity statistics (degree) for the spatial graph.

Computes the mean and standard deviation of node degrees (number of neighbors) both globally and per cluster.

Returns:

DataFrame with one row indexed by self.index containing:
  • ’mean’: Mean degree across all nodes

  • ’std’: Standard deviation of degree across all nodes

  • ’mean_degree_{cluster}’: Mean degree for each cluster

  • ’std_degree_{cluster}’: Standard deviation of degree for each cluster

Return type:

pd.DataFrame

get_counts()[source]

Calculate cell counts per cluster.

Computes the number of cells in each cluster and the total number of cells.

Returns:

DataFrame with one row indexed by self.index containing:
  • ’cluster’: Cluster label

  • ’count’: Number of cells in the cluster

  • ’total’: Total number of cells across all clusters

Return type:

pd.DataFrame

get_spatial_stats(radius)[source]

Calculate the selected spatial statistics for a sample.

Only the stat groups in self.stats are computed (see stats in the class constructor).

Parameters:

radius (int) – Radius for spatial neighbor calculations.

Returns:

Mapping of stat-group name to its result DataFrame.

Return type:

dict

to_df()[source]

Collect the computed statistics into a single-row DataFrame.

Concatenates the per-radius, per-stat-group results (populated by run()) into one row indexed by self.index. Column names are prefixed with radius:{radius}_stat:{group}_ so every statistic is traceable to the radius and stat group it came from.

Returns:

One row of spatial statistics for this sample/subunit.

Return type:

pd.DataFrame

run()[source]

Compute all selected spatial statistics across every configured radius.

Populates self.results (a dict keyed by radius) by calling get_spatial_stats() for each radius in self.radii. Call to_df() afterwards to collect the results into a DataFrame.

Returns:

None

Return type:

None

phenocoder.spatial.spatial_message_passing(adata, radius)[source]

Smooth latent representations over a spatial neighborhood graph.

Builds a radius-based spatial connectivity graph in physical space, adds self-loops, and applies a degree-normalized adjacency matrix A' = (A + I) · D⁻¹ to adata.X. Each object’s smoothed latent is the (degree-normalized) average of itself and its spatial neighbors. The result is stored in adata.layers['spatial_message_passing'].

Parameters:
  • adata (ad.AnnData) – Annotated data with latents in .X and spatial coordinates in .obsm['spatial'] (falls back to the ‘x’, ‘y’, ‘z’ obs columns if ‘spatial’ is missing).

  • radius (int) – Radius (in spatial units) used to connect neighboring objects.

Returns:

The same object, with the smoothed latents added as

.layers['spatial_message_passing'].

Return type:

ad.AnnData