resumematch-api / stats /multiple_comparisons.py
ayushgupta7777's picture
Deploy ResumeMatch Lab API (engine + 9,014-job corpus)
3a41bf2
Raw
History Blame Contribute Delete
2.47 kB
"""Per-cluster paired analysis with Bonferroni and Benjamini-Hochberg correction.
Running a paired test within each of the 8 clusters produces 8 p-values; without
correction the family-wise error rate inflates. We report both the conservative
Bonferroni adjustment and the less conservative BH FDR control.
"""
from __future__ import annotations
import numpy as np
import pandas as pd
from statsmodels.stats.multitest import multipletests
from stats.frequentist import paired_t_test
def per_cluster_analysis(
deltas: np.ndarray,
cluster_ids: np.ndarray,
cluster_names: dict[int, str],
alpha: float = 0.05,
min_n: int = 3,
) -> pd.DataFrame:
"""One row per cluster: mean delta, raw + corrected p-values, CI, winner."""
deltas = np.asarray(deltas, dtype=float)
records: list[dict] = []
for cid in sorted(cluster_names):
sub = deltas[cluster_ids == cid]
rec: dict = {"cluster_id": cid, "label": cluster_names[cid], "n": int(len(sub))}
if len(sub) >= min_n and sub.std(ddof=1) > 0:
t = paired_t_test(sub)
rec.update(
mean_delta=t.detail["mean_delta"],
t_stat=t.statistic,
p_raw=t.pvalue,
ci_low=t.ci_low,
ci_high=t.ci_high,
cohens_d=t.effect_size,
)
else:
rec.update(
mean_delta=float(np.mean(sub)) if len(sub) else float("nan"),
t_stat=float("nan"),
p_raw=float("nan"),
ci_low=float("nan"),
ci_high=float("nan"),
cohens_d=float("nan"),
)
records.append(rec)
df = pd.DataFrame(records)
valid = df["p_raw"].notna()
df["p_bonferroni"] = np.nan
df["p_bh_fdr"] = np.nan
pv = df.loc[valid, "p_raw"].to_numpy()
if len(pv):
df.loc[valid, "p_bonferroni"] = multipletests(pv, alpha=alpha, method="bonferroni")[1]
df.loc[valid, "p_bh_fdr"] = multipletests(pv, alpha=alpha, method="fdr_bh")[1]
df["sig_bonferroni"] = df["p_bonferroni"] < alpha
df["sig_bh"] = df["p_bh_fdr"] < alpha
def _winner(r: pd.Series) -> str:
if pd.isna(r["p_bh_fdr"]):
return "n/a"
if r["p_bh_fdr"] < alpha:
return "B" if r["mean_delta"] > 0 else "A"
return "tie"
df["winner"] = df.apply(_winner, axis=1)
return df.sort_values("cluster_id").reset_index(drop=True)