Analyze Visium H&E data
This tutorial shows how to apply Squidpy for the analysis of Visium spatial transcriptomics data.
The dataset used here consists of a Visium slide of a coronal section of the mouse brain.
The original dataset is publicly available at the
10x Genomics dataset portal .
Here, we provide a pre-processed dataset, with pre-annotated clusters, in AnnData format and the
tissue image in squidpy.im.ImageContainer
format.
A couple of notes on pre-processing:
The pre-processing pipeline is the same as the one shown in the original Scanpy tutorial .
The cluster annotation was performed using several resources, such as the Allen Brain Atlas , the Mouse Brain gene expression atlas from the Linnarson lab and this recent pre-print .
See also
See Analyze Visium fluorescence data for a detailed analysis example of image features.
Import packages & data
To run the notebook locally, create a conda environment as conda env create -f environment.yml using this environment.yml.
import scanpy as sc
import anndata as ad
import squidpy as sq
import numpy as np
import pandas as pd
sc.logging.print_header()
print(f"squidpy=={sq.__version__}")
# load the pre-processed dataset
img = sq.datasets.visium_hne_image()
adata = sq.datasets.visium_hne_adata()
Out:
scanpy==1.9.1 anndata==0.8.0 umap==0.5.3 numpy==1.22.4 scipy==1.8.1 pandas==1.4.2 scikit-learn==1.1.1 statsmodels==0.13.2 python-igraph==0.9.11 pynndescent==0.5.7
squidpy==1.2.2
First, let’s visualize cluster annotation in spatial context
with squidpy.pl.spatial_scatter()
.
sq.pl.spatial_scatter(adata, color="cluster")

