Compare model runs, and say what a comparison could have detected - #3
Merged
Conversation
…to see it Two runs come back with two numbers - ROC AUC 0.854 against 0.892 - and nothing in either says whether that is a difference between the models or a difference between the stations the survey happened to visit. Two functions, answering that and the question that should follow it. compare_runs() takes fitted runs and refits nothing. Every run already stores its held-out predictions with the station index and the fold, so the per-fold metrics are already there to be paired; all this adds is the comparison. The test is corrected_paired_test(), the same Nadeau-Bengio correction the covariate jackknife uses and for the same reason - two cross-validation training sets share most of their rows, so folds are not independent - but two-sided, since asking which of two models is better has no direction the way leaving a covariate out does. The column worth having is `detectable`: the smallest true difference the comparison would have found, at 80% power, given the fold-to-fold variance it actually saw. "Not significant" on its own conflates "these models perform alike" with "this survey could not tell them apart", and that column is what separates them. It is deliberately not observed power, which is a deterministic function of the p-value and tells a reader nothing new (Hoenig and Heisey 2001). Runs are paired on the station index rather than on row order, because tune returns folds in its own order and two runs need not agree on it. Runs that cover different stations - which is what different covariate sets produce, via different missingness - are compared on what they share, and warn with the count rather than intersecting silently. power_curve() answers the other question by refitting at several subsample sizes and tracing power against n. Everything is refitted at every size: a model trained on half the stations is a different model, not the same model evaluated on fewer, and a curve built the cheap way would measure the wrong thing. Both runs see the same subsample and the same folds at every point, which is what keeps the comparison paired the whole way down. It goes through the jackknife's fold_scores(), so it pays for fitting and scoring and for nothing else - no bootstrap intervals, no importance, no projection. Both were checked against a run deliberately starved of its ocean covariates, which compare_runs() puts 0.20 of AUC below the full model at p = 0.005, and whose power curve climbs from 0.43 at a quarter of the stations to 1.00 at all of them while the detectable difference falls from 0.43 to 0.13. Those are the directions the functions exist to produce, and they are asserted rather than eyeballed. Two limits are documented rather than hidden. The curve's target difference defaults to the one observed on the full data, so a gap that is itself mostly noise gives a curve about a size that may not be real. And these are differences in a bounded metric, so the normal-theory interval behind `detectable` degrades as AUC approaches 1. corrected_paired_test() gains an `alternative` argument, defaulting to the one-sided behaviour the jackknife already relied on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two functions for comparing model runs, per the scope agreed: the test and the power curve, pairing on shared stations.
compare_runs()— is the gap real?Refits nothing. Every run already stores its held-out predictions with the station index and the fold, so the per-fold metrics are there to be paired; this adds only the comparison.
The test is
corrected_paired_test()— the same Nadeau–Bengio correction the jackknife uses, for the same reason — but two-sided, since asking which of two models is better has no direction the way leaving a covariate out does.detectableis the column that earns its place. It is the smallest true difference the comparison would have found at 80% power, given the fold-to-fold variance it actually saw. "Not significant" on its own conflates these models perform alike with this survey could not tell them apart. It is deliberately not observed power, which is a deterministic function of the p-value and carries no new information (Hoenig & Heisey 2001).power_curve()— what would it take?Everything is refitted at every size — a model trained on half the stations is a different model, not the same one evaluated on fewer, and building the curve the cheap way would measure something else. Both runs see the same subsample and the same folds at every point, which keeps the comparison paired the whole way down. It reuses the jackknife's
fold_scores(), so it pays for fitting and scoring and nothing else.Pairing
On the station index, not row order —
tunereturns folds in its own order and two runs need not agree on it. Runs covering different stations (what different covariate sets produce, through different missingness) are compared on what they share and warn with the count, rather than intersecting silently. There is a test that shuffling one run's rows changes nothing.Verification
_R_CHECK_LIMIT_CORES_=TRUE— the variable that broke CI on the last PR.detectablefalls 0.43 → 0.13;minimum_detectable()andachieved_power()are checked to be inverses.tools/citations.csvand all five sections pass.Documented limits
Stated in the docs and the README rather than left for a reader to find: the curve's target difference defaults to the observed one, so a gap that is itself mostly noise yields a curve about a size that may not be real; and these are differences in a bounded metric, so the normal-theory interval behind
detectabledegrades as AUC approaches 1.corrected_paired_test()gains analternativeargument, defaulting to the one-sided behaviour the jackknife already relied on.🤖 Generated with Claude Code