Building spatial neighbors graph

This example shows how to compute a spatial neighbors graph.

Spatial graph is a graph of spatial neighbors with observations as nodes and neighbor-hood relations between observations as edges. We use spatial coordinates of spots/cells to identify neighbors among them. Different approach of defining a neighborhood relation among observations are used for different types of spatial datasets.

import squidpy as sq

import numpy as np

First, we show how to compute the spatial neighbors graph for a Visium dataset.

adata = sq.datasets.visium_fluo_adata()
adata

Out:

  0%|          | 0.00/242M [00:00<?, ?B/s]
  0%|          | 24.0k/242M [00:00<23:11, 183kB/s]
  0%|          | 56.0k/242M [00:00<19:33, 216kB/s]
  0%|          | 120k/242M [00:00<12:34, 337kB/s]
  0%|          | 224k/242M [00:00<08:16, 510kB/s]
  0%|          | 464k/242M [00:00<04:19, 977kB/s]
  0%|          | 920k/242M [00:00<02:19, 1.81MB/s]
  1%|          | 1.78M/242M [00:00<01:13, 3.44MB/s]
  1%|1         | 3.57M/242M [00:01<00:37, 6.72MB/s]
  3%|2         | 6.55M/242M [00:01<00:21, 11.8MB/s]
  4%|3         | 9.48M/242M [00:01<00:16, 15.1MB/s]
  5%|5         | 12.5M/242M [00:01<00:13, 17.5MB/s]
  6%|6         | 15.5M/242M [00:01<00:12, 19.2MB/s]
  8%|7         | 18.5M/242M [00:01<00:11, 20.3MB/s]
  9%|8         | 21.3M/242M [00:01<00:11, 20.9MB/s]
 10%|9         | 23.7M/242M [00:02<00:11, 20.1MB/s]
 11%|#         | 26.6M/242M [00:02<00:10, 20.7MB/s]
 12%|#2        | 29.5M/242M [00:02<00:10, 21.2MB/s]
 13%|#3        | 32.5M/242M [00:02<00:10, 21.7MB/s]
 15%|#4        | 35.4M/242M [00:02<00:09, 21.8MB/s]
 16%|#5        | 38.2M/242M [00:02<00:09, 21.8MB/s]
 17%|#6        | 41.1M/242M [00:02<00:09, 22.0MB/s]
 18%|#8        | 43.7M/242M [00:03<00:09, 21.4MB/s]
 19%|#9        | 46.7M/242M [00:03<00:09, 21.8MB/s]
 20%|##        | 49.6M/242M [00:03<00:09, 22.0MB/s]
 22%|##1       | 52.6M/242M [00:03<00:08, 22.2MB/s]
 23%|##2       | 55.5M/242M [00:03<00:08, 22.2MB/s]
 24%|##4       | 58.3M/242M [00:03<00:08, 22.1MB/s]
 25%|##5       | 61.2M/242M [00:03<00:08, 22.1MB/s]
 26%|##6       | 64.2M/242M [00:03<00:08, 22.3MB/s]
 28%|##7       | 67.0M/242M [00:04<00:08, 22.2MB/s]
 29%|##8       | 70.0M/242M [00:04<00:08, 22.3MB/s]
 30%|###       | 72.9M/242M [00:04<00:07, 23.6MB/s]
 31%|###1      | 75.4M/242M [00:04<00:07, 24.1MB/s]
 32%|###1      | 76.5M/242M [00:04<00:08, 20.5MB/s]
 33%|###2      | 79.2M/242M [00:04<00:07, 22.4MB/s]
 34%|###3      | 81.4M/242M [00:04<00:07, 22.6MB/s]
 35%|###4      | 84.1M/242M [00:04<00:06, 24.1MB/s]
 35%|###5      | 85.3M/242M [00:04<00:07, 20.8MB/s]
 36%|###6      | 87.9M/242M [00:05<00:07, 22.7MB/s]
 37%|###7      | 90.1M/242M [00:05<00:07, 22.3MB/s]
 38%|###8      | 92.4M/242M [00:05<00:06, 22.6MB/s]
 39%|###8      | 93.9M/242M [00:05<00:07, 20.1MB/s]
 40%|###9      | 96.9M/242M [00:05<00:07, 21.0MB/s]
 41%|####1     | 99.8M/242M [00:05<00:06, 21.5MB/s]
 42%|####2     | 103M/242M [00:05<00:07, 20.6MB/s]
 44%|####3     | 106M/242M [00:05<00:06, 21.2MB/s]
 45%|####4     | 108M/242M [00:06<00:06, 21.4MB/s]
 46%|####5     | 111M/242M [00:06<00:06, 21.7MB/s]
 47%|####7     | 114M/242M [00:06<00:06, 22.0MB/s]
 48%|####8     | 117M/242M [00:06<00:05, 22.2MB/s]
 50%|####9     | 120M/242M [00:06<00:05, 22.4MB/s]
 51%|#####     | 123M/242M [00:06<00:05, 21.7MB/s]
 52%|#####1    | 126M/242M [00:06<00:05, 21.9MB/s]
 53%|#####3    | 129M/242M [00:07<00:05, 22.1MB/s]
 54%|#####4    | 132M/242M [00:07<00:05, 22.1MB/s]
 56%|#####5    | 135M/242M [00:07<00:05, 22.3MB/s]
 57%|#####6    | 138M/242M [00:07<00:04, 22.5MB/s]
 58%|#####8    | 140M/242M [00:07<00:04, 22.6MB/s]
 59%|#####9    | 143M/242M [00:07<00:04, 22.6MB/s]
 60%|######    | 146M/242M [00:07<00:04, 22.6MB/s]
 62%|######1   | 149M/242M [00:07<00:04, 23.3MB/s]
 63%|######2   | 152M/242M [00:08<00:03, 24.0MB/s]
 64%|######3   | 154M/242M [00:08<00:04, 22.4MB/s]
 65%|######4   | 157M/242M [00:08<00:03, 24.2MB/s]
 65%|######5   | 158M/242M [00:08<00:04, 21.4MB/s]
 66%|######6   | 161M/242M [00:08<00:04, 21.2MB/s]
 67%|######7   | 163M/242M [00:08<00:03, 22.3MB/s]
 68%|######8   | 166M/242M [00:08<00:03, 22.2MB/s]
 69%|######9   | 168M/242M [00:08<00:03, 22.7MB/s]
 70%|#######   | 170M/242M [00:08<00:03, 22.0MB/s]
 71%|#######1  | 172M/242M [00:09<00:03, 20.9MB/s]
 72%|#######2  | 175M/242M [00:09<00:03, 23.0MB/s]
 73%|#######2  | 176M/242M [00:09<00:03, 20.7MB/s]
 74%|#######3  | 179M/242M [00:09<00:03, 20.4MB/s]
 75%|#######4  | 182M/242M [00:09<00:03, 21.0MB/s]
 76%|#######6  | 184M/242M [00:09<00:02, 21.4MB/s]
 77%|#######7  | 187M/242M [00:09<00:02, 21.6MB/s]
 78%|#######8  | 190M/242M [00:09<00:02, 23.1MB/s]
 79%|#######9  | 192M/242M [00:10<00:02, 23.8MB/s]
 80%|########  | 194M/242M [00:10<00:02, 21.2MB/s]
 81%|########  | 196M/242M [00:10<00:02, 20.4MB/s]
 82%|########2 | 199M/242M [00:10<00:02, 21.2MB/s]
 83%|########3 | 202M/242M [00:10<00:01, 21.5MB/s]
 84%|########4 | 205M/242M [00:10<00:01, 22.0MB/s]
 86%|########5 | 207M/242M [00:10<00:01, 22.2MB/s]
 87%|########6 | 210M/242M [00:10<00:01, 22.3MB/s]
 88%|########8 | 213M/242M [00:11<00:01, 22.3MB/s]
 89%|########9 | 216M/242M [00:11<00:01, 22.4MB/s]
 91%|######### | 219M/242M [00:11<00:01, 22.4MB/s]
 92%|#########1| 222M/242M [00:11<00:00, 22.3MB/s]
 93%|#########2| 225M/242M [00:11<00:00, 22.1MB/s]
 94%|#########4| 228M/242M [00:11<00:00, 21.8MB/s]
 95%|#########5| 231M/242M [00:11<00:00, 22.0MB/s]
 96%|#########6| 234M/242M [00:12<00:00, 22.1MB/s]
 98%|#########7| 236M/242M [00:12<00:00, 22.3MB/s]
 99%|#########8| 239M/242M [00:12<00:00, 22.2MB/s]
