Note
Click here to download the full example code
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")

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)

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,
)

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