pymc_forecast.model#

Model-building core: the train/forecast Horizon and the primitives that register time-series latents and observation variables against it.

The package’s central invariant (inherited from pyro / numpyro_forecast): one model definition both trains and forecasts. In-sample time latents live on variables dimmed "time"; the forecast horizon lives on separate {name}_future variables dimmed "time_future". Those future variables are absent from the fitted posterior, so pm.sample_posterior_predictive replays the posterior in-sample and draws the future from the prior — conditioned on the replayed parents (see tests/test_replay_mechanism.py).

A model is a callable (Horizon, covariates) -> None executed inside a managed pm.Model whose coords carry real time coordinates. The horizon is derived from the coords: future = len(covariates.time) - len(data.time).

pymc_forecast.model.FORECAST_VAR = 'forecast'#

Reserved name of the forecast-horizon variable registered by predict().

pymc_forecast.model.MU_FORECAST_VAR = 'mu_future'#

Reserved name of the forecast-horizon noise-free latent predictor registered by predict() (see MU_VAR).

pymc_forecast.model.MU_VAR = 'mu'#

Reserved name of the in-sample noise-free latent predictor registered by predict() — the latent passed to it, before observation noise (for GLM-style models this is the linear predictor, not the distribution mean).

pymc_forecast.model.OBS_VAR = 'obs'#

Reserved name of the observed (in-sample) variable registered by predict().

class pymc_forecast.model.ForecastingModel(priors=None)[source]#

Bases: PriorConfig, ABC

Object-oriented facade over the functional primitives.

Subclasses implement model() and use the bound helpers time_series() / predict(), which thread the current Horizon automatically. An instance is a valid model function for build_model() and the forecaster classes.

Priors are user-injectable (see PriorConfig): a subclass declares its overridable defaults in default_priors and reads self.prior_config[...] in the model body; callers override any subset at construction time:

from pymc_extras.prior import Prior

class LocalLevel(ForecastingModel):
    default_priors = {
        "drift": Prior("Normal", mu=0, sigma=0.1),
        "noise": Prior("Normal", sigma=Prior("HalfNormal", sigma=1)),
    }

    def model(self, h, covariates):
        drift = self.time_series("drift", self.prior_config["drift"])
        self.predict(self.prior_config["noise"], pt.cumsum(drift))

LocalLevel(priors={"drift": Prior("StudentT", nu=4, mu=0, sigma=0.2)})
abstractmethod model(h, covariates)[source]#

Define the generative model; call predict() exactly once.

time_series(name, rv_fn, *, dims=())[source]#

Bound time_series() using the current build’s horizon.

predict(obs_fn, latent, *, dims=None)[source]#

Bound predict() using the current build’s horizon.

class pymc_forecast.model.Horizon(data, time, time_future=<factory>)[source]#

Bases: object

The train/forecast split of a single model build, derived from coords.

data#

Observed data (time-first DataArray), or None for prior-only builds.

Type:

xarray.core.dataarray.DataArray | None

time#

Coordinate values of the observed window (length t_obs).

Type:

numpy.ndarray

time_future#

Coordinate values of the forecast horizon (empty while training).

Type:

numpy.ndarray

property t_obs#

Number of observed (in-sample) time steps.

property future#

Number of forecast time steps (0 while training).

property duration#

Total horizon length t_obs + future.

classmethod from_arrays(covariates, data)[source]#

Derive the horizon from normalized data/covariate time coords.

covariates span the full horizon; data (if given) covers the observed prefix. With data=None (prior-only builds) the whole covariate span counts as observed time.

pymc_forecast.model.build_model(model_fn, data, covariates, *, coords=None)[source]#

Build a pm.Model from a model function and (data, covariates).

The horizon is derived from the time coords: covariates span the full horizon, data covers the observed prefix. Registered coords: "time" (observed steps), "time_future" (forecast steps, only when forecasting), every non-time dim of data/covariates, plus any user coords.

Parameters:
  • model_fn – The model body (Horizon, covariates) -> None or a ForecastingModel instance.

  • data – Observed data (DataArray / Series / DataFrame / ndarray), or None for a prior-only build over the whole covariate span.

  • covariates – Covariates spanning the full horizon (use null_covariates() if the model has none).

  • coords – Extra coords to register on the model.

pymc_forecast.model.predict(h, obs_fn, latent, *, dims=None)[source]#

Register the observation and forecast variables of the model.

latent is the deterministic full-horizon predictor (time on axis 0). The observed prefix becomes the likelihood ("obs", dims ("time", *dims)); when forecasting, the suffix becomes the unobserved "forecast" variable (dims ("time_future", *dims)) that pm.sample_posterior_predictive draws.

The latent itself is also recorded, noise-free, as "mu" (in-sample) and "mu_future" (forecast horizon) Deterministics — the documented way to separate parameter/latent uncertainty from observation noise (see docs/schema.md). For GLM-style models this is the linear predictor passed to predict, not the distribution mean. The names "mu" and "mu_future" are therefore reserved: a model body must not define variables with these names.

This single primitive covers both upstream predict (location-family noise: pass lambda name, mu, dims, observed: pm.Normal(name, mu, sigma, dims=dims, observed=observed)) and upstream predict_glm (any link, e.g. lambda name, eta, dims, observed: pm.Poisson(name, pt.exp(eta), dims=dims, observed=observed)) — PyMC likelihoods take their parameters directly, so no distribution surgery is needed.

Parameters:
  • h – The horizon of the current model build.

  • obs_fn – Observation factory (name, latent, dims, observed) -> RV. Must create the variable with exactly the dims it is given, and pass observed through. A pymc-extras Prior is also accepted (e.g. Prior("Normal", sigma=Prior("HalfNormal", sigma=1))): its distribution becomes the likelihood with the latent as location, and nested hyper-priors are created once under "obs" and shared by the observed and forecast segments (see pymc_forecast.priors).

  • latent – Full-horizon predictor with time on axis 0.

  • dims – Extra (non-time) dims of the observation. Default: inferred from the data’s non-time dims (() for prior-only builds).

pymc_forecast.model.time_series(h, name, rv_fn, *, dims=())[source]#

Sample a per-step latent over the full horizon.

Calls rv_fn(name, ("time", *dims)) for the observed window and — when forecasting — rv_fn(f"{name}_future", ("time_future", *dims)) for the horizon, concatenating along time (axis 0). The future variable is what keeps the posterior blind to the horizon (see module docstring).

Parameters:
  • h – The horizon of the current model build.

  • name – Base variable name for the in-sample latent.

  • rv_fn – Factory creating the variable, e.g. lambda name, dims: pm.Normal(name, 0, drift_scale, dims=dims). It must create the variable with exactly the dims it is given. A pymc-extras Prior is also accepted (e.g. Prior("Normal", mu=0, sigma=0.1)); nested hyper-priors are created once under name and shared by the in-sample and future segments (see pymc_forecast.priors).

  • dims – Extra (non-time) dims of the latent, e.g. ("series",).

Returns:

TensorVariable – The latent over the full horizon, time on axis 0.