Comparisons between groups of samples
@selmanozleyen is already working on this.
Since Dec 11, 2025.
- Dominant language
- Python
- Stars
- 598
- Forks
- 121
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 3
Description
Most functions in squidpy seem to be centered around analyzing a single sample.
For me, in the context of clinical trials, it would be important to compare between sample groups. The usual variables of interest are
- comparisons of samples over time (pre-treatment vs. on-treatment), i.e. "how does our drug influence the tissue?" and
- comparisons of responders vs. non-responders, i.e. "What could explain treatment failure?"
A few things that came to my mind
- Differential ligand/receptor interactions between groups
- Differential neighborhood analysis (e.g. tumor more infiltrated after treatment?)
- differential spatial distance (e.g. tumor and certain immune cells closer together in responders?)
Would be great to have dedicated functions for this, and happy about further ideas!
CC @Zethson, because this is basically "spatial pertpy"
Example data
- Myocardial infarction, AMI vs. ICM vs. control; https://www.nature.com/articles/s41586-022-05060-x#additional-information
- Cellcharter mouse cortex data:
adata = cc.datasets.codex_mouse_spleen('./data/codex_mouse_spleen.h5ad')-> 3 vs. 6
Collection of use-cases and implementation ideas
Differential neighborhood enrichment
Sharing here a very simple approach that estimates for each "niche" (e.g. tumor spots), the average neighborhood (i.e. across all spots of that niche, what's the fraction of neighboring niches). This allows to distinguish, for instance, between immune-infiltrated and immune-excluded tumors.
# adata_vis -> AnnData with visium data, contains multiple samples
# "niche_leiden" -> categorical annotation of niches
res2 = []
for sample in samples:
tmp_ad = select_slide(adata_vis, sample)
sq.gr.spatial_neighbors(tmp_ad)
res = []
for spot in range(tmp_ad.shape[0]):
spot_neighbors = tmp_ad.obsp["spatial_distances"][spot, :].todense().A1.astype(bool)
current_niche = tmp_ad.obs["niche_leiden"][spot]
res.append(tmp_ad.obs["niche_leiden"][spot_neighbors].value_counts().to_frame().T.assign(current_niche = current_niche))
res_df = pd.concat(res).groupby("current_niche").agg("mean").assign(sample = sample)
res2.append(res_df)
neighbors_per_sample = pd.concat(res2).reset_index(drop=False).set_index(["current_niche", "sample"]).pipe(lambda x: x.div(np.sum(x, axis=1), axis=0))
This results in something like this (each column a sample):
Statistics between groups of samples can be computed using a standard linear model.
Visualization idea:
(but instead color heatmap by enriched/depleted)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.