100%|#########9| 242M/242M [00:12<00:00, 21.7MB/s]
100%|##########| 242M/242M [00:12<00:00, 20.5MB/s]

AnnData object with n_obs × n_vars = 2800 × 16562
    obs: 'in_tissue', 'array_row', 'array_col', 'n_genes_by_counts', 'log1p_n_genes_by_counts', 'total_counts', 'log1p_total_counts', 'pct_counts_in_top_50_genes', 'pct_counts_in_top_100_genes', 'pct_counts_in_top_200_genes', 'pct_counts_in_top_500_genes', 'total_counts_MT', 'log1p_total_counts_MT', 'pct_counts_MT', 'n_counts', 'leiden', 'cluster'
    var: 'gene_ids', 'feature_types', 'genome', 'MT', 'n_cells_by_counts', 'mean_counts', 'log1p_mean_counts', 'pct_dropout_by_counts', 'total_counts', 'log1p_total_counts', 'n_cells', 'highly_variable', 'highly_variable_rank', 'means', 'variances', 'variances_norm'
    uns: 'cluster_colors', 'hvg', 'leiden', 'leiden_colors', 'neighbors', 'pca', 'spatial', 'umap'
    obsm: 'X_pca', 'X_umap', 'spatial'
    varm: 'PCs'
    obsp: 'connectivities', 'distances'

