SabaPivot's picture
download
raw
3.63 kB
"""Claim 5 addendum: is the 5-permutation derandomised Wilcoxon still VALID?
The n-sweep in c5b_derandomization.py reproduces the paper's power gain from
derandomisation, but the type-I error rises with the number of permutations.
Theorem 3.3 covers Algorithm 1/2, which draws ONE pair of permutations; the
Rao-Blackwellised variant of Section 5 has no accompanying theory.
This script isolates the question under a clean GLOBAL NULL (y drawn completely
independently of X, so EVERY coordinate is null) with the ORACLE nu_j = rho_j,
i.e. the exact hypothesis of Theorem 3.3. If the type-I error still inflates
with the number of permutations there, the inflation is a property of the
Rao-Blackwellisation itself, not of the masked-correlation design.
"""
from __future__ import annotations
import json
import os
import sys
import time
import numpy as np
from joblib import Parallel, delayed
from scipy import stats
from sklearn.ensemble import GradientBoostingRegressor
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__))))
from semiknockoffs import ( # noqa: E402
ar1_cov, fit_nu_rho, gaussian_nu, sko_pvalue, sq_loss,
)
OUT = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "outputs"
)
SEED0 = 20260725
PERMS = [1, 2, 5, 10, 20]
def _rep(rep, n, p=30, oracle=True):
rng = np.random.default_rng(
SEED0 + 71_000_000 + 733 * rep + 3 * n + (0 if oracle else 5)
)
Sigma = ar1_cov(p, 0.6)
X = rng.standard_normal((n, p)) @ np.linalg.cholesky(Sigma).T
y = rng.standard_normal(n) # GLOBAL NULL: y independent of X
m = GradientBoostingRegressor(random_state=rep).fit(X, y)
out = {k: [] for k in PERMS}
for j in range(min(p, 10)):
if oracle:
nu = gaussian_nu(X, j, Sigma) # under the global null rho = nu
rho = nu.copy()
else:
nu, rho = fit_nu_rho(X, y, j, alpha=1.0)
for k in PERMS:
out[k].append(
sko_pvalue(X, y, j, nu, rho, m.predict, sq_loss, rng, n_perm=k)
)
return out
def run(n, reps=200, n_jobs=100, oracle=True):
t0 = time.time()
got = Parallel(n_jobs=n_jobs)(
delayed(_rep)(r, n, oracle=oracle) for r in range(reps)
)
res = {"n": n, "p": 30, "replicates": reps, "oracle": oracle,
"seconds": round(time.time() - t0, 1)}
for k in PERMS:
pv = np.concatenate([g[k] for g in got])
ks = stats.kstest(pv, "uniform")
res[f"perm{k}"] = {
"n_pvalues": int(len(pv)),
"type_I_at_0.05": float(np.mean(pv <= 0.05)),
"type_I_at_0.10": float(np.mean(pv <= 0.10)),
"mean_pvalue": float(np.mean(pv)),
"ks_pvalue": float(ks.pvalue),
}
print(
f"[global-null {'oracle' if oracle else 'estim'} n={n}] perms={k:<3} "
f"typeI@0.05={res[f'perm{k}']['type_I_at_0.05']:.4f} "
f"typeI@0.10={res[f'perm{k}']['type_I_at_0.10']:.4f} "
f"KS p={ks.pvalue:.2e}",
flush=True,
)
return res
if __name__ == "__main__":
os.makedirs(OUT, exist_ok=True)
res = {
"seed0": SEED0,
"permutation_grid": PERMS,
"design": "global null, oracle nu=rho, GB black box",
}
for n in (100, 300):
res[f"oracle_n{n}"] = run(n, oracle=True)
for n in (100, 300, 1000):
res[f"estimated_n{n}"] = run(n, oracle=False)
with open(os.path.join(OUT, "claim5c_derand_validity.json"), "w") as f:
json.dump(res, f, indent=2)
print("wrote", os.path.join(OUT, "claim5c_derand_validity.json"))

Xet Storage Details

Size:
3.63 kB
·
Xet hash:
8033f20f158091f0d8c408686473b025c7927d576a9cd929ff4813acee2ce31f

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.