Observed since 20 Oct, 17:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Sample-ratio, peeking, multiple-testing and winner's-curse checks for experiment readouts
Sample-ratio, peeking, multiple-testing and winner's-curse checks for experiment readouts.
An experiment readout can say "B wins, p = 0.03, ship it" while the traffic split was broken, three whale customers carried the whole effect, the analyst stopped at the first significant refresh, the winning metric was one of forty tried, or the experiment never had the power to estimate what it now claims. None of that shows up in the p-value. abkit reads the per-unit data and the intended design, measures each of these failure modes, and flags whatever leaves the range a healthy experiment could occupy.
Eight synthetic experiments, seven implanted defects. Bold values are flags; each lands on the row where its defect was implanted, and the clean row carries none.
| experiment | implanted defect | SRM p | contam. | fragility | look FPR | novelty gap | uncorrected | exagg. | flags |
|---|---|---|---|---|---|---|---|---|---|
| clean | nothing | 0.89 | 0 | 20 | - | -0.02 | - | 1.0 | none |
| traffic-leak | 3% of traffic diverted to treatment | 4.1e-14 | 0 | 20 | - | -0.02 | - | 1.0 | sample ratio mismatch |
| double-dipper | 25 units assigned to both arms | 0.67 | 25 | 20 | - | -0.03 | - | 1.0 | assignment contamination |
| whale-driven | 3 extreme units carry the significance | 0.11 | 0 | 1 | - | 0.26 | - | 1.2 | outlier fragility |
| peeker | null effect, stopped at the first significant look | 0.55 | 0 | 8 | 0.19 | -0.17 | - | 1.2 | peeking |
| fading-novelty | early-half effect 0.30, late-half effect 0.00 | 0.041 | 0 | 20 | - | 0.31 | - | 1.0 | novelty |
| metric-fisher | null effect, 40 secondary metrics tested | 0.054 | 0 | 0 | - | -0.11 | 2 | 3.4 | uncorrected winners |
| underpowered-winner | true effect 0.1, powered for 0.14 of that chance | 0.91 | 0 | 8 | - | -0.38 | - | 2.7 | winner's curse |
The row to sit with is peeker: a true null effect, analyzed after every
batch of 250 units and stopped the moment z crossed 1.96. Its final readout
is indistinguishable from a real win — p < 0.05, decent sample size — but the
schedule it followed has a 19% false-positive rate, not 5%. The audit flags
the process, which is the only place that defect lives.


make demo regenerates the table, the full report and
the figures from fixed seeds; CI rebuilds them from pinned dependencies and
fails if a committed number differs from what the code produces.
examples/upworthy/ runs abkit's aggregate checks over
the exploratory split of the Upworthy Research Archive
— 4,873 real headline A/B tests from 2013–2015. Committed results:
examples/upworthy/results/upworthy.md.
pip install abkit
Python 3.11+. Runtime dependencies: numpy, pydantic, matplotlib.
Log one JSON object per randomized unit, and the design the experiment was supposed to follow:
{"unit_id": "u1", "arm": "treatment", "metrics": {"revenue": 12.4, "conversion": 1.0}, "pre": {"revenue": 9.1}, "t": 1718040000}
{
"split": {"control": 0.5, "treatment": 0.5},
"control": "control",
"treatment": "treatment",
"primary_metric": "revenue",
"alpha": 0.05,
"mde": 0.5,
"looks": [{"n": 20000, "z": 1.31}, {"n": 40000, "z": 2.11}]
}
from abkit import load_units, load_design, run_audit
audit = run_audit(load_units("units.jsonl"), load_design("design.json"))
for flag in audit.flags:
print(flag.name, "--", flag.detail)
Or from the shell, with an exit code CI can gate on:
abkit report units.jsonl --design design.json --out audit --fail-on-flags
Alongside report.md the audit writes report.json for pipelines that gate
on specific numbers. Any check that could not run is listed with the exact
fields to log to enable it — a skip describes the log file, not the
experiment.
| check | question it answers | needs |
|---|---|---|
| sample ratio mismatch | did the split the readout assumes actually happen? | arms + intended split |
| assignment contamination | is any unit in more than one arm? | unit ids |
| covariate balance | were the groups equivalent before the treatment? | pre covariates |
| outlier fragility | how many extreme units does significance rest on? | per-unit primary metric |
| uncorrected winners | do the claimed wins survive multiple-testing control? | 2+ metrics |
| peeking | what false-positive rate did the stopping rule really have? | design.looks |
| novelty | is the single reported effect averaging over a changing one? | t per unit |
| winner's curse | how exaggerated is a significant estimate at this power? | design.mde (or observed) |
| variance ratio | does the treatment move the distribution, not just the mean? | per-unit primary metric |
| CUPED headroom | how much tighter could the CI have been? | pre covariates |
scipy.stats.ttest_ind, the two-proportion z matches statsmodels
proportions_ztest, and Benjamini–Hochberg reproduces statsmodels
multipletests decisions exactly. The library itself depends on none of
them.probes.py) and are easy to disagree with.design.mde the winner's-curse
probe reports power and exaggeration at the effect the experiment was
designed for; without it, the observed effect is used and labeled an
optimistic bound, since conditioning on significance inflates it.abkit.stats) apply.The failure modes measured here are the standard ones from the online experimentation literature: sample-ratio mismatch and its taxonomy (Fabijan et al., 2019), trustworthy experimentation practice (Kohavi, Tang & Xu, 2020), peeking and always-valid inference (Johari et al., 2017), variance reduction with pre-experiment data — CUPED (Deng et al., 2013), Type-M/Type-S errors (Gelman & Carlin, 2014), the winner's curse in A/B testing (Lee & Shen, 2018), and experimentation culture at scale (Kaufman, Pitchforth & Vermeer, 2017). The real-data case study uses the Upworthy Research Archive (Matias, Munger & Watts, 2021). abkit's contribution is packaging the checks as one auditable tool with uncertainty on every number and validation by implantation.
Companion projects: judgekit applies the same audit-before-you-trust pattern to LLM judges; raterkit applies it to the human labels underneath both; trajectory-judge measures what outcome-only judges miss on agent trajectories; arenakit applies it to pairwise model leaderboards.
Releases are archived on Zenodo. Cite the concept DOI 10.5281/zenodo.21809478, which always resolves to the latest version; structured metadata is in CITATION.cff.
@software{mohammadi_abkit,
author = {Mohammadi, Hadi},
title = {abkit: audit an A/B-test readout before you ship the decision},
url = {https://github.com/mohammadi-hadi/abkit},
doi = {10.5281/zenodo.21809478},
version = {0.1.0},
year = {2026}
}
MIT
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.1 | 7 Aug 2026 | 8 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 17:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:093b4f94038b6a1446bab88ee62cdab8af55b7530ed9f706c552bf10df1fae06