Spaces:
Sleeping
Sleeping
| """Permutation null distribution via the fast baseline procedure. | |
| Shuffles the TRAIN labels (via the objective's ``permute``), re-runs the | |
| prefilter on the shuffled labels, fits the baseline (top-K) on | |
| shuffled-label TRAIN, and scores it against the TRUE TEST labels with | |
| the same objective. p = fraction of nulls whose score is at least the | |
| observed value — "could a separation this strong arise by chance?". | |
| The matrix is ranked once and reused for every permutation, so the loop | |
| is fast even at n=200 over ~20k features. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| import pandas as pd | |
| from engine.baseline import BASELINE_K | |
| from engine.objectives import BinaryAUROCObjective, Objective | |
| from engine.prefilter import precompute_ranks | |
| def permutation_null( | |
| M_train: pd.DataFrame, | |
| y_train: np.ndarray, | |
| M_test: pd.DataFrame, | |
| y_test: np.ndarray, | |
| *, | |
| objective: Objective | None = None, | |
| n_permutations: int = 200, | |
| seed: int = 0, | |
| k: int = BASELINE_K, | |
| ) -> list[float]: | |
| obj = objective or BinaryAUROCObjective() | |
| rng = np.random.default_rng(seed) | |
| ranks = precompute_ranks(M_train) | |
| cols = list(M_train.columns) | |
| null_scores: list[float] = [] | |
| for _ in range(n_permutations): | |
| y_shuf = obj.permute(y_train, rng) | |
| per_feature = obj.prefilter_score_per_feature(ranks, y_shuf) | |
| scores = pd.Series(per_feature, index=cols) | |
| top_k = scores.nlargest(k).index.tolist() | |
| states_tr = M_train[top_k].mean(axis=1).values.reshape(-1, 1) | |
| states_te = M_test[top_k].mean(axis=1).values.reshape(-1, 1) | |
| null_scores.append( | |
| float(obj.holdout_score(states_tr, y_shuf, states_te, y_test)) | |
| ) | |
| return null_scores | |
| def permutation_p_value(observed: float, null_dist: list[float]) -> float: | |
| """Fraction of nulls >= observed, with a +1 / +1 add-one correction.""" | |
| null_arr = np.asarray(null_dist) | |
| return float(((null_arr >= observed).sum() + 1) / (len(null_arr) + 1)) | |