Image features
Visium datasets contain high-resolution images of the tissue that was used for the gene extraction.
Using the function squidpy.im.calculate_image_features()
you can calculate image features
for each Visium spot and create a obs x features
matrix in adata
that can then be analyzed together
with the obs x gene
gene expression matrix.
By extracting image features we are aiming to get both similar and complementary information to the gene expression values. Similar information is for example present in the case of a tissue with two different cell types whose morphology is different. Such cell type information is then contained in both the gene expression values and the tissue image features.
Squidpy contains several feature extractors and a flexible pipeline of calculating features
of different scales and sizes.
There are several detailed examples of how to use squidpy.im.calculate_image_features()
.
Extract image features provides a good starting point for learning more.
Here, we will extract summary features at different crop sizes and scales to allow the calculation of multi-scale features and segmentation features. For more information on the summary features, also refer to Extract summary features.
# calculate features for different scales (higher value means more context)
for scale in [1.0, 2.0]:
feature_name = f"features_summary_scale{scale}"
sq.im.calculate_image_features(
adata,
img.compute(),
features="summary",
key_added=feature_name,
n_jobs=4,
scale=scale,
)
# combine features in one dataframe
adata.obsm["features"] = pd.concat(
[adata.obsm[f] for f in adata.obsm.keys() if "features_summary" in f], axis="columns"
)
# make sure that we have no duplicated feature names in the combined table
adata.obsm["features"].columns = ad.utils.make_index_unique(adata.obsm["features"].columns)
Out:
0%| | 0/2688 [00:00<?, ?/s]
0%| | 1/2688 [00:12<9:08:06, 12.24s/]
1%|1 | 27/2688 [00:12<14:24, 3.08/s]
2%|2 | 59/2688 [00:12<05:20, 8.21/s]
4%|3 | 100/2688 [00:12<02:29, 17.28/s]
5%|5 | 145/2688 [00:12<01:22, 30.77/s]
7%|7 | 193/2688 [00:12<00:49, 49.94/s]
9%|9 | 251/2688 [00:12<00:30, 80.30/s]
11%|#1 | 306/2688 [00:12<00:20, 115.76/s]
14%|#3 | 371/2688 [00:13<00:13, 167.78/s]
16%|#6 | 439/2688 [00:13<00:09, 230.08/s]
19%|#8 | 500/2688 [00:13<00:07, 285.72/s]
21%|## | 563/2688 [00:13<00:06, 345.17/s]
23%|##3 | 628/2688 [00:13<00:05, 405.67/s]
26%|##5 | 697/2688 [00:13<00:04, 467.68/s]
29%|##8 | 770/2688 [00:13<00:03, 529.13/s]
31%|###1 | 837/2688 [00:13<00:03, 560.78/s]
34%|###3 | 906/2688 [00:13<00:03, 593.40/s]
36%|###6 | 981/2688 [00:13<00:02, 634.88/s]
39%|###9 | 1050/2688 [00:14<00:02, 646.99/s]
42%|####1 | 1126/2688 [00:14<00:02, 673.56/s]
45%|####4 | 1204/2688 [00:14<00:02, 701.10/s]
48%|####7 | 1284/2688 [00:14<00:01, 726.63/s]
51%|##### | 1359/2688 [00:14<00:01, 688.56/s]
53%|#####3 | 1430/2688 [00:14<00:01, 664.75/s]
56%|#####5 | 1498/2688 [00:14<00:02, 564.02/s]
58%|#####7 | 1558/2688 [00:15<00:02, 390.14/s]
60%|###### | 1623/2688 [00:15<00:02, 440.67/s]
63%|######2 | 1691/2688 [00:15<00:02, 491.53/s]
65%|######5 | 1758/2688 [00:15<00:01, 532.20/s]
68%|######7 | 1826/2688 [00:15<00:01, 569.48/s]
71%|####### | 1897/2688 [00:15<00:01, 606.43/s]
73%|#######3 | 1964/2688 [00:15<00:01, 622.31/s]
76%|#######5 | 2032/2688 [00:15<00:01, 635.35/s]
78%|#######8 | 2105/2688 [00:15<00:00, 660.75/s]
81%|######## | 2177/2688 [00:15<00:00, 672.27/s]
84%|########3 | 2246/2688 [00:16<00:00, 676.26/s]
86%|########6 | 2318/2688 [00:16<00:00, 685.94/s]
89%|########8 | 2388/2688 [00:16<00:00, 689.60/s]
91%|#########1| 2459/2688 [00:16<00:00, 695.29/s]
94%|#########4| 2531/2688 [00:16<00:00, 700.51/s]
97%|#########6| 2606/2688 [00:16<00:00, 710.45/s]
100%|#########9| 2678/2688 [00:16<00:00, 712.29/s]
100%|##########| 2688/2688 [00:16<00:00, 160.59/s]
0%| | 0/2688 [00:00<?, ?/s]
0%| | 1/2688 [00:05<4:25:06, 5.92s/]
1%| | 14/2688 [00:06<13:49, 3.22/s]
1%| | 26/2688 [00:06<06:21, 6.99/s]
1%|1 | 38/2688 [00:06<03:41, 11.97/s]
2%|1 | 51/2688 [00:06<02:20, 18.83/s]
2%|2 | 64/2688 [00:06<01:35, 27.36/s]
3%|2 | 77/2688 [00:06<01:09, 37.56/s]
3%|3 | 90/2688 [00:06<00:53, 48.63/s]
4%|3 | 104/2688 [00:06<00:42, 60.63/s]
4%|4 | 117/2688 [00:06<00:35, 72.35/s]
5%|4 | 131/2688 [00:07<00:30, 83.59/s]
5%|5 | 146/2688 [00:07<00:26, 95.34/s]
6%|5 | 161/2688 [00:07<00:23, 106.16/s]
7%|6 | 176/2688 [00:07<00:21, 116.15/s]
7%|7 | 190/2688 [00:07<00:20, 119.32/s]
8%|7 | 205/2688 [00:07<00:19, 125.38/s]
8%|8 | 221/2688 [00:07<00:19, 129.27/s]
9%|8 | 236/2688 [00:07<00:18, 131.06/s]
9%|9 | 251/2688 [00:07<00:17, 135.55/s]
10%|9 | 266/2688 [00:07<00:17, 135.19/s]
10%|# | 280/2688 [00:08<00:17, 135.72/s]
11%|# | 294/2688 [00:08<00:17, 134.29/s]
11%|#1 | 309/2688 [00:08<00:17, 134.88/s]
12%|#2 | 323/2688 [00:08<00:17, 134.87/s]
13%|#2 | 337/2688 [00:08<00:17, 133.03/s]
13%|#3 | 351/2688 [00:08<00:18, 129.61/s]
14%|#3 | 365/2688 [00:08<00:18, 127.25/s]
14%|#4 | 379/2688 [00:08<00:17, 129.96/s]
15%|#4 | 393/2688 [00:08<00:18, 126.85/s]
15%|#5 | 406/2688 [00:09<00:18, 124.23/s]
16%|#5 | 419/2688 [00:09<00:22, 100.63/s]
16%|#5 | 430/2688 [00:09<00:25, 89.17/s]
16%|#6 | 440/2688 [00:09<00:24, 91.16/s]
17%|#6 | 450/2688 [00:09<00:24, 91.25/s]
17%|#7 | 462/2688 [00:09<00:22, 97.94/s]
18%|#7 | 473/2688 [00:09<00:22, 99.29/s]
18%|#8 | 484/2688 [00:09<00:22, 97.45/s]
18%|#8 | 497/2688 [00:10<00:20, 106.12/s]
19%|#8 | 510/2688 [00:10<00:20, 106.40/s]
19%|#9 | 522/2688 [00:10<00:20, 108.17/s]
20%|#9 | 533/2688 [00:10<00:20, 104.25/s]
20%|## | 544/2688 [00:10<00:20, 105.07/s]
21%|## | 556/2688 [00:10<00:20, 106.53/s]
21%|##1 | 567/2688 [00:10<00:21, 96.96/s]
22%|##1 | 579/2688 [00:10<00:21, 97.90/s]
22%|##1 | 591/2688 [00:10<00:20, 102.32/s]
22%|##2 | 602/2688 [00:11<00:20, 103.80/s]
23%|##2 | 613/2688 [00:11<00:20, 101.75/s]
23%|##3 | 624/2688 [00:11<00:20, 100.37/s]
24%|##3 | 635/2688 [00:11<00:20, 100.35/s]
24%|##4 | 646/2688 [00:11<00:22, 92.30/s]
24%|##4 | 656/2688 [00:11<00:23, 87.92/s]
25%|##4 | 665/2688 [00:11<00:22, 88.43/s]
25%|##5 | 674/2688 [00:11<00:23, 86.58/s]
25%|##5 | 683/2688 [00:12<00:23, 85.59/s]
26%|##5 | 692/2688 [00:12<00:25, 77.72/s]
26%|##6 | 701/2688 [00:12<00:25, 76.56/s]
26%|##6 | 709/2688 [00:12<00:26, 76.11/s]
27%|##6 | 717/2688 [00:12<00:27, 72.49/s]
27%|##6 | 725/2688 [00:12<00:27, 70.42/s]
27%|##7 | 733/2688 [00:12<00:29, 65.70/s]
28%|##7 | 740/2688 [00:12<00:29, 65.35/s]
28%|##7 | 748/2688 [00:12<00:28, 67.21/s]
28%|##8 | 756/2688 [00:13<00:28, 68.31/s]
29%|##8 | 767/2688 [00:13<00:25, 73.94/s]
29%|##8 | 776/2688 [00:13<00:24, 77.06/s]
29%|##9 | 786/2688 [00:13<00:23, 81.81/s]
30%|##9 | 795/2688 [00:13<00:22, 83.67/s]
30%|##9 | 804/2688 [00:13<00:22, 84.85/s]
30%|### | 813/2688 [00:13<00:21, 86.31/s]
31%|### | 823/2688 [00:13<00:21, 87.78/s]
31%|### | 832/2688 [00:13<00:21, 85.44/s]
31%|###1 | 841/2688 [00:14<00:22, 83.35/s]
32%|###1 | 850/2688 [00:14<00:21, 84.10/s]
32%|###1 | 860/2688 [00:14<00:22, 81.92/s]
32%|###2 | 869/2688 [00:14<00:21, 83.25/s]
33%|###2 | 880/2688 [00:14<00:19, 90.43/s]
33%|###3 | 892/2688 [00:14<00:18, 97.88/s]
34%|###3 | 903/2688 [00:14<00:17, 101.11/s]
34%|###4 | 915/2688 [00:14<00:16, 106.38/s]
35%|###4 | 929/2688 [00:14<00:15, 115.45/s]
35%|###5 | 943/2688 [00:15<00:15, 114.95/s]
36%|###5 | 956/2688 [00:15<00:14, 119.08/s]
36%|###6 | 971/2688 [00:15<00:14, 122.27/s]
37%|###6 | 984/2688 [00:15<00:13, 123.23/s]
37%|###7 | 997/2688 [00:15<00:13, 124.67/s]
38%|###7 | 1011/2688 [00:15<00:13, 125.29/s]
38%|###8 | 1026/2688 [00:15<00:12, 131.76/s]
39%|###8 | 1040/2688 [00:15<00:12, 129.40/s]
39%|###9 | 1055/2688 [00:15<00:12, 126.99/s]
40%|###9 | 1068/2688 [00:16<00:12, 127.21/s]
40%|#### | 1082/2688 [00:16<00:12, 125.72/s]
41%|#### | 1098/2688 [00:16<00:12, 127.89/s]
41%|####1 | 1112/2688 [00:16<00:12, 131.10/s]
42%|####1 | 1126/2688 [00:16<00:12, 127.30/s]
42%|####2 | 1142/2688 [00:16<00:11, 129.64/s]
43%|####3 | 1156/2688 [00:16<00:11, 132.45/s]
44%|####3 | 1170/2688 [00:16<00:11, 130.18/s]
44%|####4 | 1184/2688 [00:16<00:11, 132.69/s]
45%|####4 | 1198/2688 [00:17<00:11, 128.69/s]
45%|####5 | 1213/2688 [00:17<00:11, 132.55/s]
46%|####5 | 1227/2688 [00:17<00:11, 131.39/s]
46%|####6 | 1241/2688 [00:17<00:10, 131.84/s]
47%|####6 | 1255/2688 [00:17<00:11, 130.16/s]
47%|####7 | 1270/2688 [00:17<00:10, 130.06/s]
48%|####7 | 1285/2688 [00:17<00:10, 134.79/s]
48%|####8 | 1299/2688 [00:17<00:10, 130.21/s]
49%|####8 | 1313/2688 [00:17<00:10, 129.81/s]
49%|####9 | 1327/2688 [00:18<00:10, 130.07/s]
50%|####9 | 1341/2688 [00:18<00:10, 129.77/s]
50%|##### | 1354/2688 [00:18<00:12, 109.11/s]
51%|##### | 1366/2688 [00:18<00:12, 109.32/s]
51%|#####1 | 1379/2688 [00:18<00:11, 113.70/s]
52%|#####1 | 1392/2688 [00:18<00:11, 112.37/s]
52%|#####2 | 1406/2688 [00:18<00:10, 119.55/s]
53%|#####2 | 1420/2688 [00:18<00:10, 117.72/s]
53%|#####3 | 1434/2688 [00:18<00:10, 123.56/s]
54%|#####3 | 1447/2688 [00:19<00:10, 123.08/s]
54%|#####4 | 1460/2688 [00:19<00:10, 116.81/s]
55%|#####4 | 1473/2688 [00:19<00:10, 118.73/s]
55%|#####5 | 1485/2688 [00:19<00:10, 118.49/s]
56%|#####5 | 1499/2688 [00:19<00:09, 124.05/s]
56%|#####6 | 1512/2688 [00:19<00:10, 116.46/s]
57%|#####6 | 1526/2688 [00:19<00:09, 122.38/s]
57%|#####7 | 1539/2688 [00:19<00:09, 124.13/s]
58%|#####7 | 1552/2688 [00:19<00:09, 122.16/s]
58%|#####8 | 1566/2688 [00:20<00:08, 126.93/s]
59%|#####8 | 1579/2688 [00:20<00:08, 123.40/s]
59%|#####9 | 1592/2688 [00:20<00:09, 121.55/s]
60%|#####9 | 1605/2688 [00:20<00:08, 122.16/s]
60%|###### | 1618/2688 [00:20<00:08, 122.93/s]
61%|###### | 1632/2688 [00:20<00:08, 121.59/s]
61%|######1 | 1646/2688 [00:20<00:08, 125.45/s]
62%|######1 | 1660/2688 [00:20<00:08, 125.34/s]
62%|######2 | 1674/2688 [00:20<00:07, 127.74/s]
63%|######2 | 1687/2688 [00:20<00:07, 127.61/s]
63%|######3 | 1700/2688 [00:21<00:07, 124.01/s]
64%|######3 | 1714/2688 [00:21<00:07, 127.43/s]
64%|######4 | 1727/2688 [00:21<00:07, 127.47/s]
65%|######4 | 1740/2688 [00:21<00:07, 120.78/s]
65%|######5 | 1754/2688 [00:21<00:07, 125.58/s]
66%|######5 | 1768/2688 [00:21<00:07, 124.42/s]
66%|######6 | 1781/2688 [00:21<00:07, 125.35/s]
67%|######6 | 1796/2688 [00:21<00:07, 125.43/s]
67%|######7 | 1809/2688 [00:21<00:07, 121.97/s]
68%|######7 | 1822/2688 [00:22<00:07, 123.47/s]
68%|######8 | 1836/2688 [00:22<00:06, 123.68/s]
69%|######8 | 1849/2688 [00:22<00:07, 119.77/s]
69%|######9 | 1862/2688 [00:22<00:06, 122.34/s]
70%|######9 | 1876/2688 [00:22<00:06, 122.90/s]
70%|####### | 1890/2688 [00:22<00:06, 125.57/s]
71%|####### | 1903/2688 [00:22<00:06, 126.39/s]
71%|#######1 | 1916/2688 [00:22<00:06, 125.35/s]
72%|#######1 | 1930/2688 [00:22<00:06, 125.14/s]
72%|#######2 | 1944/2688 [00:23<00:05, 129.19/s]
73%|#######2 | 1958/2688 [00:23<00:05, 127.28/s]
73%|#######3 | 1972/2688 [00:23<00:05, 130.85/s]
74%|#######3 | 1986/2688 [00:23<00:05, 127.72/s]
74%|#######4 | 2001/2688 [00:23<00:05, 132.90/s]
75%|#######4 | 2015/2688 [00:23<00:05, 128.88/s]
75%|#######5 | 2029/2688 [00:23<00:05, 130.25/s]
76%|#######6 | 2043/2688 [00:23<00:05, 126.64/s]
77%|#######6 | 2057/2688 [00:23<00:04, 127.72/s]
77%|#######7 | 2070/2688 [00:24<00:04, 125.06/s]
77%|#######7 | 2083/2688 [00:24<00:04, 126.00/s]
78%|#######8 | 2097/2688 [00:24<00:04, 129.39/s]
78%|#######8 | 2110/2688 [00:24<00:04, 126.52/s]
79%|#######9 | 2124/2688 [00:24<00:04, 130.31/s]
80%|#######9 | 2138/2688 [00:24<00:04, 125.71/s]
80%|######## | 2152/2688 [00:24<00:04, 128.59/s]
81%|######## | 2165/2688 [00:24<00:04, 127.30/s]
81%|########1 | 2178/2688 [00:24<00:04, 126.97/s]
82%|########1 | 2192/2688 [00:24<00:03, 128.31/s]
82%|########2 | 2206/2688 [00:25<00:03, 130.34/s]
83%|########2 | 2220/2688 [00:25<00:03, 128.84/s]
83%|########3 | 2234/2688 [00:25<00:03, 130.86/s]
84%|########3 | 2248/2688 [00:25<00:03, 128.69/s]
84%|########4 | 2262/2688 [00:25<00:03, 130.88/s]
85%|########4 | 2276/2688 [00:25<00:03, 127.92/s]
85%|########5 | 2289/2688 [00:25<00:03, 127.40/s]
86%|########5 | 2304/2688 [00:25<00:03, 124.94/s]
86%|########6 | 2320/2688 [00:25<00:02, 125.64/s]
87%|########6 | 2336/2688 [00:26<00:02, 126.60/s]
87%|########7 | 2350/2688 [00:26<00:02, 129.84/s]
88%|########7 | 2364/2688 [00:26<00:02, 125.13/s]
88%|########8 | 2377/2688 [00:26<00:02, 124.45/s]
89%|########8 | 2391/2688 [00:26<00:02, 127.92/s]
89%|########9 | 2404/2688 [00:26<00:02, 122.19/s]
90%|########9 | 2419/2688 [00:26<00:02, 129.85/s]
91%|######### | 2433/2688 [00:26<00:02, 126.90/s]
91%|#########1| 2447/2688 [00:26<00:01, 129.65/s]
92%|#########1| 2461/2688 [00:27<00:01, 128.37/s]
92%|#########2| 2475/2688 [00:27<00:01, 129.95/s]
93%|#########2| 2489/2688 [00:27<00:01, 128.83/s]
93%|#########3| 2503/2688 [00:27<00:01, 130.85/s]
94%|#########3| 2517/2688 [00:27<00:01, 129.19/s]
94%|#########4| 2532/2688 [00:27<00:01, 127.47/s]
95%|#########4| 2547/2688 [00:27<00:01, 132.46/s]
95%|#########5| 2561/2688 [00:27<00:00, 129.76/s]
96%|#########5| 2575/2688 [00:27<00:00, 131.37/s]
96%|#########6| 2589/2688 [00:28<00:00, 126.92/s]
97%|#########6| 2604/2688 [00:28<00:00, 127.67/s]
97%|#########7| 2620/2688 [00:28<00:00, 130.20/s]
98%|#########7| 2634/2688 [00:28<00:00, 131.00/s]
99%|#########8| 2648/2688 [00:28<00:00, 121.26/s]
99%|#########8| 2661/2688 [00:28<00:00, 119.79/s]
100%|#########9| 2676/2688 [00:28<00:00, 121.64/s]
100%|##########| 2688/2688 [00:28<00:00, 92.87/s]
We can use the extracted image features to compute a new cluster annotation. This could be useful to gain insights in similarities across spots based on image morphology.
# helper function returning a clustering
def cluster_features(features: pd.DataFrame, like=None) -> pd.Series:
"""
Calculate leiden clustering of features.
Specify filter of features using `like`.
"""
# filter features
if like is not None:
features = features.filter(like=like)
# create temporary adata to calculate the clustering
adata = ad.AnnData(features)
# important - feature values are not scaled, so need to scale them before PCA
sc.pp.scale(adata)
# calculate leiden clustering
sc.pp.pca(adata, n_comps=min(10, features.shape[1] - 1))
sc.pp.neighbors(adata)
sc.tl.leiden(adata)
return adata.obs["leiden"]
# calculate feature clusters
adata.obs["features_cluster"] = cluster_features(adata.obsm["features"], like="summary")
# compare feature and gene clusters
sq.pl.spatial_scatter(adata, color=["features_cluster", "cluster"])

