A calibration claim has a floor

A perfectly calibrated model does not score an Expected Calibration Error of zero. On a finite sample it scores something above zero, and on a few hundred rows that something is often larger than the differences people report. Below a certain row count, a calibration claim is not measurable at all. This page lets you check where your own numbers sit, without installing anything.

The example report, 105 rows

Measured ECE 0.0740

Calibrated-model floor, 95th percentile 0.1109

measured 0.0740floor, 95th percentile 0.1109

INCONCLUSIVE The measured figure sits inside what a perfectly calibrated model scores on 105 rows, so this dataset establishes nothing either way. See it derived from the rows.

Check your numbers Read the methodology

The argument

ECE sorts predictions into bins by the probability the model gave, and in each bin compares that probability with how often the model was actually right. It averages the gaps. Zero would be perfect.

Zero is not reachable. Each bin holds a handful of cases, and a handful of outcomes does not land exactly on its own probability, any more than ten coin flips land on exactly five heads. The gaps are absolute values, so that scatter does not cancel out: it adds up. The result is a floor under ECE that has nothing to do with the model and everything to do with the row count.

So a measured ECE means nothing until you know what a perfectly calibrated model would have scored on the same number of rows. plumbline computes that floor by simulation and reads every calibration figure against it. When the figure sits inside the floor, the verdict is INCONCLUSIVE, and that is not a pass. It means the dataset cannot tell your model apart from a perfect one, so nothing was established in either direction.

Floor calculator

Enter a configuration to see the calibrated-model floor, and optionally a measured ECE to see whether it clears it.

Worked example: the report's ECE line

The repository's example report prints this line:

Loading the example...

Below, this page derives that line from the 105 rows behind it, rather than quoting it.

Your own predictions

Paste the predictions a model made and whether each one was right, and this computes their ECE, the floor for exactly those predictions, and the verdict a plumbline report would print, word for word.

Nothing you paste leaves this page. It is read and scored by the script on this page, in your browser. It is not uploaded, not stored, and not put in the address, so reloading the page clears it.

Sample-size planner

Planning estimate

The question to ask before collecting labels: to detect an ECE of a given size, how many rows do you need? This searches over the row count for the point where a measured ECE of that size clears the calibrated-model floor's 95th percentile.

Read the answer as a planning estimate, for two reasons. It is built on the summary floor, which invents the spread of predictions from the accuracy; as the worked example shows, the floor from your real predictions can land either side of it. And it is the row count at which a measured value of that size would clear the floor, not the count at which a model miscalibrated by that much is reliably caught. For that, plan for more.

What computes these numbers

Everything on this page is computed in your browser by a JavaScript reimplementation of plumbline's floor, not by the Python itself. It reproduces numpy's seeded random stream draw for draw (the PCG64 generator and numpy's own normal, gamma, and beta samplers), so with the seeds the Python uses it redraws exactly the outcomes the Python draws. It is checked against values exported from the Python on every change, in CI, and the site does not deploy if they disagree by more than 1e-9. That tolerance is far above the rounding differences left once the random streams agree (around 1e-15) and far below what one mismatched draw would cause (around 1e-3).