Skip to main content
Your forecaster keeps its own features. X is a separate block handed to the calibration, letting interval width vary with the step being predicted rather than resting only on the size of recent errors. This page threads a feature block through calibration and prediction, and shows what SPCI does with it.

The shape

X is 4-D: (n_series, n_windows | n_samples, horizon, n_features). One feature vector per predicted step, not per observation. If you are forecasting 12 months ahead with 3 features, the last two axes are (12, 3): the features that describe each of those twelve future months.
What is supported in v0.1 Exogenous support is narrower than the array shape suggests:
  • calibrate_cv(X=...) always raises NotImplementedError. Cross-validated calibration with exogenous features is not implemented. Use the explicit calibrate(forecaster, histories, truths, X=...) loop, which is what this page does.
  • CQR rejects X entirely — both calibrate(X=...) and predict(X=...) raise, because the quantile path has nowhere to put it.
  • All three Nixtla adapters carry X through predict, given a train_df holding the regressors’ history. StatsForecast and MLForecast opt in with exog_features=[...]; NeuralForecast reads the list off the model’s futr_exog_list, since a model built that way requires those values. But that is the forecaster using its own features, which is a different thing from the calibration seeing X. It is not a lesser thing: a model that reads its regressors makes sharper point forecasts, which shrinks the calibration residuals and so the interval, for every method. What it does not give you is width that varies with the step being predicted.
  • refit and quantile methods are unavailable on an exogenous adapter. A history panel is target-only, so a refit would drop the regressors, and predict_quantiles has no X in its contract to feed them.
  • SPCI reads X at calibrate time, not at predict time. Its interval() ignores the X you pass and queries with the most recent calibration row instead, so future features shape the regressors it fits, not the query it answers.
So feeding X to the calibration still means CallableAdapter. That is what the rest of this page uses.

Features from the calendar

AirPassengers carries no exogenous variables, so build them from its own index: a trend and two Fourier pairs for the yearly cycle. These are known in advance, which is what makes them legitimate: at prediction time you can state the features of a month that has not happened yet.

A forecaster that takes X

CallableAdapter wraps any function of the history. If the function declares an X keyword, the adapter forwards the exogenous block to it; if it does not, X is ignored. That is the whole contract.

Calibrating with X

The explicit loop needs three aligned things: a list of histories, the truths that followed each one, and the features of those truths’ timestamps.
SPCI folds X into the feature vector of each cell’s quantile regressor, alongside the previous window_size residuals. So the width it predicts for a step can depend on which step it is — which month of the year, how far into the trend — and not only on how large the recent errors were. That happens at fit time. The fitted object’s interval() discards the X handed to it and queries with the most recent calibration row, so the “with X” numbers below differ through the regressors it fitted and the point forecast it was given — not through an interval query conditioned on the future features. Split conformal ignores X even when given it: its score is a plain absolute residual with nowhere to put a feature. Passing X to a method that cannot use it is not an error, and not useful either.

Predicting with X

At prediction time the features describe the steps you are about to forecast.

What the method actually gains

Held out over the last two years, with and without the feature block.
evaluate does not thread X diagnostics.evaluate takes (fitted, histories, truths) and no exogenous argument, so an X-aware holdout has to be rolled by hand. It is six lines, and it also makes the predict / update order explicit — which is worth seeing once.
On this problem, the features do not earn their place. SPCI with X is wider than SPCI without it (61.3 against 55.2) and covers worse (58.3% against 62.5%), and both sit far under the 90% target while plain split conformal — which cannot read X at all — covers best at 83.3%. That is a real result, not a broken example, and it has two causes worth separating:
  • The features are redundant. A trend and two Fourier pairs describe where in the year each step falls. SPCI’s residual window already carries that information implicitly, because the last 24 residuals of a seasonal series encode the season. Giving a model a feature it can already infer adds variance, not signal. Exogenous features pay off when they carry information the residual history cannot contain — a scheduled promotion, a known outage, a holiday calendar that does not repeat annually.
  • The horizon. SPCI under-covers at multi-step horizons on real series; see the warning on the SPCI page for the measurement and the reason. Both SPCI columns inherit that, with or without X.
The transferable lesson is the one this page opened with: thread the features, then measure. X is a hypothesis about what drives your uncertainty, and diagnostics is how you find out whether it was right.

Next

  • SPCI — the method that consumes X, in full.
  • Adapters — writing your own, including the X keyword.
  • Diagnostics — measuring whether the features earned their place.