S3 · Rogan-Gladen correction¶
- Author: Krishna Nandimandalam
- Date: 2026-10-09
- Card: #7 · PR: #TBD
What I built¶
In stats/correction.py:
- rogan_gladen(q, sensitivity, specificity) -> float: (q + c − 1) / (s + c − 1), clipped to [0, 1]
- corrected_pass_rate(judge_test, judge_cal, human_cal, *, rng, n_boot=2000, confidence=0.95, test_clusters=None) -> Estimate
- MIN_YOUDEN = 0.1, and JudgeTooWeakError in core/errors.py
The idea, simply¶
If a judge passes 95% of good answers but also 40% of bad ones, its raw pass rate is inflated.
The measured rate is q = s·θ + (1−c)(1−θ), so we solve for the true rate θ. The denominator
s + c − 1 (Youden's J) says how much better than a coin flip the judge is; when it is close to
zero the correction blows up, so we refuse instead of reporting a number.
The error bar has two sources, the calibration set (which gives s and c) and the test set (which gives q), so each is resampled independently.
How I know it's right¶
rogan_gladen(0.82, 0.95, 0.60)is 0.42 / 0.55 ≈ 0.7636.- Clipping, a perfect judge (θ = q) and the refusal at J ≤ 0.1 are tested.
- A large simulated world recovers the true rate inside its interval.
- A
slowbenchmark checks 2,000 resamples on 100k test items run in under 50 ms. - Not run yet: tests are written but the maintainer runs them.
What surprised me / gotchas¶
- Bootstrapping counts (
rng.multinomial,rng.binomial) is vectorised, which is what makes the 50 ms target realistic; a Python loop over resamples is far slower. - Resamples where a class is missing or J ≤ 0 are dropped; more than 1% dropped means the interval would be unreliable, so we raise.
test_clustersresamples whole clusters of the test set (shared helper instats/clustered.py).
How AI helped (and where it was wrong)¶
To be filled in by the author.
Learn more¶
- Rogan & Gladen (1978), Estimating prevalence from the results of a screening test.
- How to Correctly Report LLM-as-a-Judge Evaluations (arXiv 2511.21140).
- Architecture §13.3.