scverse / scverse/squidpy

Comparisons between groups of samples

Open
#784 7 comments 5 reactions 1 assignee View on GitHub

@selmanozleyen is already working on this.

Since Dec 11, 2025.

diff analysis :bar_chart: squidpy2.0 workstream
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

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):
image

Statistics between groups of samples can be computed using a standard linear model.

Visualization idea:
image
(but instead color heatmap by enriched/depleted)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.