Out:
/Users/giovanni.palla/Projects/squidpy_notebooks/tutorials/tutorial_visium_hne.py:112: FutureWarning: X.dtype being converted to np.float32 from float64. In the next version of anndata (0.9) conversion will not be automatic. Pass dtype explicitly to avoid this warning. Pass `AnnData(X, dtype=X.dtype, ...)` to get the future behavour.
adata = ad.AnnData(features)
Comparing gene and feature clusters, we notice that in some regions, they look very similar, like the cluster Fiber_tract, or clusters around the Hippocampus seems to be roughly recapitulated by the clusters in image feature space. In others, the feature clusters look different, like in the cortex, where the gene clusters show the layered structure of the cortex, and the features clusters rather seem to show different regions of the cortex.
This is only a simple, comparative analysis of the image features, note that you could also use the image features to e.g. compute a common image and gene clustering by computing a shared neighbors graph (for instance on concatenated PCAs on both feature spaces).
Spatial statistics and graph analysis
Similar to other spatial data, we can investigate spatial organization by leveraging spatial and graph statistics in Visium data.
Neighborhood enrichment
Computing a neighborhood enrichment can help us identify spots clusters that share
a common neighborhood structure across the tissue.
We can compute such score with the following function: squidpy.gr.nhood_enrichment()
.
In short, it’s an enrichment score on spatial proximity of clusters:
if spots belonging to two different clusters are often close to each other,
then they will have a high score and can be defined as being enriched.
On the other hand, if they are far apart, and therefore are seldom a neighborhood,
the score will be low and they can be defined as depleted. This score is
based on a permutation-based test, and you can set
the number of permutations with the n_perms
argument (default is 1000).
Since the function works on a connectivity matrix, we need to compute that as well.
This can be done with squidpy.gr.spatial_neighbors()
.
Please see Building spatial neighbors graph for more details
of how this function works.
Finally, we’ll directly visualize the results with squidpy.pl.nhood_enrichment()
.
sq.gr.spatial_neighbors(adata)
sq.gr.nhood_enrichment(adata, cluster_key="cluster")
sq.pl.nhood_enrichment(adata, cluster_key="cluster")

