Nixtla
Conformal 🎯 Forecast
Adaptive conformal prediction intervals for time series forecasting
ConformalForecast turns any point forecaster into one that emits calibrated prediction intervals. It ships seven conformal methods — split, CQR, ACI, AgACI, NexCP, SPCI, and weighted split — with a focus on the online and adaptive variants designed for non-exchangeable, sequential data. It works with StatsForecast, MLForecast, NeuralForecast, or a plain Python callable.
Installation
Install ConformalForecast from PyPI:
pip install conformalforecast
The core install depends only on numpy, narwhals, pydantic, and scipy.
It installs neither pandas nor polars: the Nixtla adapters accept whichever one
your forecasting library uses.
Everything else is an extra:
pip install "conformalforecast[nixtla]" # statsforecast, mlforecast, neuralforecast, utilsforecast
pip install "conformalforecast[spci]" # quantile-forest, for SPCI
pip install "conformalforecast[plotting]" # matplotlib
pip install "conformalforecast[all]" # all of the above
Quick start
Calibrate a forecaster on its own cross-validation errors, then predict with intervals:
import numpy as np
import pandas as pd
from statsforecast import StatsForecast
from statsforecast.models import SeasonalNaive
from conformalforecast import SplitConformal
from conformalforecast.adapters import StatsForecastAdapter
# A three-series monthly panel in Nixtla long format.
rng = np.random.default_rng(0)
n_series, T, season, horizon = 3, 132, 12, 6
t = np.arange(T)
df = pd.concat(
[
pd.DataFrame(
{
"unique_id": f"series_{s}",
"ds": pd.date_range("2000-01-01", periods=T, freq="MS"),
"y": 10 + 0.05 * t + 3 * np.sin(2 * np.pi * t / season) + rng.normal(0, 1, T),
}
)
for s in range(n_series)
],
ignore_index=True,
)
sf = StatsForecast(models=[SeasonalNaive(season_length=season)], freq="MS", n_jobs=1)
sf.fit(df)
forecaster = StatsForecastAdapter(
sf=sf, train_df=df, horizon=horizon, freq="MS", model_name="SeasonalNaive"
)
method = SplitConformal(alpha=0.1) # 90% intervals
fitted = method.calibrate_cv(forecaster, n_windows=30, refit=True)
panel = df.pivot(index="unique_id", columns="ds", values="y").to_numpy()
prediction = fitted.predict(panel)
prediction.point # (n_series, 1, horizon)
prediction.interval # (n_series, 1, horizon, 2) — (lower, upper)
alpha=0.1 asks for 90% coverage. calibrate_cv runs the forecaster's own
cross-validation, calibrates on the resulting errors and returns a fitted
object; calibrate takes explicit history/truth arrays instead when you
already have them. The method itself is never modified, so one configured
method can calibrate as many forecasters as you like.
See the quick start for the full walkthrough, including measured coverage on a held-out split.
Methods
| Method | Handles | Online | Reference |
|---|---|---|---|
SplitConformal |
the exchangeable baseline | – | Papadopoulos et al. 2002 |
ConformalizedQuantileRegression |
heteroscedastic errors, via quantile forecasts | – | Romano et al. 2019 |
AdaptiveConformalInference |
distribution shift, by adapting alpha |
✅ | Gibbs & Candès 2021 |
AggregatedAdaptiveConformalInference |
shift without picking a learning rate | ✅ | Zaffran et al. 2022 |
NonexchangeableConformalPrediction |
shift, by down-weighting stale residuals | ✅ | Barber et al. 2023 |
SequentialPredictiveConformalInference |
conditional coverage, via a quantile regressor | ✅ | Xu & Xie 2023 |
WeightedSplit |
covariate shift and transfer, by reweighting calibration scores | – | Tibshirani et al. 2019 |
The fitted objects of online methods implement update(prediction, truth),
which advances them in place, so intervals keep recalibrating as new
observations arrive.
Every fitted object offers recalibrate() for a target domain you have labels
for, returning a new fitted object. A fitted WeightedSplit additionally
offers adapt(), which needs only target features or target history — no
target labels at all. See
Transfer and adaptation.
Works with the Nixtlaverse
An adapter is the uniform interface a conformal method talks to. It converts a library's long-format frames into arrays at the boundary, so the calibration math never sees a DataFrame. Fit your forecaster as usual, then wrap it.
StatsForecast
from conformalforecast.adapters import StatsForecastAdapter
forecaster = StatsForecastAdapter(sf=sf, train_df=df, horizon=14, freq="D", model_name="AutoETS")
MLForecast
from conformalforecast.adapters import MLForecastAdapter
forecaster = MLForecastAdapter(
mlf=mlf, train_df=df, horizon=14, freq="D", model_name="LGBMRegressor"
)
NeuralForecast
from conformalforecast.adapters import NeuralForecastAdapter
# The horizon is read from the model's own `h`.
forecaster = NeuralForecastAdapter(nf=nf, train_df=df, freq="D", model_name="NHITS")
Anything else
from conformalforecast.adapters import CallableAdapter
forecaster = CallableAdapter(predict_fn=my_model.forecast, horizon=14, n_series=100)
Install the forecasting libraries you use separately, or through the nixtla
extra. Importing conformalforecast never imports statsforecast, mlforecast,
neuralforecast, or torch. See the
adapters guide
for capability requirements and exogenous handling.
A method and the fitted object it returns
Every method is a pair: a method that is pure configuration, and the
fitted object its fit / calibrate / calibrate_cv returns. The method
is never modified by fitting; everything learned from calibration lives on the
returned object.
from conformalforecast import AbsoluteResidual, AdaptiveConformalInference
method = AdaptiveConformalInference(alpha=0.1, gamma=0.01, score=AbsoluteResidual())
fitted = method.calibrate_cv(forecaster, n_windows=50)
result = fitted.predict(history)
fitted.update(result.point, truth) # online methods advance in place
The score is configuration too: pass score= to the constructor, or leave it
out for the method's default. An incompatible score is rejected right there.
The forecaster is the one collaborator bound at calibration.
Which entry point you call depends on who owns the cross-validation loop.
The array surface — method.fit(predictions, truths), then
fitted.interval(predictions) and fitted.update(prediction, truth). Pure
array-in/array-out calibration math, taking no forecaster at all. A signature
here may only accept numpy-coercible arrays and plain-Python config — never
adapters, DataFrames, or library types. This is the surface for libraries that
already own their cross-validation and predict orchestration: statsforecast,
mlforecast, neuralforecast.
The forecaster surface — method.calibrate(forecaster, histories, truths)
or method.calibrate_cv(forecaster, n_windows), then
fitted.predict(history). CV/predict dispatch through a ForecasterAdapter,
written once in the base classes. This is the surface for standalone users.
The architecture page explains when to reach for which.
Features
- Seven conformal methods, four of them online and adaptive
- Declarative methods whose
fitreturns a fitted object, with an array surface for embedding and a forecaster surface for standalone use - Panels are first class — every method is multi-series and multi-horizon
- Ragged panels through NaN masking, with no imputation
- NaN-aware quantiles identical across numpy versions — the exact
ceil((1 - alpha)(n + 1))order statistic for split and CQR float32preserved on the array surface (fit/interval/update)- Exogenous features threaded through
calibrateandpredict(see the roadmap for the limits) - Coverage and interval-quality diagnostics in
conformalforecast.diagnostics - Deterministic given a config and inputs;
seeddefaults to0, neverNone
Why ConformalForecast?
Conformal prediction gives distribution-free coverage guarantees without assuming a noise model. The catch for forecasting is that the classic guarantee assumes exchangeable data, and time series are not exchangeable: they drift, they have regimes, and their errors are serially correlated. This library prioritizes the methods built for that setting.
It is designed for:
- Adding calibrated intervals to a point forecaster that has none
- Replacing intervals from a model whose distributional assumptions do not hold
- Keeping coverage under distribution shift, via online recalibration
- Auditing whether the intervals you already ship actually cover
- Embedding calibration math directly inside a forecasting library
Know the limits. Conformal coverage is marginal, not conditional: 90% intervals cover 90% of the time on average, not for every series or every horizon step. Adaptive methods trade finite-sample exactness for robustness to shift, and their guarantees are asymptotic or regret-based. Interval width is a property of your forecaster — a better model gives narrower intervals, not more valid ones.
Documentation
- Getting started
- Tutorials and API reference
- Roadmap — known gaps and limitations
- Contributing
- Changelog
- Support
- Security policy
- Third-party notices
- Code of conduct
- Citation
🤝 How to contribute
Bug reports, method implementations, adapters, and documentation are all welcome. Read CONTRIBUTING.md for the development setup, the one-class-two-surfaces architecture new methods have to fit, and the PR expectations. For a new method or adapter, open an issue first — a short design discussion up front avoids rework.
git clone https://github.com/Nixtla/conformalforecast.git
cd conformalforecast
uv sync --all-extras --group dev
uv run pytest tests/unit
🙏 How to cite
If ConformalForecast helps your research, please cite it. The repository ships a CITATION.cff, so GitHub's "Cite this repository" button gives you BibTeX directly:
@software{conformalforecast,
author = {Peixeiro, Marco and Caballero Ramirez, Saul and {Nixtla}},
title = {{ConformalForecast}: Adaptive conformal prediction for time series forecasting},
year = {2026},
license = {Apache-2.0},
url = {https://github.com/Nixtla/conformalforecast}
}
Please also cite the paper behind whichever method you used — the references are in each method's API documentation.
AI disclaimer
Parts of this project were developed with assistance from generative AI tools. All AI-assisted code and documentation are reviewed, tested, and maintained by human contributors, who remain responsible for correctness, security, licensing, and design.
License
ConformalForecast is licensed under the Apache License 2.0.
Metadata
Release files for conformalforecast 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| conformalforecast-0.1.0.tar.gz | 287.3 kB | With provenance | Details for conformalforecast-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| conformalforecast-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for conformalforecast-0.1.0-py3-none-any.whl |
Total release size: 457.8 kB
Release files / conformalforecast-0.1.0.tar.gz
| Download URL | conformalforecast-0.1.0.tar.gz |
|---|---|
| Size | 287.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
603e4de453599bf286a4c8444c6dcb676d6420c4ee1a5860b466a224582a7869
|
|
BLAKE2b-256 checksum How to use checksums |
124d45a67b23ad407fd4f06e17f2e4c341381259a814fdbfab3c8a5c86128097
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / conformalforecast-0.1.0-py3-none-any.whl
| Download URL | conformalforecast-0.1.0-py3-none-any.whl |
|---|---|
| Size | 170.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f991bc3b30b613f31ae357e2a8263e678202e0009ff9efb979144c0afade3b2
|
|
BLAKE2b-256 checksum How to use checksums |
381c39305783aed1244927d58293c324b84e01b48f0f422f91e347a3b2fd2020
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logSecurity for conformalforecast 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for conformalforecast-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.