We use squidpy.gr.spatial_neighbors() for this. The function expects coord_type = 'visium' by default. We set this parameter here explicitly for clarity. n_rings should be used only for Visium datasets. It specifies for each spot how many hexagonal rings of spots around will be considered neighbors.

sq.gr.spatial_neighbors(adata, n_rings=2, coord_type="grid", n_neighs=6)

The function builds a spatial graph and saves its adjacency matrix to adata.obsp['spatial_connectivities'] and weighted adjacency matrix to adata.obsp['spatial_distances'] by default. Note that it can also build a a graph from a square grid, just set n_neighs = 4.

adata.obsp["spatial_connectivities"]

Out:

<2800x2800 sparse matrix of type '<class 'numpy.float64'>'
    with 48240 stored elements in Compressed Sparse Row format>

The weights of the weighted adjacency matrix are ordinal numbers of hexagonal rings in the case of coord_type = 'visium'.

adata.obsp["spatial_distances"]

Out:

<2800x2800 sparse matrix of type '<class 'numpy.float64'>'
    with 48240 stored elements in Compressed Sparse Row format>

We can visualize the neighbors of a point to better visualize what n_rings mean:

_, idx = adata.obsp["spatial_connectivities"][420, :].nonzero()
idx = np.append(idx, 420)
sq.pl.spatial_scatter(adata[idx, :], connectivity_key="spatial_connectivities", img=False, na_color="lightgrey")
compute spatial neighbors

Next, we show how to compute the spatial neighbors graph for a non-grid dataset.

adata = sq.datasets.imc()
adata

Out:

  0%|          | 0.00/1.50M [00:00<?, ?B/s]
  2%|1         | 24.0k/1.50M [00:00<00:08, 183kB/s]
  4%|3         | 56.0k/1.50M [00:00<00:07, 217kB/s]
  6%|5         | 88.0k/1.50M [00:00<00:06, 227kB/s]
 14%|#3        | 208k/1.50M [00:00<00:02, 493kB/s]
 28%|##8       | 432k/1.50M [00:00<00:01, 921kB/s]
 57%|#####6    | 872k/1.50M [00:00<00:00, 1.73MB/s]
100%|##########| 1.50M/1.50M [00:00<00:00, 1.91MB/s]

AnnData object with n_obs × n_vars = 4668 × 34
    obs: 'cell type'
    uns: 'cell type_colors'
    obsm: 'spatial'

We use the same function for this with coord_type = 'generic'. n_neighs and radius can be used for non-Visium datasets. n_neighs specifies a fixed number of the closest spots for each spot as neighbors. Alternatively, delaunay = True can be used, for a Delaunay triangulation graph.

sq.gr.spatial_neighbors(adata, n_neighs=10, coord_type="generic")
_, idx = adata.obsp["spatial_connectivities"][420, :].nonzero()
idx = np.append(idx, 420)
sq.pl.spatial_scatter(adata[idx, :], shape=None, color="cell type", connectivity_key="spatial_connectivities", size=100)
cell type

Out:

/home/runner/work/squidpy_notebooks/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/squidpy/pl/_color_utils.py:26: ImplicitModificationWarning: Trying to modify attribute `._uns` of view, initializing view as actual.
  target.uns[color_key] = source.uns[color_key]
/home/runner/work/squidpy_notebooks/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/squidpy/pl/_spatial_utils.py:956: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap', 'norm' will be ignored
  _cax = scatter(

We use the same function for this with coord_type = 'generic' and delaunay = True. You can appreciate that the neighbor graph is slightly different than before.

sq.gr.spatial_neighbors(adata, delaunay=True, coord_type="generic")
_, idx = adata.obsp["spatial_connectivities"][420, :].nonzero()
idx = np.append(idx, 420)
sq.pl.spatial_scatter(
    adata[idx, :],
    shape=None,
    color="cell type",
    connectivity_key="spatial_connectivities",
    size=100,
)
cell type

Out:

/home/runner/work/squidpy_notebooks/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/squidpy/pl/_color_utils.py:26: ImplicitModificationWarning: Trying to modify attribute `._uns` of view, initializing view as actual.
  target.uns[color_key] = source.uns[color_key]
/home/runner/work/squidpy_notebooks/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/squidpy/pl/_spatial_utils.py:956: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap', 'norm' will be ignored
  _cax = scatter(

In order to get all spots within a specified radius (in units of the spatial coordinates) from each spot as neighbors, the parameter radius should be used.

sq.gr.spatial_neighbors(adata, radius=0.3, coord_type="generic")

adata.obsp["spatial_connectivities"]
adata.obsp["spatial_distances"]

Out:

<4668x4668 sparse matrix of type '<class 'numpy.float64'>'
    with 0 stored elements in Compressed Sparse Row format>

Total running time of the script: ( 0 minutes 26.994 seconds)

Estimated memory usage: 234 MB

Gallery generated by Sphinx-Gallery