Skip to main content

PyMC-Forecast

CI Docs

Bayesian time-series forecasting with PyMC: you write the generative model; the package handles the train/forecast plumbing, inference, backtesting, and evaluation.

A PyMC port of numpyro_forecast (itself a port of Pyro's pyro.contrib.forecast) — redesigned around PyMC idioms rather than a 1:1 translation.

Status: early development. The design and roadmap live in PLAN.md and the issue tracker.

Documentation: https://pymc-labs.github.io/pymc-forecast/ — API reference and executed example notebooks (univariate, hierarchical, covariates, state-space, VAR, M5).

Quickstart

Write a model with one predict() call, fit it, and forecast:

import numpy as np, pandas as pd, pymc as pm, pytensor.tensor as pt
from pymc_forecast import Forecaster, ForecastingModel, backtest, evaluate_forecast

# a trending weekly series; hold out the last 8 weeks
dates = pd.date_range("2024-01-07", periods=60, freq="W")
y = pd.Series(np.cumsum(np.random.default_rng(0).normal(0.2, 1.0, 60)) + 10, index=dates)
train, test = y.iloc[:52], y.iloc[52:]


class LocalLevel(ForecastingModel):
    def model(self, covariates, data=None):
        # a per-step drift latent; innovations adds the matching `_future` latent
        drift = self.innovations("drift", pm.Normal.dist(0.0, 0.5))
        sigma = pm.HalfNormal("sigma", 1.0)
        self.predict(pm.Normal.dist(0.0, sigma), pt.cumsum(drift))  # local-linear trend


model = LocalLevel()
fc = Forecaster(model, train, num_steps=5_000, random_seed=0)  # ADVI
idata = fc.forecast(horizon=8, num_samples=500, random_seed=0)
forecast = idata["predictions"]["forecast"]  # dims: (chain, draw, time_future)
# Outputs stay draw-level (never reduced to means/quantiles); prediction_samples(idata)
# extracts the samples Dataset from a forecast or in-sample result alike.

# score against the held-out weeks (aligned by dim name, not axis position)
truth = test.to_xarray().rename({"index": "time_future"})
print(evaluate_forecast(forecast, truth))  # {'mae': ..., 'rmse': ..., 'crps': ..., 'coverage': ...}

# rolling-origin backtest over the whole series
results = backtest(
    y,
    None,
    model,
    min_train_window=48,
    test_window=4,
    stride=4,
    num_samples=200,
    forecaster_options={"num_steps": 3_000},
    random_seed=0,
)

Swap Forecaster for HMCForecaster (NUTS, with nuts_sampler="nutpie"/"numpyro"/...) or PathfinderForecaster (pymc-extras) — the fit/forecast interface is identical. For models with real covariates, pass full-horizon covariates to .forecast() instead of horizon=, or hand the post-training rows alone to future_covariates= for a forecast conditioned on them. Covariate-free models can also forecast over an exact — even irregular — later time index with future_index=; the horizon length is derived from it at forecast time. See ssoe for observation/error-driven recursions such as ARMA and Holt-Winters, markov_series for state-space latents, and predict_mvn for observation noise correlated across time.

Functional API

The same model can be written as a plain function: it builds the current Horizon itself and passes it to the module-level innovations / predict primitives that the ForecastingModel helpers wrap. The functional fitters return a FitResult; draw the posterior from it and forecast:

from pymc_forecast import (
    Horizon,
    draw_posterior,
    fit_vi,
    forecast,
    innovations,
    null_covariates,
    predict,
)


def local_level(covariates, data=None):
    h = Horizon.from_data(covariates, data)
    drift = innovations(h, "drift", pm.Normal.dist(0.0, 0.5))
    sigma = pm.HalfNormal("sigma", 1.0)
    predict(h, pm.Normal.dist(0.0, sigma), pt.cumsum(drift))


result = fit_vi(local_level, train, num_steps=5_000, random_seed=0)  # ADVI
posterior = draw_posterior(result, 500, random_seed=0)
idata = forecast(local_level, posterior, train, null_covariates(dates), random_seed=0)

Both forms build the same PyMC model. The forecaster classes are thin wrappers that call fit_vi / fit_mcmc / fit_pathfinder, and every forecaster, fitter, and backtest accepts either model form.

pymc-extras statespace structural models (level/trend, seasonality, SARIMAX, ...) are first-class citizens too: define one as a StatespaceModel and fit it with StatespaceForecaster — the same forecast (including exogenous-regression covariates), predict_in_sample, backtest, and metrics calls apply, with the Kalman filter marginalizing the latent states instead of sampling them (see docs/examples/scan_vs_statespace_local_level.ipynb). The pymc-extras integrations (PathfinderForecaster / fit_pathfinder, StatespaceForecaster) are an optional extra: install with pip install 'pymc-forecast[extras]'.

Priors stay user-injectable on the model object: pass pymc_extras.prior.Prior objects directly to innovations/predict, or declare named default_priors on a ForecastingModel/StatespaceModel subclass and let callers override any subset with Model(priors={...}) — nested hyper-priors are shared across the train/forecast split so replay semantics hold.

Design principles

  • One model trains and forecasts. In-sample time latents are fitted; the forecast horizon uses separate {name}_future variables that pm.sample_posterior_predictive draws from the prior while replaying the posterior for everything else.
  • Dims and coords everywhere. No positional axis conventions: variables carry named dims ("time", "time_future", "obs", batch dims), results are arviz.InferenceData / xarray objects with real coordinates, and metrics are dim-aware. The prediction output schema is documented, test-locked public API.
  • Not AutoML. No model zoo, no automatic feature pipelines — a clean path from a hand-written PyMC model to probabilistic forecasts and scores.
  • Leverage the ecosystem. ADVI/NUTS from PyMC core, Pathfinder and state-space models from pymc-extras, diagnostics and storage from ArviZ.

Development

Requires Python >= 3.11 and uv:

uv sync --all-extras
uv run pytest
uv run ruff check .

To build the documentation (the example notebooks are committed fully executed; CI re-executes them with reduced sampling settings):

uv sync --all-extras --group docs
uv run sphinx-build -b html docs docs/_build/html

License

Apache-2.0. Portions derived from numpyro_forecast (Apache-2.0).

Metadata

Release files for pymc-forecast 0.3.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 pymc-forecast 0.3.0
File Size Uploaded
pymc_forecast-0.3.0.tar.gz 115.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pymc-forecast 0.3.0
File Interpreter ABI Platform
pymc_forecast-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 201.4 kB

Release files / pymc_forecast-0.3.0.tar.gz

Download URL pymc_forecast-0.3.0.tar.gz
Size 115.8 kB
Tags Source
SHA-256 checksum
How to use checksums
0d29996855992255dadb899fce525d60f323a0f771b297b51328efca59f39d76
BLAKE2b-256 checksum
How to use checksums
fe807e8fe8357ca0fa3fe34a16de9a26f20fc7b8a7e47483e3bb618f16d961ec
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 5, 2026.

Transparency log

Release files / pymc_forecast-0.3.0-py3-none-any.whl

Download URL pymc_forecast-0.3.0-py3-none-any.whl
Size 85.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
98642f1fc58b7776845db556a081916f313e0736ce77aea384a5afe7de7e1772
BLAKE2b-256 checksum
How to use checksums
8ca58fad53b5c047f2de6bcd35bc0e369e0f0b024b711d70d081dde72d3e7fd9
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 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.1

2 release files

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