Source code for metalsinglecell.gr

"""AnnData ``gr`` namespace — drop-in mirror of ``squidpy.gr``.

Spatial graph functions: read ``adata.obsm['spatial']`` / ``adata.obs[cluster_key]`` /
``adata.obsp['spatial_connectivities']`` and write results to the slots squidpy uses
(``obsp['spatial_connectivities']``, ``uns['moranI']``/``['gearyC']``,
``uns[f'{cluster_key}_co_occurrence']``, ``uns[f'{cluster_key}_ligrec']``, ``obs['niche']``).
So ``sq.gr`` pipelines work by swapping ``sq.gr`` → ``msc.gr``.
"""

from __future__ import annotations

import numpy as np

from . import spatial as _gr


[docs] def spatial_neighbors(adata, n_neighs: int = 6, coord_type: str = "generic", copy: bool = False): """Spatial graph from coordinates (``sq.gr.spatial_neighbors``); writes ``obsp['spatial_*']``.""" adata = adata.copy() if copy else adata A = _gr.spatial_neighbors(np.asarray(adata.obsm["spatial"], dtype=np.float32), n_neighs=n_neighs) adata.obsp["spatial_connectivities"] = A adata.obsp["spatial_distances"] = A # binary graph; squidpy keeps a parallel distances key adata.uns["spatial_neighbors"] = {"connectivities_key": "spatial_connectivities", "distances_key": "spatial_distances", "params": {"n_neighbors": n_neighs, "coord_type": coord_type}} return adata if copy else None
[docs] def spatial_autocorr(adata, mode: str = "moran", genes=None, n_perms: int | None = 100, connectivity_key: str = "spatial_connectivities", layer=None, seed: int = 0, copy: bool = False): """Moran's I / Geary's C per gene (``sq.gr.spatial_autocorr``); writes ``uns['moranI']``/``['gearyC']``.""" import pandas as pd import scipy.sparse as sp adata = adata.copy() if copy else adata if genes is None: genes = (adata.var_names[adata.var["highly_variable"].to_numpy()] if "highly_variable" in adata.var else adata.var_names).tolist() gi = [adata.var_names.get_loc(g) for g in genes] X = adata.layers[layer] if layer is not None else adata.X Xg = np.asarray(X[:, gi].todense() if sp.issparse(X) else X[:, gi], dtype=np.float32) out = _gr.spatial_autocorr(Xg, adata.obsp[connectivity_key], mode=mode, n_perms=n_perms or 0, random_state=seed) stat = "I" if mode == "moran" else "C" df = pd.DataFrame({stat: out[mode], "pval_sim": out["pval"]}, index=genes).sort_values(stat, ascending=False) adata.uns["moranI" if mode == "moran" else "gearyC"] = df return adata if copy else None
[docs] def co_occurrence(adata, cluster_key, interval: int = 50, copy: bool = False): """Cluster co-occurrence vs distance (``sq.gr.co_occurrence``); writes ``uns[f'{cluster_key}_co_occurrence']``.""" adata = adata.copy() if copy else adata res = _gr.co_occurrence(np.asarray(adata.obsm["spatial"], dtype=np.float32), adata.obs[cluster_key].to_numpy(), n_intervals=interval) adata.uns[f"{cluster_key}_co_occurrence"] = {"occ": res["occ"], "interval": res["interval"]} return adata if copy else None
[docs] def ligrec(adata, cluster_key, interactions, n_perms: int = 100, seed: int = 0, key_added: str | None = None, copy: bool = False): """Ligand-receptor permutation test (``sq.gr.ligrec``); writes ``uns[key_added]`` (means/pvalues). ``interactions`` is a list of ``(ligand, receptor)`` gene-symbol pairs. """ adata = adata.copy() if copy else adata res = _gr.ligrec(adata.X, adata.obs[cluster_key].to_numpy(), list(interactions), adata.var_names.to_numpy(), n_perms=n_perms, random_state=seed) adata.uns[key_added or f"{cluster_key}_ligrec"] = { "means": res["means"], "pvalues": res["pvalues"], "categories": res["categories"], "interactions": res["lr_pairs"]} return adata if copy else None
[docs] def calculate_niche(adata, cluster_key, n_niches: int = 10, connectivity_key: str = "spatial_connectivities", key_added: str = "niche", random_state: int = 0, copy: bool = False): """Spatial niches from neighborhood composition (``sq.gr.calculate_niche``); writes ``obs[key_added]``.""" import pandas as pd adata = adata.copy() if copy else adata res = _gr.calculate_niche(adata.obsp[connectivity_key], adata.obs[cluster_key].to_numpy(), n_niches=n_niches, random_state=random_state) adata.obs[key_added] = pd.Categorical([str(x) for x in res["niche"]]) return adata if copy else None