SpatialQuery 0.0.5__tar.gz → 0.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spatialquery-0.0.5/SpatialQuery.egg-info → spatialquery-0.0.6}/PKG-INFO +15 -2
- {spatialquery-0.0.5 → spatialquery-0.0.6}/README.md +14 -1
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/__init__.py +3 -1
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/plotting.py +82 -1
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/spatial_gene_covarying.py +1 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/spatial_query.py +216 -3
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/spatial_query_multiple_fov.py +452 -159
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/spatial_utils.py +301 -3
- {spatialquery-0.0.5 → spatialquery-0.0.6/SpatialQuery.egg-info}/PKG-INFO +15 -2
- {spatialquery-0.0.5 → spatialquery-0.0.6}/pyproject.toml +1 -1
- {spatialquery-0.0.5 → spatialquery-0.0.6}/LICENSE +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/MANIFEST.in +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/__init__.py +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/core_methods.py +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/EliasFano.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/QueryScore.cpp +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/QueryScore.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/Serialization.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/__init__.py +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/const.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/eliasFano.cpp +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/fp_growth.cpp +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/fp_growth.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/serialization.cpp +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/typedef.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/utils.cpp +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/scfind4sp/cpp_src/utils.h +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/spatial_differential_pattern.py +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery/utils.py +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery.egg-info/SOURCES.txt +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery.egg-info/dependency_links.txt +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery.egg-info/requires.txt +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/SpatialQuery.egg-info/top_level.txt +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/setup.cfg +0 -0
- {spatialquery-0.0.5 → spatialquery-0.0.6}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SpatialQuery
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Spatial query tools for analyzing spatial transcriptomics data
|
|
5
5
|
Author-email: Shaokun An <shan12@bwh.harvard.edu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -35,6 +35,10 @@ Requires-Dist: sphinx-copybutton; extra == "docs"
|
|
|
35
35
|
Requires-Dist: myst-parser; extra == "docs"
|
|
36
36
|
Dynamic: license-file
|
|
37
37
|
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="https://raw.githubusercontent.com/ShaokunAn/Spatial-Query/main/docs/_static/img/logo.png" alt="SpatialQuery" width="480">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
38
42
|
# SpatialQuery
|
|
39
43
|
|
|
40
44
|
SpatialQuery is a Python package for systematic spatial analysis of single-cell resolution spatial omics data. It provides a unified framework to discover, quantify, and compare recurring spatial cell type patterns — termed *motifs* — across single or multiple fields of view (FOVs).
|
|
@@ -47,6 +51,7 @@ Key capabilities include:
|
|
|
47
51
|
|
|
48
52
|
- **Spatial motif discovery**: Identify frequent cell type patterns in local neighborhoods
|
|
49
53
|
- **Motif enrichment analysis**: Statistically test whether motifs occur more than expected
|
|
54
|
+
- **Motif composition**: Quantify the relative abundance of each motif cell type within motif-positive neighborhoods
|
|
50
55
|
- **Differential motif analysis**: Compare spatial compositions across conditions
|
|
51
56
|
- **Motif-associated differential expression**: Find DE genes linked to specific motifs
|
|
52
57
|
- **Cross-cell gene-gene covariation**: Detect spatially dependent intercellular gene correlations
|
|
@@ -58,13 +63,15 @@ Key capabilities include:
|
|
|
58
63
|
pip install SpatialQuery
|
|
59
64
|
```
|
|
60
65
|
|
|
66
|
+
> **Note:** Installation typically takes ~5 minutes depending on your environment.
|
|
67
|
+
|
|
61
68
|
## Documentation
|
|
62
69
|
|
|
63
70
|
Full documentation, tutorials, and API reference: **[https://spatialquery.readthedocs.io/en/latest/](https://spatialquery.readthedocs.io/en/latest/)**
|
|
64
71
|
|
|
65
72
|
## Reproducibility
|
|
66
73
|
|
|
67
|
-
The following tutorials reproduce the main analyses presented in our manuscript
|
|
74
|
+
The following tutorials reproduce the main analyses presented in our manuscript. Each tutorial takes approximately 5–20 minutes to run depending on dataset size and hardware.
|
|
68
75
|
|
|
69
76
|
- [Mouse organogenesis — single-FOV analysis (seqFISH)](https://spatialquery.readthedocs.io/en/latest/tutorials/tutorial_1.html)
|
|
70
77
|
- [Kidney disease atlas — multi-condition analysis (MERFISH)](https://spatialquery.readthedocs.io/en/latest/tutorials/tutorial_2.html)
|
|
@@ -90,6 +97,12 @@ spm = spatial_query_multi(adatas=adatas, datasets=datasets,
|
|
|
90
97
|
enrich_motif = spm.motif_enrichment_dist(ct="T_cell", dataset="healthy", max_dist=10, min_support=0.5)
|
|
91
98
|
```
|
|
92
99
|
|
|
100
|
+
## Citation
|
|
101
|
+
|
|
102
|
+
If you use SpatialQuery in your research, please cite:
|
|
103
|
+
|
|
104
|
+
> An, S. et al. [SpatialQuery: scalable discovery and molecular characterization of multicellular motifs from spatial omics data](https://www.biorxiv.org/content/10.64898/2026.04.22.720136v1). *bioRxiv* (2026).
|
|
105
|
+
|
|
93
106
|
## License
|
|
94
107
|
|
|
95
108
|
MIT
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ShaokunAn/Spatial-Query/main/docs/_static/img/logo.png" alt="SpatialQuery" width="480">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# SpatialQuery
|
|
2
6
|
|
|
3
7
|
SpatialQuery is a Python package for systematic spatial analysis of single-cell resolution spatial omics data. It provides a unified framework to discover, quantify, and compare recurring spatial cell type patterns — termed *motifs* — across single or multiple fields of view (FOVs).
|
|
@@ -10,6 +14,7 @@ Key capabilities include:
|
|
|
10
14
|
|
|
11
15
|
- **Spatial motif discovery**: Identify frequent cell type patterns in local neighborhoods
|
|
12
16
|
- **Motif enrichment analysis**: Statistically test whether motifs occur more than expected
|
|
17
|
+
- **Motif composition**: Quantify the relative abundance of each motif cell type within motif-positive neighborhoods
|
|
13
18
|
- **Differential motif analysis**: Compare spatial compositions across conditions
|
|
14
19
|
- **Motif-associated differential expression**: Find DE genes linked to specific motifs
|
|
15
20
|
- **Cross-cell gene-gene covariation**: Detect spatially dependent intercellular gene correlations
|
|
@@ -21,13 +26,15 @@ Key capabilities include:
|
|
|
21
26
|
pip install SpatialQuery
|
|
22
27
|
```
|
|
23
28
|
|
|
29
|
+
> **Note:** Installation typically takes ~5 minutes depending on your environment.
|
|
30
|
+
|
|
24
31
|
## Documentation
|
|
25
32
|
|
|
26
33
|
Full documentation, tutorials, and API reference: **[https://spatialquery.readthedocs.io/en/latest/](https://spatialquery.readthedocs.io/en/latest/)**
|
|
27
34
|
|
|
28
35
|
## Reproducibility
|
|
29
36
|
|
|
30
|
-
The following tutorials reproduce the main analyses presented in our manuscript
|
|
37
|
+
The following tutorials reproduce the main analyses presented in our manuscript. Each tutorial takes approximately 5–20 minutes to run depending on dataset size and hardware.
|
|
31
38
|
|
|
32
39
|
- [Mouse organogenesis — single-FOV analysis (seqFISH)](https://spatialquery.readthedocs.io/en/latest/tutorials/tutorial_1.html)
|
|
33
40
|
- [Kidney disease atlas — multi-condition analysis (MERFISH)](https://spatialquery.readthedocs.io/en/latest/tutorials/tutorial_2.html)
|
|
@@ -53,6 +60,12 @@ spm = spatial_query_multi(adatas=adatas, datasets=datasets,
|
|
|
53
60
|
enrich_motif = spm.motif_enrichment_dist(ct="T_cell", dataset="healthy", max_dist=10, min_support=0.5)
|
|
54
61
|
```
|
|
55
62
|
|
|
63
|
+
## Citation
|
|
64
|
+
|
|
65
|
+
If you use SpatialQuery in your research, please cite:
|
|
66
|
+
|
|
67
|
+
> An, S. et al. [SpatialQuery: scalable discovery and molecular characterization of multicellular motifs from spatial omics data](https://www.biorxiv.org/content/10.64898/2026.04.22.720136v1). *bioRxiv* (2026).
|
|
68
|
+
|
|
56
69
|
## License
|
|
57
70
|
|
|
58
71
|
MIT
|
|
@@ -7,7 +7,8 @@ from .plotting import (
|
|
|
7
7
|
plot_motif_grid,
|
|
8
8
|
plot_motif_rand,
|
|
9
9
|
plot_motif_celltype,
|
|
10
|
-
plot_all_center_motif
|
|
10
|
+
plot_all_center_motif,
|
|
11
|
+
plot_motif_composition
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
@@ -22,5 +23,6 @@ __all__ = [
|
|
|
22
23
|
'plot_motif_rand',
|
|
23
24
|
'plot_motif_celltype',
|
|
24
25
|
'plot_all_center_motif',
|
|
26
|
+
'plot_motif_composition',
|
|
25
27
|
'interactive_motif'
|
|
26
28
|
]
|
|
@@ -670,7 +670,88 @@ def plot_motif_enrichment_heatmap(enrich_df: pd.DataFrame,
|
|
|
670
670
|
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
671
671
|
|
|
672
672
|
plt.show()
|
|
673
|
-
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def plot_motif_composition(comp_df: pd.DataFrame,
|
|
676
|
+
motif: Optional[List[str]] = None,
|
|
677
|
+
bins: int = 25,
|
|
678
|
+
colors: Optional[List] = None,
|
|
679
|
+
figsize: tuple = (6, 5),
|
|
680
|
+
title: Optional[str] = None,
|
|
681
|
+
save_path: Optional[str] = None):
|
|
682
|
+
"""
|
|
683
|
+
Plot the distribution of each motif cell type's proportion across motif-positive center cells.
|
|
684
|
+
|
|
685
|
+
One semi-transparent histogram per motif cell type, on fixed bins over [0, 1]; the legend
|
|
686
|
+
reports the median and interquartile range (IQR) of each type's proportion.
|
|
687
|
+
|
|
688
|
+
Parameters
|
|
689
|
+
----------
|
|
690
|
+
comp_df : pd.DataFrame
|
|
691
|
+
Output from motif_composition_dist or motif_composition_knn.
|
|
692
|
+
motif : List[str], optional
|
|
693
|
+
Motif to plot. May be omitted when comp_df holds a single motif.
|
|
694
|
+
bins : int, default=25
|
|
695
|
+
Number of equal-width bins on [0, 1].
|
|
696
|
+
colors : List, optional
|
|
697
|
+
One color per motif cell type, in sorted cell type order. Defaults to the tab10 palette.
|
|
698
|
+
figsize : tuple, default=(6, 5)
|
|
699
|
+
Figure size.
|
|
700
|
+
title : str, optional
|
|
701
|
+
Figure title. Defaults to the center cell type and the motif.
|
|
702
|
+
save_path : str, optional
|
|
703
|
+
Path to save the figure. If None, the figure will not be saved.
|
|
704
|
+
|
|
705
|
+
Returns
|
|
706
|
+
-------
|
|
707
|
+
None
|
|
708
|
+
Displays a histogram of motif cell type proportions.
|
|
709
|
+
"""
|
|
710
|
+
if len(comp_df) == 0:
|
|
711
|
+
print("No motif-positive center cells to plot.")
|
|
712
|
+
return
|
|
713
|
+
|
|
714
|
+
keys = comp_df['motifs'].map(spatial_utils.canonical_motif)
|
|
715
|
+
if motif is None:
|
|
716
|
+
if keys.nunique() > 1:
|
|
717
|
+
raise ValueError("comp_df holds several motifs; specify which one to plot with motif.")
|
|
718
|
+
motif_key = keys.iloc[0]
|
|
719
|
+
else:
|
|
720
|
+
motif_key = spatial_utils.canonical_motif([motif] if isinstance(motif, str) else motif)
|
|
721
|
+
df = comp_df[keys == motif_key]
|
|
722
|
+
if len(df) == 0:
|
|
723
|
+
raise ValueError(f"Motif {list(motif_key)} is not in comp_df.")
|
|
724
|
+
|
|
725
|
+
cell_types = list(motif_key)
|
|
726
|
+
if colors is None:
|
|
727
|
+
colors = [plt.cm.tab10(i % 10) for i in range(len(cell_types))]
|
|
728
|
+
bin_edges = np.linspace(0, 1, bins + 1)
|
|
729
|
+
|
|
730
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
731
|
+
for cell_type, color in zip(cell_types, colors):
|
|
732
|
+
values = df.loc[df['cell_type'] == cell_type, 'proportion'].to_numpy(dtype=float)
|
|
733
|
+
q25, median, q75 = np.percentile(values, [25, 50, 75])
|
|
734
|
+
ax.hist(values, bins=bin_edges, color=color, alpha=0.5,
|
|
735
|
+
label=f'{cell_type}\n(median={median:.2f}, IQR={q75 - q25:.2f})')
|
|
736
|
+
|
|
737
|
+
ax.set_xlim(-0.03, 1.03)
|
|
738
|
+
ax.set_xlabel('Relative abundance among motif-associated cells', fontsize=12)
|
|
739
|
+
ax.set_ylabel('Number of motif-positive anchors', fontsize=12)
|
|
740
|
+
ax.spines[['top', 'right']].set_visible(False)
|
|
741
|
+
ax.legend(frameon=False, fontsize=10, loc='best')
|
|
742
|
+
|
|
743
|
+
if title is None:
|
|
744
|
+
centers = df['center'].unique()
|
|
745
|
+
center = f'{centers[0]} | ' if len(centers) == 1 else ''
|
|
746
|
+
title = f'{center}{" + ".join(cell_types)}'
|
|
747
|
+
ax.set_title(title, fontsize=13)
|
|
748
|
+
plt.tight_layout()
|
|
749
|
+
|
|
750
|
+
if save_path is not None:
|
|
751
|
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
752
|
+
|
|
753
|
+
plt.show()
|
|
754
|
+
|
|
674
755
|
|
|
675
756
|
def plot_differential_pattern_heatmap(
|
|
676
757
|
diff_motif: dict,
|
|
@@ -4395,6 +4395,7 @@ def compute_gene_gene_correlation_by_type_binary_multi_fov(
|
|
|
4395
4395
|
if len(non_center_types) == 1:
|
|
4396
4396
|
print(f"Only one non-center cell type in motif: {non_center_types}. Using compute_gene_gene_correlation_binary method.")
|
|
4397
4397
|
result = compute_gene_gene_correlation_binary_multi_fov(
|
|
4398
|
+
sq_objs=sq_objs,
|
|
4398
4399
|
ct=ct,
|
|
4399
4400
|
motif=motif,
|
|
4400
4401
|
dataset=dataset,
|
|
@@ -157,6 +157,7 @@ class spatial_query:
|
|
|
157
157
|
k: int = 30,
|
|
158
158
|
min_support: float = 0.5,
|
|
159
159
|
max_dist: float = 20,
|
|
160
|
+
if_max: bool = True,
|
|
160
161
|
) -> pd.DataFrame:
|
|
161
162
|
"""
|
|
162
163
|
Find frequent patterns within the KNNs of certain cell type.
|
|
@@ -171,6 +172,9 @@ class spatial_query:
|
|
|
171
172
|
Threshold of frequency to consider a pattern as a frequent pattern.
|
|
172
173
|
max_dist : float, default=20
|
|
173
174
|
Maximum distance for considering a cell as a neighbor.
|
|
175
|
+
if_max : bool, default=True
|
|
176
|
+
Return only maximal patterns. Set to False to return every frequent pattern,
|
|
177
|
+
which nested motif enrichment needs to look up sub-pattern supports.
|
|
174
178
|
|
|
175
179
|
Returns
|
|
176
180
|
-------
|
|
@@ -190,7 +194,7 @@ class spatial_query:
|
|
|
190
194
|
k=k,
|
|
191
195
|
min_support=min_support,
|
|
192
196
|
max_dist=max_dist,
|
|
193
|
-
if_max=
|
|
197
|
+
if_max=if_max
|
|
194
198
|
)
|
|
195
199
|
|
|
196
200
|
return fp
|
|
@@ -200,6 +204,7 @@ class spatial_query:
|
|
|
200
204
|
max_dist: float = 20,
|
|
201
205
|
min_size: int = 0,
|
|
202
206
|
min_support: float = 0.5,
|
|
207
|
+
if_max: bool = True,
|
|
203
208
|
):
|
|
204
209
|
"""
|
|
205
210
|
Find frequent patterns within the radius of certain cell type.
|
|
@@ -214,6 +219,9 @@ class spatial_query:
|
|
|
214
219
|
Minimum neighborhood size for each point to consider.
|
|
215
220
|
min_support : float, default=0.5
|
|
216
221
|
Threshold of frequency to consider a pattern as a frequent pattern.
|
|
222
|
+
if_max : bool, default=True
|
|
223
|
+
Return only maximal patterns. Set to False to return every frequent pattern,
|
|
224
|
+
which nested motif enrichment needs to look up sub-pattern supports.
|
|
217
225
|
|
|
218
226
|
Returns
|
|
219
227
|
-------
|
|
@@ -233,7 +241,8 @@ class spatial_query:
|
|
|
233
241
|
max_dist=max_dist,
|
|
234
242
|
min_size=min_size,
|
|
235
243
|
min_support=min_support,
|
|
236
|
-
cinds=cinds
|
|
244
|
+
cinds=cinds,
|
|
245
|
+
if_max=if_max
|
|
237
246
|
)
|
|
238
247
|
|
|
239
248
|
return fp
|
|
@@ -244,7 +253,8 @@ class spatial_query:
|
|
|
244
253
|
k: int = 30,
|
|
245
254
|
min_support: float = 0.5,
|
|
246
255
|
max_dist: float = 20,
|
|
247
|
-
return_cellID: bool = False
|
|
256
|
+
return_cellID: bool = False,
|
|
257
|
+
mode: str = 'maximal'
|
|
248
258
|
) -> pd.DataFrame:
|
|
249
259
|
"""
|
|
250
260
|
Perform motif enrichment analysis using k-nearest neighbors (KNN).
|
|
@@ -265,6 +275,16 @@ class spatial_query:
|
|
|
265
275
|
return_cellID : bool, default=False
|
|
266
276
|
Indicate whether return cell IDs for each frequent pattern within the neighborhood of grid points.
|
|
267
277
|
By defaults do not return cell ID.
|
|
278
|
+
mode : {'maximal', 'nested'}, default='maximal'
|
|
279
|
+
How the motifs to test are chosen when motifs=None. Ignored when motifs is given.
|
|
280
|
+
|
|
281
|
+
'maximal'
|
|
282
|
+
Test the maximal patterns only.
|
|
283
|
+
'nested'
|
|
284
|
+
Test the maximal patterns, then descend into the sub-patterns of any that
|
|
285
|
+
are not significant, stopping a branch as soon as it becomes significant or
|
|
286
|
+
reaches length 1. Finds cores that a maximal pattern hides when one abundant
|
|
287
|
+
cell type dilutes it. Multiple-testing correction covers everything tested.
|
|
268
288
|
|
|
269
289
|
Returns
|
|
270
290
|
-------
|
|
@@ -285,6 +305,27 @@ class spatial_query:
|
|
|
285
305
|
if ct not in self.labels.unique():
|
|
286
306
|
raise ValueError(f"Found no {ct} in {self.label_key}!")
|
|
287
307
|
|
|
308
|
+
if mode not in ('nested', 'maximal'):
|
|
309
|
+
raise ValueError(f"mode must be 'nested' or 'maximal', got {mode!r}")
|
|
310
|
+
|
|
311
|
+
# Nested descent only decides WHICH motifs get tested; each batch is scored by this
|
|
312
|
+
# same method, so the statistics are identical to the maximal mode's.
|
|
313
|
+
if mode == 'nested' and motifs is None:
|
|
314
|
+
fp_all = self.find_fp_knn(ct=ct, k=k, min_support=min_support,
|
|
315
|
+
max_dist=max_dist, if_max=False)
|
|
316
|
+
if len(fp_all) == 0:
|
|
317
|
+
return spatial_utils.apply_fdr_correction(pd.DataFrame())
|
|
318
|
+
# Cell IDs are DataFrame columns here, so they travel with the rows and need
|
|
319
|
+
# no separate merging.
|
|
320
|
+
return spatial_utils.nested_motif_descent(
|
|
321
|
+
test_motifs=lambda ms: self.motif_enrichment_knn(
|
|
322
|
+
ct=ct, motifs=ms, k=k, min_support=min_support, max_dist=max_dist,
|
|
323
|
+
return_cellID=return_cellID, mode='maximal'),
|
|
324
|
+
maximal_motifs=spatial_utils.find_maximal_patterns(fp_all)['itemsets'],
|
|
325
|
+
support_of={spatial_utils.canonical_motif(i): s
|
|
326
|
+
for i, s in zip(fp_all['itemsets'], fp_all['support'])},
|
|
327
|
+
)
|
|
328
|
+
|
|
288
329
|
dists, idxs = self.kd_tree.query(self.spatial_pos,
|
|
289
330
|
k=k + 1, workers=-1
|
|
290
331
|
) # use k+1 to find the knn except for the points themselves
|
|
@@ -418,6 +459,7 @@ class spatial_query:
|
|
|
418
459
|
min_size: int = 0,
|
|
419
460
|
min_support: float = 0.5,
|
|
420
461
|
return_cellID: bool = False,
|
|
462
|
+
mode: str = 'maximal',
|
|
421
463
|
) -> DataFrame:
|
|
422
464
|
"""
|
|
423
465
|
Perform motif enrichment analysis within a specified radius-based neighborhood.
|
|
@@ -438,6 +480,16 @@ class spatial_query:
|
|
|
438
480
|
return_cellID : bool, default=False
|
|
439
481
|
Indicate whether return cell IDs for each motif within the neighborhood of central cell type.
|
|
440
482
|
By defaults do not return cell ID.
|
|
483
|
+
mode : {'maximal', 'nested'}, default='maximal'
|
|
484
|
+
How the motifs to test are chosen when motifs=None. Ignored when motifs is given.
|
|
485
|
+
|
|
486
|
+
'maximal'
|
|
487
|
+
Test the maximal patterns only.
|
|
488
|
+
'nested'
|
|
489
|
+
Test the maximal patterns, then descend into the sub-patterns of any that
|
|
490
|
+
are not significant, stopping a branch as soon as it becomes significant or
|
|
491
|
+
reaches length 1. Finds cores that a maximal pattern hides when one abundant
|
|
492
|
+
cell type dilutes it. Multiple-testing correction covers everything tested.
|
|
441
493
|
|
|
442
494
|
Returns
|
|
443
495
|
-------
|
|
@@ -458,6 +510,28 @@ class spatial_query:
|
|
|
458
510
|
if ct not in self.labels.unique():
|
|
459
511
|
raise ValueError(f"Found no {ct} in {self.label_key}!")
|
|
460
512
|
|
|
513
|
+
if mode not in ('nested', 'maximal'):
|
|
514
|
+
raise ValueError(f"mode must be 'nested' or 'maximal', got {mode!r}")
|
|
515
|
+
|
|
516
|
+
# Nested descent only decides WHICH motifs get tested; each batch is scored by this
|
|
517
|
+
# same method, so the statistics are identical to the maximal mode's.
|
|
518
|
+
if mode == 'nested' and motifs is None:
|
|
519
|
+
fp_all = self.find_fp_dist(ct=ct, max_dist=max_dist, min_size=min_size,
|
|
520
|
+
min_support=min_support, if_max=False)
|
|
521
|
+
if len(fp_all) == 0:
|
|
522
|
+
return spatial_utils.apply_fdr_correction(pd.DataFrame())
|
|
523
|
+
# Cell IDs are DataFrame columns here, so they travel with the rows and need
|
|
524
|
+
# no separate merging.
|
|
525
|
+
return spatial_utils.nested_motif_descent(
|
|
526
|
+
test_motifs=lambda ms: self.motif_enrichment_dist(
|
|
527
|
+
ct=ct, motifs=ms, max_dist=max_dist, min_size=min_size,
|
|
528
|
+
min_support=min_support, return_cellID=return_cellID,
|
|
529
|
+
mode='maximal'),
|
|
530
|
+
maximal_motifs=spatial_utils.find_maximal_patterns(fp_all)['itemsets'],
|
|
531
|
+
support_of={spatial_utils.canonical_motif(i): s
|
|
532
|
+
for i, s in zip(fp_all['itemsets'], fp_all['support'])},
|
|
533
|
+
)
|
|
534
|
+
|
|
461
535
|
out = []
|
|
462
536
|
if motifs is None:
|
|
463
537
|
fp = self.find_fp_dist(ct=ct,
|
|
@@ -576,6 +650,109 @@ class spatial_query:
|
|
|
576
650
|
out_pd = out_pd.sort_values(by='adj-pval', ignore_index=True)
|
|
577
651
|
return out_pd
|
|
578
652
|
|
|
653
|
+
def motif_composition_dist(self,
|
|
654
|
+
ct: str,
|
|
655
|
+
motifs: Union[str, List[str], List[List[str]]],
|
|
656
|
+
max_dist: float = 20,
|
|
657
|
+
) -> pd.DataFrame:
|
|
658
|
+
"""
|
|
659
|
+
Compute the composition of motif cell types in the radius-based neighborhoods of center cells.
|
|
660
|
+
|
|
661
|
+
Uses the same neighborhoods as motif_enrichment_dist, but performs no enrichment test.
|
|
662
|
+
Only center cells whose neighborhood contains every cell type of the motif (motif-positive
|
|
663
|
+
centers) are kept. For each of them, the proportion of a motif cell type is its number of
|
|
664
|
+
cells divided by the total number of motif-type cells in that neighborhood.
|
|
665
|
+
|
|
666
|
+
Parameters
|
|
667
|
+
----------
|
|
668
|
+
ct : str
|
|
669
|
+
Cell type as the center cells.
|
|
670
|
+
motifs : str or List[str] or List[List[str]]
|
|
671
|
+
Motifs to analyze, e.g. the 'motifs' column of motif_enrichment_dist output.
|
|
672
|
+
max_dist : float, default=20
|
|
673
|
+
Maximum distance for considering a cell as a neighbor.
|
|
674
|
+
|
|
675
|
+
Returns
|
|
676
|
+
-------
|
|
677
|
+
pd.DataFrame
|
|
678
|
+
Long-format table with one row per (motif-positive center cell, motif cell type):
|
|
679
|
+
- center: center cell type name
|
|
680
|
+
- motifs: sorted list of cell types in the motif
|
|
681
|
+
- cell_type: motif cell type this row describes
|
|
682
|
+
- n_cells: number of cells of cell_type in the neighborhood
|
|
683
|
+
- n_motif_cells: number of motif-type cells in the neighborhood
|
|
684
|
+
- proportion: n_cells / n_motif_cells
|
|
685
|
+
"""
|
|
686
|
+
if ct not in self.labels.unique():
|
|
687
|
+
raise ValueError(f"Found no {ct} in {self.label_key}!")
|
|
688
|
+
motifs = spatial_utils.normalize_motif_list(motifs, self.labels.unique(), self.label_key)
|
|
689
|
+
|
|
690
|
+
label_encoder = LabelEncoder()
|
|
691
|
+
int_labels = label_encoder.fit_transform(np.array(self.labels))
|
|
692
|
+
cinds = np.where(self.labels == ct)[0]
|
|
693
|
+
counts = spatial_utils.anchor_neighbor_counts_dist(
|
|
694
|
+
self.kd_tree, self.spatial_pos, cinds, int_labels,
|
|
695
|
+
len(label_encoder.classes_), max_dist)
|
|
696
|
+
|
|
697
|
+
out = [spatial_utils.motif_composition_table(
|
|
698
|
+
ct, motif, counts[:, label_encoder.transform(np.array(motif))]) for motif in motifs]
|
|
699
|
+
if len(out) == 0:
|
|
700
|
+
return pd.DataFrame(columns=spatial_utils.COMPOSITION_COLUMNS)
|
|
701
|
+
return pd.concat(out, ignore_index=True)
|
|
702
|
+
|
|
703
|
+
def motif_composition_knn(self,
|
|
704
|
+
ct: str,
|
|
705
|
+
motifs: Union[str, List[str], List[List[str]]],
|
|
706
|
+
k: int = 30,
|
|
707
|
+
max_dist: float = 20,
|
|
708
|
+
) -> pd.DataFrame:
|
|
709
|
+
"""
|
|
710
|
+
Compute the composition of motif cell types in the k-nearest-neighbor neighborhoods of center cells.
|
|
711
|
+
|
|
712
|
+
Uses the same neighborhoods as motif_enrichment_knn, but performs no enrichment test.
|
|
713
|
+
Only center cells whose neighborhood contains every cell type of the motif (motif-positive
|
|
714
|
+
centers) are kept. For each of them, the proportion of a motif cell type is its number of
|
|
715
|
+
cells divided by the total number of motif-type cells in that neighborhood.
|
|
716
|
+
|
|
717
|
+
Parameters
|
|
718
|
+
----------
|
|
719
|
+
ct : str
|
|
720
|
+
Cell type of the center cell.
|
|
721
|
+
motifs : str or List[str] or List[List[str]]
|
|
722
|
+
Motifs to analyze, e.g. the 'motifs' column of motif_enrichment_knn output.
|
|
723
|
+
k : int, default=30
|
|
724
|
+
Number of nearest neighbors to consider.
|
|
725
|
+
max_dist : float, default=20
|
|
726
|
+
Maximum distance for neighbors.
|
|
727
|
+
|
|
728
|
+
Returns
|
|
729
|
+
-------
|
|
730
|
+
pd.DataFrame
|
|
731
|
+
Long-format table with one row per (motif-positive center cell, motif cell type):
|
|
732
|
+
- center: center cell type name
|
|
733
|
+
- motifs: sorted list of cell types in the motif
|
|
734
|
+
- cell_type: motif cell type this row describes
|
|
735
|
+
- n_cells: number of cells of cell_type in the neighborhood
|
|
736
|
+
- n_motif_cells: number of motif-type cells in the neighborhood
|
|
737
|
+
- proportion: n_cells / n_motif_cells
|
|
738
|
+
"""
|
|
739
|
+
if ct not in self.labels.unique():
|
|
740
|
+
raise ValueError(f"Found no {ct} in {self.label_key}!")
|
|
741
|
+
motifs = spatial_utils.normalize_motif_list(motifs, self.labels.unique(), self.label_key)
|
|
742
|
+
|
|
743
|
+
label_encoder = LabelEncoder()
|
|
744
|
+
int_labels = label_encoder.fit_transform(np.array(self.labels))
|
|
745
|
+
cinds = np.where(self.labels == ct)[0]
|
|
746
|
+
counts = spatial_utils.anchor_neighbor_counts_knn(
|
|
747
|
+
self.kd_tree, self.spatial_pos, cinds, int_labels,
|
|
748
|
+
len(label_encoder.classes_), k, max_dist)
|
|
749
|
+
|
|
750
|
+
out = [spatial_utils.motif_composition_table(
|
|
751
|
+
ct, motif, counts[:, label_encoder.transform(np.array(motif))]) for motif in motifs]
|
|
752
|
+
if len(out) == 0:
|
|
753
|
+
return pd.DataFrame(columns=spatial_utils.COMPOSITION_COLUMNS)
|
|
754
|
+
return pd.concat(out, ignore_index=True)
|
|
755
|
+
|
|
579
756
|
|
|
580
757
|
def find_patterns_grid(self,
|
|
581
758
|
max_dist: float = 20,
|
|
@@ -1404,6 +1581,42 @@ class spatial_query:
|
|
|
1404
1581
|
"""
|
|
1405
1582
|
return plotting.plot_motif_enrichment_heatmap(enrich_df=enrich_df, figsize=figsize,
|
|
1406
1583
|
save_path=save_path, title=title, cmap=cmap)
|
|
1584
|
+
|
|
1585
|
+
def plot_motif_composition(self,
|
|
1586
|
+
comp_df: pd.DataFrame,
|
|
1587
|
+
motif: Optional[List[str]] = None,
|
|
1588
|
+
bins: int = 25,
|
|
1589
|
+
colors: Optional[List] = None,
|
|
1590
|
+
figsize: tuple = (6, 5),
|
|
1591
|
+
title: Optional[str] = None,
|
|
1592
|
+
save_path: Optional[str] = None):
|
|
1593
|
+
"""
|
|
1594
|
+
Plot the distribution of each motif cell type's proportion across motif-positive center cells.
|
|
1595
|
+
|
|
1596
|
+
Parameters
|
|
1597
|
+
----------
|
|
1598
|
+
comp_df : pd.DataFrame
|
|
1599
|
+
Output DataFrame from motif_composition_dist or motif_composition_knn.
|
|
1600
|
+
motif : List[str], optional
|
|
1601
|
+
Motif to plot. May be omitted when comp_df holds a single motif.
|
|
1602
|
+
bins : int, default=25
|
|
1603
|
+
Number of equal-width bins on [0, 1].
|
|
1604
|
+
colors : List, optional
|
|
1605
|
+
One color per motif cell type, in sorted cell type order. Defaults to the tab10 palette.
|
|
1606
|
+
figsize : tuple, default=(6, 5)
|
|
1607
|
+
Figure size.
|
|
1608
|
+
title : str, optional
|
|
1609
|
+
Figure title. Defaults to the center cell type and the motif.
|
|
1610
|
+
save_path : str, optional
|
|
1611
|
+
Path to save the figure. If None, the figure will not be saved.
|
|
1612
|
+
|
|
1613
|
+
Returns
|
|
1614
|
+
-------
|
|
1615
|
+
None
|
|
1616
|
+
Displays a histogram of motif cell type proportions, with median and IQR in the legend.
|
|
1617
|
+
"""
|
|
1618
|
+
return plotting.plot_motif_composition(comp_df=comp_df, motif=motif, bins=bins, colors=colors,
|
|
1619
|
+
figsize=figsize, title=title, save_path=save_path)
|
|
1407
1620
|
|
|
1408
1621
|
def plot_gene_pair_heatmap(self,
|
|
1409
1622
|
gene_pair_df: pd.DataFrame,
|