Skip to main content

Nixtla

Tweet Slack LinkedIn

Conformal 🎯 Forecast

Adaptive conformal prediction intervals for time series forecasting

CI Python PyPI License Docs

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 fit returns 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
  • float32 preserved on the array surface (fit / interval / update)
  • Exogenous features threaded through calibrate and predict (see the roadmap for the limits)
  • Coverage and interval-quality diagnostics in conformalforecast.diagnostics
  • Deterministic given a config and inputs; seed defaults to 0, never None

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

🤝 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)

Source distribution for conformalforecast 0.1.0
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)

Table of built distributions (wheels) for conformalforecast 0.1.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page