File size: 3,725 Bytes
e8edb9d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """Cross-dataset consistency analysis."""
from __future__ import annotations
import numpy as np
import pandas as pd
from anndata import AnnData
from scipy import stats
from .._constants import GAMMA
from .._utils import get_layer, require_layers
def cross_dataset_consistency(
adatas_dict: dict[str, AnnData],
case_insensitive: bool = True,
) -> pd.DataFrame:
"""Compare per-gene median gamma across multiple datasets.
For each pair of datasets, computes Spearman and Pearson correlations
of per-gene median gamma on their shared gene set.
Parameters
----------
adatas_dict
Dictionary mapping dataset name to analyzed AnnData
(each must have ``gamma`` layer).
case_insensitive
Match gene symbols case-insensitively (default True). Useful
for cross-species comparisons (mouse Titlecase vs human UPPER).
Returns
-------
DataFrame with columns: ``dataset_a``, ``dataset_b``, ``n_shared_genes``,
``spearman_r``, ``pearson_r``.
"""
names = sorted(adatas_dict.keys())
# Pre-compute median gamma for each dataset
medians = {}
for name in names:
adata = adatas_dict[name]
require_layers(adata, GAMMA)
gamma = get_layer(adata, GAMMA)
medians[name] = pd.Series(
np.median(gamma, axis=0), index=adata.var_names
)
records = []
for i, name_a in enumerate(names):
for name_b in names[i + 1:]:
if case_insensitive:
# Build uppercase-to-original mapping for both
map_a = {}
for g in medians[name_a].index:
if isinstance(g, str):
map_a[g.upper()] = g
map_b = {}
for g in medians[name_b].index:
if isinstance(g, str):
map_b[g.upper()] = g
shared_upper = set(map_a.keys()) & set(map_b.keys())
n_shared = len(shared_upper)
if n_shared < 3:
sp_r = pe_r = np.nan
else:
ga = np.array([medians[name_a][map_a[u]] for u in shared_upper], dtype=float)
gb = np.array([medians[name_b][map_b[u]] for u in shared_upper], dtype=float)
valid = np.isfinite(ga) & np.isfinite(gb)
ga, gb = ga[valid], gb[valid]
n_shared = len(ga)
if len(ga) < 3:
sp_r = pe_r = np.nan
else:
sp_r, _ = stats.spearmanr(ga, gb)
pe_r, _ = stats.pearsonr(ga, gb)
else:
shared = medians[name_a].index.intersection(medians[name_b].index)
n_shared = len(shared)
if n_shared < 3:
sp_r = pe_r = np.nan
else:
ga = medians[name_a][shared].values.astype(float)
gb = medians[name_b][shared].values.astype(float)
valid = np.isfinite(ga) & np.isfinite(gb)
ga, gb = ga[valid], gb[valid]
if len(ga) < 3:
sp_r = pe_r = np.nan
else:
sp_r, _ = stats.spearmanr(ga, gb)
pe_r, _ = stats.pearsonr(ga, gb)
records.append({
"dataset_a": name_a,
"dataset_b": name_b,
"n_shared_genes": n_shared,
"spearman_r": float(sp_r) if not np.isnan(sp_r) else np.nan,
"pearson_r": float(pe_r) if not np.isnan(pe_r) else np.nan,
})
return pd.DataFrame(records)
|