Out:
0%| | 0/1000 [00:00<?, ?/s]
0%| | 1/1000 [00:10<2:51:27, 10.30s/]
34%|###4 | 344/1000 [00:10<00:13, 47.03/s]
70%|######9 | 696/1000 [00:10<00:02, 113.61/s]
100%|##########| 1000/1000 [00:10<00:00, 94.49/s]
Given the spatial organization of the mouse brain coronal section, not surprisingly we find high neighborhood enrichment the Hippocampus region: Pyramidal_layer_dentate_gyrus and Pyramidal_layer clusters seems to be often neighbors with the larger Hippocampus cluster.
Co-occurrence across spatial dimensions
In addition to the neighbor enrichment score, we can visualize cluster co-occurrence in spatial dimensions. This is a similar analysis of the one presented above, yet it does not operate on the connectivity matrix, but on the original spatial coordinates. The co-occurrence score is defined as:
where \(p(exp|cond)\) is the conditional probability of observing a cluster \(exp\) conditioned on the presence of a cluster \(cond\), whereas \(p(exp)\) is the probability of observing \(exp\) in the radius size of interest. The score is computed across increasing radii size around each observation (i.e. spots here) in the tissue.
We are gonna compute such score with squidpy.gr.co_occurrence()
and set the cluster annotation
for the conditional probability with the argument clusters
.
Then, we visualize the results with squidpy.pl.co_occurrence()
.
sq.gr.co_occurrence(adata, cluster_key="cluster")
sq.pl.co_occurrence(
adata,
cluster_key="cluster",
clusters="Hippocampus",
figsize=(8, 4),
)

