mixle.evolve.verify module¶
The champion/challenger gate: “did the challenger SIGNIFICANTLY beat the champion, no regression?”
This is the single decision rule the whole loop turns on, and the anti-regression guarantee lives here. It is pure glue over existing statistics – it owns the policy (alpha, the effect-size floor, the calibration no-regression rule, multiplicity) and never edits the underlying tests:
Paired significance –
mixle.inference.model_comparison.paired_score_difference()on the objective’s per-observation vectors, scored from the same held-out batch in the same order.Pairing integrity – both vectors are required to have equal length (they are produced from one
dataargument, so the order cannot diverge); a mismatch is a hard error, not a silent skip.Effect-size floor –
|mean_diff| >= min_effect; a statistically significant but practically negligible win does not promote.Non-nested cross-check – for family swaps (
nonnested=True) the challenger must additionally winvuong_test()andclarke_test()(BIC-corrected) on the pointwise log-likelihoods.ELPD band – when LOO/WAIC pointwise arrays are supplied,
compare_elpd()’s 2-SE band is the conservative tie rule.Calibration no-regression – a more-accurate-but-less-calibrated challenger is refused: its calibration scalar must not exceed the champion’s by
calib_tol.Verdict. calibration_statusis'unavailable'only for an explicit, checked applicability decision – e.g. the calibration objective is PIT-based and needs a continuous predictive distribution, so it does not apply to a categorical/discrete model – since that is not itself evidence of a regression. An unexpected failure inside the computation itself (applicability was confirmed, yet it still raised) is'error', not'unavailable': an implementation defect must not silently read as calibrated, so'error'blocks promotion the same as'failed'– see_calibration_no_regression()andVerdict.calibrated.Multiplicity – comparing one champion/challenger pair produces exactly one p-value, so this function cannot correct it for multiplicity by itself: every method in
mixle.inference.multiple_testingis the identity transform at family size 1 (bonferroni multiplies alpha by 1; BH ranks the one p-value against itself). A caller running many simultaneous challengers at once (e.g. one population generation) must pool the RAW p-values from every comparison and adjust them together, ONCE, viamixle.inference.multiple_testing.adjust_pvalues(), then compare each candidate’s own adjusted p-value toalphaitself – seemixle.evolve.population.Population.step(), which does exactly this. That pool must first drop anyVerdict.p_valuethat isnan– see the scalar-only note below –adjust_pvalues()rejects non-finite input outright rather than silently mishandling it, so an un-filtered pool crashes instead of under-correcting.Scalar-only objectives – an objective whose
pointwisereturnsNone(e.g.calibration_objective(),decision_regret_objective()) has no per-observation vector to pair, so no paired test can run:Verdict.favoredis still decided from a bare scalar-delta-vs-min_effectcomparison (reported for a human to review), butVerdict. p_value/ciare set tonanas an explicit “not applicable” sentinel (there being no null hypothesis test to report a p-value or CI for) – not a failure to compute one. Downstream code that pools p-values across many verdicts (point 7 above) must treat thisnanas “exclude from the pool”, not “coerce to 0 (very significant)” or “coerce to 1 (never significant)”: both would misrepresent a comparison that was never run as one that was. A bare scalar delta alone carries no sampling-uncertainty estimate – no replication, bootstrap, or resampling evidence backs it – so it cannot support the same evidence-bearing promotion guarantee a paired p-value gives:Verdict.promoteisFalsefor every scalar-only verdict regardless offavored(seeVerdict.has_statistical_evidence). A scalar objective that gains its own documented resampling/replication contract (e.g. multiple independent fit replicates, or a bootstrap over the held-out data) would be a legitimate way to earn auto-promotion eligibility in the future; none does today, so a scalar-only win is flagged in evidence for a human to act on, not auto-promoted.
- class Verdict(favored, delta, p_value, ci, calibration_status, evidence=<factory>)[source]
Bases:
objectThe outcome of a single champion/challenger comparison.
- Parameters:
- property calibrated: bool
True unless calibration regressed, or its computation broke unexpectedly.
Kept for callers that only need the promotion-relevant boolean;
calibration_statuscarries the more precise state a bare bool cannot: ‘unavailable’ (calibration wasn’t requested, or is genuinely inapplicable to this model/data – an explicit, checked applicability decision, not itself evidence of a regression, so it does not block promotion, the same as ‘passed’) is distinct from both ‘failed’ (calibration was computed for both models and the challenger’s is worse by more thancalib_tol) and ‘error’ (the computation raised unexpectedly despite looking applicable – an implementation defect, not a legitimate skip). ‘failed’ and ‘error’ are the only statuses that block promotion; an ‘error’ must not silently read the same as a pass.
- property has_statistical_evidence: bool
True unless this verdict came from a scalar-only objective with no paired test to run.
A scalar-only objective (module docstring point 8) has no per-observation vector to pair, so
p_value/ciare the explicitnan“not applicable” sentinel: there is no sampling-uncertainty estimate – no p-value, CI, replication, or bootstrap – behind the raw scalar delta at all. Automatic promotion requires this to be True; a scalar-only win is still reported (favored/delta/evidence['scalar_only']) for a human to review, but it can never auto-promote on its own – seepromote.
- property promote: bool
True iff the challenger is favored, calibration didn’t fail or error, and the verdict is backed by an actual statistical test (see
has_statistical_evidence) – the single promotion predicate.
- challenger_beats_champion(champion, challenger, data, *, objective, alpha=0.05, min_effect=0.0, require_calibration=True, nonnested=False, multiplicity=None, calib_tol=1.0e-3, seed=0, elpd_pointwise=None)[source]
Decide whether
challengersignificantly and non-regressively beatschampionondata.- Parameters:
champion (Any) – two fitted models scored on the same held-out
data.challenger (Any) – two fitted models scored on the same held-out
data.data (Any) – the held-out responses (one batch -> both models scored in the same order).
objective (Objective) – the
Objectiveto compare on.alpha (float) – significance level for the paired test (and the CI is at
1 - alpha).min_effect (float) – practical effect-size floor on
|mean score difference|.require_calibration (bool) – if True, run the calibration no-regression check.
nonnested (bool) – if True (a family swap), additionally require Vuong + Clarke to favor the challenger.
multiplicity (str | None) – must stay
None. A single champion/challenger comparison produces exactly one p-value, so it can never be corrected for multiplicity in isolation – passing a method name here raises rather than silently no-op’ing (everymixle.inference.multiple_testingmethod is the identity transform at family size 1). Kept as a parameter only to fail loudly on the mistake instead of TypeError’ing on removal; pool the raw p-values from every simultaneous comparison and callmixle.inference.multiple_testing.adjust_pvalues()once yourself, then compare each adjusted p-value toalpha.calib_tol (float) – tolerance on the calibration-error increase the challenger may carry.
seed (int) – RNG seed for the (sampled) calibration scalars.
elpd_pointwise (tuple[ndarray, ndarray] | None) – optional
(pointwise_champion, pointwise_challenger)LOO/WAIC arrays; when given, thecompare_elpd()2-SE band is required to also favor the challenger.
- Returns:
A
Verdict.verdict.promoteis the single promotion predicate; it is alwaysFalsefor a scalar-only objective, regardless offavored– see module docstring point 8 andVerdict.has_statistical_evidence.- Raises:
ValueError – if a policy value is outside its domain (see
_check_policy()), or if a pointwise vector does not carry exactly one score per held-out row (see_check_pointwise()).- Return type:
Verdict