Skip to main content
Bunobee Logo

Bunobee

PyPI version CI License: MIT Python versions

Bunobee is an experimental Bayesian time-series library built on JAX and NumPyro. It currently supports the following model families:

  • Damped Local Trend (DLT) — an exponential smoothing model with a damped slope component
  • State Space Models (SSP) — structured state space models with Kalman filtering and smoothing

Fun fact: "bunobee" is a word invented by my kids during their childhood to describe the action of the role they were playing.

Installation

pip install bunobee

JAX install caveat

Bunobee depends on JAX. pip install bunobee pulls in the CPU build of JAX, which is enough to get started. If you want GPU/TPU acceleration you must install the matching JAX build yourself, since the wheels differ by platform and accelerator:

# Example: CUDA 12 GPU build
pip install "jax[cuda12]"

See the JAX installation guide for the build that matches your hardware.

Quickstart

DLT forecast

Fit a Damped Local Trend (DLT) model to a univariate series and forecast:

import jax
import numpy as np
from bunobee.models.dlt import run_dlt_model, make_inference

jax.config.update("jax_enable_x64", True)

# Synthetic upward-trending series
y = np.linspace(10.0, 15.0, 20) + np.random.default_rng(0).normal(scale=0.3, size=20)

rng_key = jax.random.PRNGKey(0)
mcmc_args = {"num_warmup": 200, "num_samples": 200, "num_chains": 1}

# Fit: returns an xarray.Dataset of posterior samples (chain, draw, time)
idata = run_dlt_model(rng_key, lev_sm=0.5, slp_sm=0.5, theta=0.8, y=y, mcmc_run_args=mcmc_args)

# Forecast 10 steps beyond the training horizon
result = make_inference(rng_key, idata, lev_sm=0.5, slp_sm=0.5, theta=0.8, end_step=len(y) + 10)
print(result["forecast_samples"].shape)  # (n_samples, 30)

SSP forecast

Fit a multi-series state space model with the extended Kalman filter, then push it past the end of the sample with forecast_ssp. The forecast is a posterior replay: it launches from the last filtered state and walks a driftless random walk forward, one process-noise draw per step and per posterior sample, so the predictive interval widens with the horizon:

import jax
import jax.numpy as jnp
import numpy as np
import numpyro
import xarray as xr
from numpyro import distributions as dist
from numpyro.infer import MCMC, NUTS

from bunobee.models.ssp import build_forecast_design, forecast_ssp
from bunobee.models.ssp.kalman_1d_st_ekf import kalman_filter_1d_ekf_st

n_steps, n_series, horizon = 60, 3, 10
positivity = np.array([False, True])  # the level is free; the spend coefficient is kept positive
rng = np.random.default_rng(0)

# Design (n_steps, n_series, n_states): an intercept plus one covariate per series
spend = rng.uniform(0.5, 1.5, size=(n_steps, n_series, 1))
Z = np.concatenate([np.ones((n_steps, n_series, 1)), spend], axis=-1)
y = (Z * np.array([1.0, 0.5])).sum(-1) + rng.normal(scale=0.1, size=(n_steps, n_series))

def model():
    sigma_h = numpyro.sample("sigma_h", dist.HalfNormal(0.5 * jnp.ones(n_series)))
    sigma_q = numpyro.sample("sigma_q", dist.HalfNormal(0.05 * jnp.ones(2)))
    lp, at, *_ = kalman_filter_1d_ekf_st(
        a0=jnp.zeros(2), P0=jnp.eye(2), Z=jnp.asarray(Z), sigma_h=sigma_h, sigma_q=sigma_q,
        y=jnp.asarray(y), logp=True, exponent=1.0, positivity=jnp.asarray(positivity),
    )
    numpyro.factor("lp", lp)
    numpyro.deterministic("at", at)

mcmc = MCMC(NUTS(model), num_warmup=200, num_samples=200, num_chains=1, progress_bar=False)
mcmc.run(jax.random.PRNGKey(0))
samples = mcmc.get_samples(group_by_chain=True)

# forecast_ssp reads the filtered state path plus the process and observation noise
idata = xr.Dataset(
    {
        "at": (("chain", "draw", "time", "state"), np.asarray(samples["at"])),
        "sigma_q": (("chain", "draw", "state"), np.asarray(samples["sigma_q"])),
        "sigma_h": (("chain", "draw", "series"), np.asarray(samples["sigma_h"])),
        "positivity": (("state",), positivity),
    }
).assign_attrs(exponent=1.0, link="exp")  # link="exp": positivity states are EKF log-intensities

# Continue the design past the sample: the intercept carries forward, future spend is supplied
Z_future = build_forecast_design(Z, horizon, covariates_future=rng.uniform(0.5, 1.5, size=(horizon, n_series, 1)))
forecast = forecast_ssp(idata, Z_future, noise_embed=True, seed=0)
print(forecast["forecast_samples"].shape)  # (200, 10, 3) = (n_sample, horizon, n_series)

A worked example on a real multi-series panel — fan chart, state path, and per-series small multiples — is in playground/statespace_prototype/ssp_06_multi_series_forecast.ipynb.

Development

Notebooks under playground/ are committed without outputs, so the repo stays small and diffs stay readable. Two layers keep that true:

pip install nbstripout pre-commit
nbstripout --install     # git clean filter, strips outputs on commit (per clone, run once)
pre-commit install       # same hook, as a pre-commit safety net

CI enforces it regardless of local setup: the notebooks job runs nbstripout --verify on every tracked .ipynb and caps each notebook at 1.5 MiB. A notebook whose rendered plots are the point opts out with "keep_output": true in its notebook metadata — ssp_06_multi_series_forecast.ipynb is the only one that does, and the size cap is what keeps that opt-out honest.

License

Bunobee is released under the MIT License.

Metadata

Release files for bunobee 0.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bunobee 0.0.5
File Size Uploaded
bunobee-0.0.5.tar.gz 171.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bunobee 0.0.5
File Interpreter ABI Platform
bunobee-0.0.5-py3-none-any.whl Python 3 none any Details

Total release size: 296.3 kB

Release files / bunobee-0.0.5.tar.gz

Download URL bunobee-0.0.5.tar.gz
Size 171.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d4f55b81e0b7ce0fe5b5094f0ee47023e63de88f080572745fb17ae79408c2fd
BLAKE2b-256 checksum
How to use checksums
a54be2b7cdc96b343669343c427e596a7c9a249bbcd388e04aa5591af0d7b254
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 1, 2026.

Transparency log

Release files / bunobee-0.0.5-py3-none-any.whl

Download URL bunobee-0.0.5-py3-none-any.whl
Size 124.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3a8ce62e5acad5246430897d5774ac200cb7e408e7214da3df2c6d0de48ff20b
BLAKE2b-256 checksum
How to use checksums
e62cc284700bc19994e2f36bc6e11245a09d3ee0579597e74dde0e3dcfe5d053
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 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.5 This release

2 release files

0.0.4

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