Out:
0%| | 0/1 [00:00<?, ?/s]
100%|##########| 1/1 [00:06<00:00, 6.32s/]
100%|##########| 1/1 [00:06<00:00, 6.33s/]
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1582: UserWarning: Trying to register the cmap 'rocket' which already exists.
mpl_cm.register_cmap(_name, _cmap)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1583: UserWarning: Trying to register the cmap 'rocket_r' which already exists.
mpl_cm.register_cmap(_name + "_r", _cmap_r)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1582: UserWarning: Trying to register the cmap 'mako' which already exists.
mpl_cm.register_cmap(_name, _cmap)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1583: UserWarning: Trying to register the cmap 'mako_r' which already exists.
mpl_cm.register_cmap(_name + "_r", _cmap_r)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1582: UserWarning: Trying to register the cmap 'icefire' which already exists.
mpl_cm.register_cmap(_name, _cmap)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1583: UserWarning: Trying to register the cmap 'icefire_r' which already exists.
mpl_cm.register_cmap(_name + "_r", _cmap_r)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1582: UserWarning: Trying to register the cmap 'vlag' which already exists.
mpl_cm.register_cmap(_name, _cmap)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1583: UserWarning: Trying to register the cmap 'vlag_r' which already exists.
mpl_cm.register_cmap(_name + "_r", _cmap_r)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1582: UserWarning: Trying to register the cmap 'flare' which already exists.
mpl_cm.register_cmap(_name, _cmap)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1583: UserWarning: Trying to register the cmap 'flare_r' which already exists.
mpl_cm.register_cmap(_name + "_r", _cmap_r)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1582: UserWarning: Trying to register the cmap 'crest' which already exists.
mpl_cm.register_cmap(_name, _cmap)
/Users/giovanni.palla/Projects/squidpy_notebooks/.tox/docs/lib/python3.9/site-packages/seaborn/cm.py:1583: UserWarning: Trying to register the cmap 'crest_r' which already exists.
mpl_cm.register_cmap(_name + "_r", _cmap_r)
The result largely recapitulates the previous analysis:
the Pyramidal_layer cluster seem to co-occur at short distances
with the larger Hippocampus cluster.
It should be noted that the distance units are given in pixels of
the Visium source_image
, and corresponds to the same unit of
the spatial coordinates saved in adata.obsm['spatial']
.
Ligand-receptor interaction analysis
We are continuing the analysis showing couple of feature-level methods that are very relevant
for the analysis of spatial molecular data. For instance, after
quantification of cluster co-occurrence,
we might be interested in finding molecular instances
that could potentially drive cellular communication.
This naturally translates in a ligand-receptor interaction analysis.
In Squidpy, we provide a fast re-implementation the popular method CellPhoneDB [Efremova et al., 2020]
(code )
and extended its database of annotated ligand-receptor interaction pairs with
the popular database Omnipath [Türei et al., 2016].
You can run the analysis for all clusters pairs, and all genes (in seconds,
without leaving this notebook), with squidpy.gr.ligrec()
.
Furthermore, we’ll directly visualize the results, filtering out lowly-expressed genes
(with the means_range
argument) and increasing the threshold for
the adjusted p-value (with the alpha
argument).
We’ll also subset the visualization for only one source group,
the Hippocampus cluster, and two target groups, Pyramidal_layer_dentate_gyrus and Pyramidal_layer cluster.
sq.gr.ligrec(
adata,
n_perms=100,
cluster_key="cluster",
)
sq.pl.ligrec(
adata,
cluster_key="cluster",
source_groups="Hippocampus",
target_groups=["Pyramidal_layer", "Pyramidal_layer_dentate_gyrus"],
means_range=(3, np.inf),
alpha=1e-4,
swap_axes=True,
)

