Proximity scores¶
Co-occurrence tells you whether two cell types interact. Proximity scores tell you how much — what fraction of the tissue, and what fraction of the relevant cells, sit in the interaction zone.
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
import scimappro as sp
# The demo data is not shipped with the package (it is 247 MB). Point DATA at
# wherever you unpacked it; this looks in the repository root first, then in the
# path the docs are built from.
DATA = next(
path for path in (Path("example_data"), Path("../../../example_data"))
if path.exists()
)
DATA
PosixPath('../../../example_data')
import anndata as ad
adata = ad.read_h5ad(DATA / "adata_scimap.h5ad")
adata = sp.pp.rescale(adata, gate=str(DATA / "manual_gates.csv"), verbose=False)
adata = sp.tl.phenotype(adata, phenotype=str(DATA / "phenotype_workflow.csv"), verbose=False)
adata.obs["phenotype"].value_counts()
phenotype ECAD+ 7112 Other myeloid cells 2419 Dendritic cells 863 SMA+ 509 Treg 216 Unknown 46 NK cells 35 Immune 1 Name: count, dtype: int64
Two scores¶
A cell is in the interaction zone when its neighbourhood contains every cell
type you list in proximity. Two numbers summarise that per image:
- Proximity Volume — interaction-zone cells as a fraction of all cells. How much of the tissue is involved.
- Proximity Density — interaction-zone cells as a fraction of the cells of interest. How much of the relevant population is involved.
They answer different questions. A sample can have a tiny volume (few Tregs overall) and a high density (nearly every Treg is touching tumour).
adata = sp.tl.spatialProximityScore(
adata,
proximity=["Treg", "ECAD+"],
method="radius",
radius=20,
verbose=False,
)
adata.uns["spatial_pscore"]
| spatial_pscore | Treg_ECAD+ | All Cells | Proximity Volume | Celltype of interest | Proximity Density |
|---|---|---|---|---|---|
| imageid | |||||
| quant | 565 | 11201 | 0.050442 | 7328 | 0.049809 |
sp.pl.spatialProximityScore(adata)
Where the interacting cells are¶
The per-cell labels go into obs, so they can be mapped like any other column.
adata.obs["spatial_pscore"].value_counts()
spatial_pscore other 10514 Treg_ECAD+ 687 Name: count, dtype: int64
sp.pl.spatialScatterPlot(adata, colorBy="spatial_pscore", s=2, figsize=(5, 5),
fontSize=7)
Scoring by something other than the image¶
scoreBy decides what the summary rows are. With one image it has to be
imageid, but with a cohort you would score by patient, by treatment arm, or by
any ROI column.
median_x = adata.obs["X_centroid"].median()
adata.obs["region"] = ["left" if x < median_x else "right"
for x in adata.obs["X_centroid"]]
byRegion = sp.tl.spatialProximityScore(
adata.copy(),
proximity=["Treg", "ECAD+"],
scoreBy="region",
radius=20,
label="pscore_region",
verbose=False,
)
byRegion.uns["pscore_region"]
| pscore_region | Treg_ECAD+ | All Cells | Proximity Volume | Celltype of interest | Proximity Density |
|---|---|---|---|---|---|
| region | |||||
| left | 310 | 5600 | 0.055357 | 3618 | 0.056108 |
| right | 255 | 5601 | 0.045528 | 3710 | 0.043666 |
Tuning the radius¶
The radius is what "interacting" means, and the scores move with it. Report the value you used.
for radius in (10, 20, 40):
scored = sp.tl.spatialProximityScore(
adata.copy(), proximity=["Treg", "ECAD+"], radius=radius,
label=f"pscore_{radius}", verbose=False,
)
row = scored.uns[f"pscore_{radius}"].iloc[0]
print(f"radius={radius:3d} volume={row['Proximity Volume']:.4f} "
f"density={row['Proximity Density']:.4f}")
radius= 10 volume=0.0029 density=0.0041 radius= 20 volume=0.0504 density=0.0498 radius= 40 volume=0.2177 density=0.1683
More than two cell types¶
proximity takes any number. A cell then has to have all of them nearby, which
gets restrictive quickly.
triple = sp.tl.spatialProximityScore(
adata.copy(),
proximity=["Treg", "ECAD+", "Dendritic cells"],
radius=30,
label="pscore_triple",
verbose=False,
)
triple.uns["pscore_triple"]
| pscore_triple | Treg_ECAD+_Dendritic cells | All Cells | Proximity Volume | Celltype of interest | Proximity Density |
|---|---|---|---|---|---|
| imageid | |||||
| quant | 482 | 11201 | 0.043032 | 8191 | 0.037846 |
Next¶
- Search patterns — find everywhere in the slide that looks like a region you picked out.
- Latent motifs — recurring neighbourhood compositions.