Source code for athena.neighborhood.estimators

# %%
import pandas as pd
import numpy as np

from .base_estimators import Interactions, _infiltration, RipleysK

from .utils import get_node_interactions
from ..utils.general import is_categorical
from tqdm import tqdm
# %%
[docs]def interactions(so, spl: str, attr: str, mode: str ='classic', prediction_type: str ='observation', *, n_permutations: int =100, random_seed=None, alpha: float =.01, try_load: bool =True, key_added: str =None, graph_key: str ='knn', inplace: bool =True) -> None: """Compute interaction strength between species. This is done by counting the number of interactions (edges in the graph) between pair-wise observation types as encdoded by `attr`. See notes for more information or the `methodology <https://ai4scr.github.io/ATHENA/source/methodology.html>`_ section in the docs. Args: so: SpatialOmics instance spl: Spl for which to compute the metric attr: Categorical feature in SpatialOmics.obs to use for the grouping mode: One of {classic, histoCAT, proportion}, see notes n_permutations: Number of permutations to compute p-values and the interactions strength score (mode diff) random_seed: Random seed for permutations alpha: Threshold for significance prediction_type: One of {observation, pvalue, diff}, see Notes try_load: load pre-computed permutation results if available key_added: Key added to SpatialOmics.uns[spl][metric][key_added] graph_key: Specifies the graph representation to use in so.G[spl] if `local=True`. inplace: Whether to add the metric to the current SpatialOmics instance or to return a new one. Notes: `classic` and `histoCAT` are python implementations of the corresponding methods pubished by the Bodenmiller lab at UZH. The `proportion` method is similar to the `classic` method but normalises the score by the number of edges and is thus bound [0,1]. Returns: """ so = so if inplace else so.copy() # NOTE: uns_path = f'{spl}/interactions/' if key_added is None: key_added = f'{attr}_{mode}_{prediction_type}_{graph_key}' if random_seed is None: random_seed = so.random_seed estimator = Interactions(so=so, spl=spl, attr=attr, mode=mode, n_permutations=n_permutations, random_seed=random_seed, alpha=alpha, graph_key=graph_key) estimator.fit(prediction_type=prediction_type, try_load=try_load) res = estimator.predict() # add result to uns attribute add2uns(so, res, spl, 'interactions', key_added) if not inplace: return so
[docs]def infiltration(so, spl: str, attr: str, *, interaction1=('tumor', 'immune'), interaction2=('immune', 'immune'), add_key='infiltration', inplace=True, graph_key='knn', local=False) -> None: """Compute infiltration score. Generalises the infiltration score presented in `A Structured Tumor-Immune Microenvironment in Triple Negative Breast Cancer Revealed by Multiplexed Ion Beam Imaging <https://pubmed.ncbi.nlm.nih.gov/30193111/>`_ The score comptes a ratio between the number of interactions observed between the observation types specified in `interactions1` and `interaction2` as :math:`\\frac{\\texttt{number of interactions 1}}{\\texttt{number of interactions 2}}`. This ratio can be undefined. See notes for more information. Args: so: SpatialOmics instance spl: Spl for which to compute the metric attr: Categorical feature in SpatialOmics.obs to use for the grouping interaction1: labels in `attr` of enumerator interaction interaction2: labels in `attr` of denominator interaction key_added: Key added to SpatialOmics.uns[spl][metric][key_added] inplace: Whether to add the metric to the current SpatialOmics instance or to return a new one. graph_key: Specifies the graph representation to use in so.G[spl] if `local=True`. Returns: Notes: The default arguments are replicating the `immune infiltration score <infiltrationScore_>`_. However, you can compute any kind of "infiltration" between observation types. The `attr` argument specifies the column in the `obs` dataframe which encodes different observation types. `interaction{1,2}` argument defines between which types the score should be computed. .. _infiltrationScore: https://pubmed.ncbi.nlm.nih.gov/30193111/ """ so = so if inplace else so.copy() data = so.obs[spl][attr] if isinstance(data, pd.DataFrame): raise ValueError(f'multidimensional attr ({data.shape}) is not supported.') if not is_categorical(data): raise TypeError('`attr` needs to be categorical') if not np.in1d(np.array(interaction1 + interaction2), data.unique()).all(): mask = np.in1d(np.array(interaction1 + interaction2), data.unique()) missing = np.array(interaction1 + interaction2)[~mask] raise ValueError(f'specified interaction categories are not all in `attr`. Missing {missing}') G = so.G[spl][graph_key] if local: cont = [] for node in tqdm(G.nodes): neigh = G[node] g = G.subgraph(neigh) nint = get_node_interactions(g, data) res = _infiltration(node_interactions=nint, interaction1=interaction1, interaction2=interaction2) cont.append(res) res = pd.DataFrame(cont, index=G.nodes, columns=[add_key]) if add_key in so.obs[spl]: so.obs[spl] = so.obs[spl].drop(columns=[add_key]) so.obs[spl] = pd.concat((so.obs[spl], res), axis=1) else: nint = get_node_interactions(G, data) res = _infiltration(node_interactions=nint, interaction1=interaction1, interaction2=interaction2) so.spl.loc[spl, add_key] = res if not inplace: return so
[docs]def ripleysK(so, spl: str, attr: str, id, *, mode='K', radii=None, correction='ripley', inplace=True, key_added=None): """Compute Ripley's K as implemented by `[1]`_. Args: so: SpatialOmics instance spl: Spl for which to compute the metric attr: Categorical feature in SpatialOmics.obs to use for the grouping id: The category in the categorical feature `attr`, for which Ripley's K should be computed mode: {K, csr-deviation}. If `K`, Ripley's K is estimated, with `csr-deviation` the deviation from a poission process is computed. radii: List of radiis for which Ripley's K is computed correction: Correction method to use to correct for boarder effects, see [1]. inplace: Whether to add the metric to the current SpatialOmics instance or to return a new one. key_added: Key added to SpatialOmics.uns[spl][metric][key_added] Returns: Ripley's K estimates References: .. _[1]: https://docs.astropy.org/en/stable/stats/ripley.html """ so = so if inplace else so.copy() # NOTE: uns_path = f'{spl}/clustering/' if key_added is None: key_added = f'{id}_{attr}_{mode}_{correction}' estimator = RipleysK(so=so, spl=spl, id=id, attr=attr) res = estimator.predict(radii=radii, correction=correction, mode=mode) # add result to uns attribute add2uns(so, res, spl, 'ripleysK', key_added) if not inplace: return so
[docs]def add2uns(so, res, spl: str, parent_key, key_added): if spl in so.uns: if parent_key in so.uns[spl]: so.uns[spl][parent_key][key_added] = res else: so.uns[spl].update({parent_key: {key_added: res}}) else: so.uns.update({spl: {parent_key: {key_added: res}}})