What it does
Given calibration forecasts and truths, it scores each one — by default with the absolute residuals = |y - ŷ| — and takes the exact
conformal quantile of those scores:
[ŷ - q, ŷ + q]. The n + 1 is not a rounding detail:
it is what makes the coverage guarantee finite-sample rather than
asymptotic. With n = 24 windows and alpha = 0.1, k = 23, so q is
the 23rd smallest of the 24 calibration scores.
One q is stored per (series, horizon) cell, so the band can widen
with the horizon and differ across a panel even though the score
function is shared.
Worked example
calibrate_cv returned a new fitted object and left method untouched;
its score_quantile_ is that q, one per (series, horizon) cell.
Plotting it against the horizon shows the band widening as the ETS
forecast has more room to drift.


Measured coverage
Asymmetric intervals
Everything above builds an equal-width band centred on the forecast, because the absolute residual|y - ŷ| throws away the sign of the
error. It targets total coverage 1 - alpha and says nothing about how
the misses split between the tails: with skewed residuals one tail can
take nearly all of them. And the symmetric band has a cost: a forecaster
that is biased — consistently high or consistently low — can only be
covered by widening the band on both sides, including the side the
errors never fall on.
symmetric=False scores the signed residual r = y - ŷ instead and
takes one one-sided conformal quantile per tail:
n + 1 correction as before, and the interval becomes
[ŷ + lower_offset, ŷ + upper_offset]. Both offsets are stored per
(series, horizon) cell, so score_quantile_ gains a trailing axis of
size 2. Nothing constrains them to be mirror images, so the band can
shift as well as widen.
The two levels are bound by alpha_lo + (1 - alpha_hi) == alpha: the
tails move against each other on a fixed budget, and both are mandatory
once symmetric=False. Setting them to alpha/2 each — 0.05 and
0.95 here — gives the equal-tailed interval: the same total alpha as
the symmetric fit, but now alpha/2 per tail, and the band is no longer
forced to be centred. The price is calibration data — each tail needs at
least ceil(2 / alpha) windows per series instead of ceil(1 / alpha).
alpha — the fourth step
is a dead heat, and none of them is meaningfully wider.
The gap is widest at the long end, where the bias has had the most room
to accumulate: at the twelfth step the asymmetric band is barely half
the width.

The calibration data has to stretch further
Each tail is now its own one-sided quantile, so each enforces its ownceil(1 / level) floor. The binding requirement is the stricter of the
two:
ceil(1 / 0.05) = 20, comfortably under the
24 windows this page calibrates on. Tilt the budget toward the lower
tail and the floor moves fast:
alpha_lo=0.02 needs 50 calibration windows on its own, and the message
names alpha_lo rather than quoting the alpha you passed — the
failing knob is the one you want to see. Budget for it: an asymmetric
interval with a narrow tail needs several times the calibration data a
symmetric one at the same alpha would.
A one-sided interval means the forecaster is biased If both offsets come out with the same sign, the interval lies entirely above or entirely below the point forecast, andUsefitwarns:That is not a defect in the method — it is the method reporting one in the forecaster, and the offsets it produces are still valid. The fit above does not trip it, but not by much: its highest lower offset is-2.5, at the eleventh step, so a little more drift would put the whole band above the forecast. Fix the bias in the model if you can; conformalizing around it works, but it spends interval width on a problem a better point forecast would not have.
symmetric=False when one tail carries the real cost — inventory,
staffing, capacity — or when the forecaster is biased and you would
rather shift than widen. Stay symmetric otherwise: it needs less
calibration data, and an uninformative tilt just moves coverage around.
When the asymmetry depends on the features of each step rather than
being a constant offset, that is CQR’s job, not this one.
Using the array surface
The forecaster surface did three things above: ran the cross-validation, converted Nixtla frames to arrays, and called the calibration math. If your library already owns the first two — which is the case inside statsforecast, mlforecast and neuralforecast — you want the array surface:fit(predictions, truths) on the method returns the same
kind of fitted object, and interval(predictions) on that object
applies it — no forecaster and no adapter anywhere in the signature.
When not to use it
- Your coverage drifts over time. Split conformal calibrates once and never looks again, so a regime change leaves it silently mis-calibrated. Use ACI, AgACI or NexCP.
- Your errors are strongly heteroscedastic. The absolute-residual score makes one constant-width band per cell. If the noise scales with the level, that band is wrong nearly everywhere. Use CQR.
- You need the width itself to be informative. A constant band cannot say “this particular step is unusually uncertain”. Use SPCI.
References
- H. Papadopoulos, Kostas Proedrou, V. Vovk, and A. Gammerman, “Inductive Confidence Machines for Regression,” pp. 345–356, Jan. 2002, doi: 10.1007/3-540-36755-1_29.
- V. Vovk, A. Gammerman, and G. Shafer, Algorithmic Learning in a Random World. Springer Nature, 2005. doi: 10.1007/b106715.
- J. Lei, M. G’Sell, A. Rinaldo, R. J. Tibshirani, and L. Wasserman, “Distribution-Free Predictive Inference For Regression,” 2016. https://arxiv.org/abs/1604.04173
Next
- CQR — the other offline method, for heteroscedastic errors.
- Methods overview — the decision guide.
- Diagnostics — measuring an interval properly.