Out:
0%| | 0/100 [00:00<?, ?permutation/s]
1%|1 | 1/100 [00:16<27:13, 16.50s/permutation]
7%|7 | 7/100 [00:16<02:41, 1.74s/permutation]
13%|#3 | 13/100 [00:16<01:07, 1.29permutation/s]
19%|#9 | 19/100 [00:16<00:35, 2.28permutation/s]
25%|##5 | 25/100 [00:16<00:20, 3.63permutation/s]
31%|###1 | 31/100 [00:17<00:12, 5.43permutation/s]
37%|###7 | 37/100 [00:17<00:08, 7.79permutation/s]
43%|####3 | 43/100 [00:17<00:05, 10.77permutation/s]
49%|####9 | 49/100 [00:17<00:03, 14.38permutation/s]
55%|#####5 | 55/100 [00:17<00:02, 18.45permutation/s]
61%|######1 | 61/100 [00:17<00:01, 22.79permutation/s]
67%|######7 | 67/100 [00:17<00:01, 27.03permutation/s]
73%|#######3 | 73/100 [00:17<00:00, 31.45permutation/s]
78%|#######8 | 78/100 [00:18<00:00, 34.88permutation/s]
83%|########2 | 83/100 [00:18<00:00, 38.00permutation/s]
88%|########8 | 88/100 [00:18<00:00, 39.49permutation/s]
93%|#########3| 93/100 [00:18<00:00, 38.85permutation/s]
98%|#########8| 98/100 [00:18<00:00, 41.16permutation/s]
100%|##########| 100/100 [00:18<00:00, 5.40permutation/s]
The dotplot visualization provides an interesting set of candidate ligand-receptor annotation that could be involved in cellular interactions in the Hippocampus. A more refined analysis would be for instance to integrate these results with the results of a deconvolution method, to understand what’s the proportion of single-cell cell types present in this region of the tissue.
Spatially variable genes with Moran’s I
Finally, we might be interested in finding genes that show spatial patterns. There are several methods that aimed at address this explicitly, based on point processes or Gaussian process regression framework:
Here, we provide a simple approach based on the well-known
Moran’s I statistics
which is in fact used also as a baseline method in the spatially variable gene papers listed above.
The function in Squidpy is called squidpy.gr.spatial_autocorr()
, and
returns both test statistics and adjusted p-values in anndata.AnnData.var
slot.
For time reasons, we will evaluate a subset of the highly variable genes only.
genes = adata[:, adata.var.highly_variable].var_names.values[:1000]
sq.gr.spatial_autocorr(
adata,
mode="moran",
genes=genes,
n_perms=100,
n_jobs=1,
)
Out:
0%| | 0/100 [00:00<?, ?/s]
1%|1 | 1/100 [00:06<11:20, 6.87s/]
2%|2 | 2/100 [00:07<05:24, 3.31s/]
3%|3 | 3/100 [00:08<03:30, 2.17s/]
4%|4 | 4/100 [00:09<02:36, 1.63s/]
5%|5 | 5/100 [00:10<02:07, 1.35s/]
6%|6 | 6/100 [00:11<01:50, 1.18s/]
7%|7 | 7/100 [00:11<01:40, 1.08s/]
8%|8 | 8/100 [00:12<01:31, 1.00/s]
9%|9 | 9/100 [00:13<01:26, 1.05/s]
10%|# | 10/100 [00:14<01:21, 1.10/s]
11%|#1 | 11/100 [00:15<01:18, 1.13/s]
12%|#2 | 12/100 [00:16<01:16, 1.15/s]
13%|#3 | 13/100 [00:16<01:14, 1.17/s]
14%|#4 | 14/100 [00:17<01:12, 1.18/s]
15%|#5 | 15/100 [00:18<01:11, 1.19/s]
16%|#6 | 16/100 [00:19<01:10, 1.20/s]
17%|#7 | 17/100 [00:20<01:08, 1.20/s]
18%|#8 | 18/100 [00:20<01:07, 1.21/s]
19%|#9 | 19/100 [00:21<01:06, 1.22/s]
20%|## | 20/100 [00:22<01:05, 1.22/s]
21%|##1 | 21/100 [00:23<01:04, 1.22/s]
22%|##2 | 22/100 [00:24<01:04, 1.22/s]
23%|##3 | 23/100 [00:25<01:03, 1.22/s]
24%|##4 | 24/100 [00:25<01:02, 1.22/s]
25%|##5 | 25/100 [00:26<01:01, 1.22/s]
26%|##6 | 26/100 [00:27<01:00, 1.21/s]
27%|##7 | 27/100 [00:28<00:59, 1.22/s]
28%|##8 | 28/100 [00:29<00:58, 1.22/s]
29%|##9 | 29/100 [00:29<00:58, 1.21/s]
30%|### | 30/100 [00:30<00:58, 1.19/s]
31%|###1 | 31/100 [00:31<00:57, 1.20/s]
32%|###2 | 32/100 [00:32<00:56, 1.20/s]
33%|###3 | 33/100 [00:33<00:55, 1.21/s]
34%|###4 | 34/100 [00:34<00:54, 1.21/s]
35%|###5 | 35/100 [00:34<00:53, 1.22/s]
36%|###6 | 36/100 [00:35<00:52, 1.22/s]
37%|###7 | 37/100 [00:36<00:51, 1.22/s]
38%|###8 | 38/100 [00:37<00:50, 1.22/s]
39%|###9 | 39/100 [00:38<00:50, 1.21/s]
40%|#### | 40/100 [00:39<00:49, 1.20/s]
41%|####1 | 41/100 [00:39<00:48, 1.21/s]
42%|####2 | 42/100 [00:40<00:47, 1.21/s]
43%|####3 | 43/100 [00:41<00:46, 1.21/s]
44%|####4 | 44/100 [00:42<00:45, 1.22/s]
45%|####5 | 45/100 [00:43<00:45, 1.21/s]
46%|####6 | 46/100 [00:44<00:44, 1.20/s]
47%|####6 | 47/100 [00:44<00:44, 1.19/s]
48%|####8 | 48/100 [00:45<00:44, 1.18/s]
49%|####9 | 49/100 [00:46<00:42, 1.19/s]
50%|##### | 50/100 [00:47<00:41, 1.21/s]
51%|#####1 | 51/100 [00:48<00:40, 1.21/s]
52%|#####2 | 52/100 [00:49<00:39, 1.22/s]
53%|#####3 | 53/100 [00:49<00:38, 1.22/s]
54%|#####4 | 54/100 [00:50<00:37, 1.22/s]
55%|#####5 | 55/100 [00:51<00:36, 1.22/s]
56%|#####6 | 56/100 [00:52<00:35, 1.23/s]
57%|#####6 | 57/100 [00:53<00:34, 1.23/s]
58%|#####8 | 58/100 [00:53<00:34, 1.21/s]
59%|#####8 | 59/100 [00:54<00:33, 1.22/s]
60%|###### | 60/100 [00:55<00:33, 1.20/s]
61%|######1 | 61/100 [00:56<00:32, 1.21/s]
62%|######2 | 62/100 [00:57<00:31, 1.21/s]
63%|######3 | 63/100 [00:58<00:30, 1.22/s]
64%|######4 | 64/100 [00:58<00:29, 1.22/s]
65%|######5 | 65/100 [00:59<00:28, 1.22/s]
66%|######6 | 66/100 [01:00<00:27, 1.23/s]
67%|######7 | 67/100 [01:01<00:26, 1.23/s]
68%|######8 | 68/100 [01:02<00:26, 1.23/s]
69%|######9 | 69/100 [01:02<00:25, 1.23/s]
70%|####### | 70/100 [01:03<00:24, 1.23/s]
71%|#######1 | 71/100 [01:04<00:23, 1.22/s]
72%|#######2 | 72/100 [01:05<00:22, 1.22/s]
73%|#######3 | 73/100 [01:06<00:22, 1.22/s]
74%|#######4 | 74/100 [01:07<00:21, 1.22/s]
75%|#######5 | 75/100 [01:07<00:20, 1.23/s]
76%|#######6 | 76/100 [01:08<00:19, 1.23/s]
77%|#######7 | 77/100 [01:09<00:18, 1.23/s]
78%|#######8 | 78/100 [01:10<00:17, 1.23/s]
79%|#######9 | 79/100 [01:11<00:17, 1.23/s]
80%|######## | 80/100 [01:11<00:16, 1.23/s]
81%|########1 | 81/100 [01:12<00:15, 1.23/s]
82%|########2 | 82/100 [01:13<00:14, 1.22/s]
83%|########2 | 83/100 [01:14<00:13, 1.23/s]
84%|########4 | 84/100 [01:15<00:13, 1.23/s]
85%|########5 | 85/100 [01:15<00:12, 1.23/s]
86%|########6 | 86/100 [01:16<00:11, 1.23/s]
87%|########7 | 87/100 [01:17<00:10, 1.24/s]
88%|########8 | 88/100 [01:18<00:09, 1.24/s]
89%|########9 | 89/100 [01:19<00:08, 1.24/s]
90%|######### | 90/100 [01:20<00:08, 1.24/s]
91%|#########1| 91/100 [01:20<00:07, 1.24/s]
92%|#########2| 92/100 [01:21<00:06, 1.23/s]
93%|#########3| 93/100 [01:22<00:05, 1.23/s]
94%|#########3| 94/100 [01:23<00:04, 1.23/s]
95%|#########5| 95/100 [01:24<00:04, 1.23/s]
96%|#########6| 96/100 [01:24<00:03, 1.23/s]
97%|#########7| 97/100 [01:25<00:02, 1.23/s]
98%|#########8| 98/100 [01:26<00:01, 1.19/s]
99%|#########9| 99/100 [01:27<00:00, 1.16/s]
100%|##########| 100/100 [01:28<00:00, 1.16/s]
100%|##########| 100/100 [01:28<00:00, 1.13/s]
The results are saved in adata.uns['moranI']
slot.
Genes have already been sorted by Moran’s I statistic.
adata.uns["moranI"].head(10)
We can select few genes and visualize their expression levels in the tissue with squidpy.pl.spatial_scatter()
.
sq.pl.spatial_scatter(adata, color=["Olfm1", "Plp1", "Itpka", "cluster"])

Interestingly, some of these genes seems to be related to the pyramidal layers and the fiber tract.
Total running time of the script: ( 3 minutes 57.792 seconds)
Estimated memory usage: 1019 MB