Repository Public
1"""Paired bootstrap confidence intervals over evaluation items (clustered by document to respect dependence)."""2from __future__ import annotations34import numpy as np567def bootstrap_ci(values: list[float], groups: list[str] | None = None, n: int = 2000, seed: int = 0, alpha: float = 0.05) -> dict[str, float]:8 v = np.asarray(values, dtype=float)9 rng = np.random.default_rng(seed)10 if groups is None:11 groups = list(range(len(v)))12 g = np.asarray(groups)13 uniq = np.unique(g)14 idx_by = {u: np.where(g == u)[0] for u in uniq}15 means = np.empty(n)16 for i in range(n):17 pick = rng.choice(uniq, size=len(uniq), replace=True)18 sel = np.concatenate([idx_by[u] for u in pick])19 means[i] = v[sel].mean()20 return {"mean": float(v.mean()), "ci_low": float(np.quantile(means, alpha / 2)), "ci_high": float(np.quantile(means, 1 - alpha / 2)), "n_items": int(len(v)), "n_groups": int(len(uniq))}212223def paired_bootstrap_diff(a: list[float], b: list[float], groups: list[str] | None = None, n: int = 2000, seed: int = 0) -> dict[str, float]:24 a, b = np.asarray(a, float), np.asarray(b, float)25 d = a - b26 r = bootstrap_ci(list(d), groups, n, seed)27 r["p_value_two_sided"] = float(2 * min((d.mean() <= 0) + 0.0, 1.0)) # placeholder replaced below28 rng = np.random.default_rng(seed)29 g = np.asarray(groups if groups is not None else list(range(len(d))))30 uniq = np.unique(g)31 idx_by = {u: np.where(g == u)[0] for u in uniq}32 cnt = 033 for _ in range(n):34 pick = rng.choice(uniq, size=len(uniq), replace=True)35 sel = np.concatenate([idx_by[u] for u in pick])36 if d[sel].mean() <= 0:37 cnt += 138 p = cnt / n39 r["p_value_two_sided"] = float(min(1.0, 2 * min(p, 1 - p)))40 return r41