ScooBDoob / scoobdoob /validation /preference.py
ynuozhang
Upload ScooBDoob code without binary caches
31d1d63
Raw History Blame Contribute Delete
3.69 kB
import json
from collections import defaultdict
from pathlib import Path
import numpy as np
from scipy.stats import spearmanr
RS = Path(__file__).resolve().parents[2] / "data" / "p53" / "rmsd_screen"
COVER = 0.9
N_SPLITS = 400
rng = np.random.default_rng(0)
def load_matrix():
manifest = json.loads((RS / "receptors.json").read_text())
recs = {(r["state"], r["frame"]): r for r in manifest}
per = defaultdict(dict)
for f in sorted((RS / "scores").glob("*.json")):
d = json.loads(f.read_text())
for s in d["scores"]:
if s.get("affinity") is None:
continue
r = recs.get((s.get("state"), s.get("frame")))
if r:
per[d["ligand"]][r["frame"]] = float(s["affinity"])
ligs_all = sorted(per)
all_frames = sorted({r["frame"] for r in manifest})
cov = {i: sum(i in per[l] for l in ligs_all) / len(ligs_all) for i in all_frames}
frames = [i for i in all_frames if cov[i] >= COVER]
lig_cov = {l: sum(i in per[l] for i in frames) / len(frames) for l in ligs_all}
ligs = [l for l in ligs_all if lig_cov[l] >= COVER]
rec_by_frame = {r["frame"]: r for r in manifest}
vol = np.array([rec_by_frame[i]["site_volume"] for i in frames], dtype=float)
S = np.full((len(ligs), len(frames)), np.nan)
for li, l in enumerate(ligs):
for ri, i in enumerate(frames):
if i in per[l]:
S[li, ri] = per[l][i]
row_mean = np.nanmean(S, axis=1, keepdims=True)
S = np.where(np.isnan(S), row_mean, S)
return S, ligs, frames, vol, rec_by_frame, manifest
def volume_trend(Z, vol, nbins=10):
"""Return the pooled volume-decile trend for each receptor."""
edges = np.quantile(vol, np.linspace(0, 1, nbins + 1))
edges[-1] += 1e-9
bin_id = np.clip(np.searchsorted(edges, vol, side="right") - 1, 0, nbins - 1)
g = np.array(
[Z[:, bin_id == b].mean() if (bin_id == b).any() else 0.0 for b in range(nbins)]
)
return g[bin_id]
def gamma_matrix():
"""Exactly the manuscript's gamma: S -> z-score by ligand -> remove f(V_r) -> remove beta_r."""
S, ligs, frames, vol, rec_by_frame, manifest = load_matrix()
Z = (S - S.mean(axis=1, keepdims=True)) / S.std(axis=1, keepdims=True)
G = volume_trend(Z, vol)
R = Z - G[None, :]
gamma = R - R.mean(axis=0)[None, :]
strat = np.array([rec_by_frame[i]["state"] for i in frames])
return gamma, np.array(ligs), np.array(frames), strat
def pref_corr(mat):
"""Ligand-preference profile = ligand x ligand correlation over the given receptors."""
return np.corrcoef(mat)
def split_half_within(gamma, strat, n_splits=N_SPLITS, shuffle_strata=False):
"""Split receptors into halves INSIDE each stratum, then compare ligand-preference
structure between the halves. Between-stratum contrasts cannot drive this."""
n_lig = gamma.shape[0]
iu = np.triu_indices(n_lig, k=1)
out = []
for _ in range(n_splits):
s = strat.copy()
if shuffle_strata:
s = rng.permutation(s)
A, B = [], []
for lab in np.unique(s):
idx = np.where(s == lab)[0]
perm = rng.permutation(idx)
A.extend(perm[: len(idx) // 2])
B.extend(perm[len(idx) // 2 :])
A, B = np.array(A), np.array(B)
if len(A) < 3 or len(B) < 3:
continue
gc = gamma.copy()
for lab in np.unique(s):
m = s == lab
gc[:, m] -= gc[:, m].mean(axis=1, keepdims=True)
r_, _ = spearmanr(pref_corr(gc[:, A])[iu], pref_corr(gc[:, B])[iu])
if np.isfinite(r_):
out.append(r_)
return np.array(out)