pymc_forecast.model#
Model bodies are (covariates, data=None). Build the horizon with
Horizon.from_data. Per-step latents use innovations; observations use
predict.
Model-building core: the train/forecast Horizon and the primitives built on it.
The primitives here register time-series latents and observation variables
against the Horizon.
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 (covariates, data) -> 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.EXPECTED_OBSERVATION_FORECAST_VAR = 'expected_observation_future'#
Reserved name of the forecast-horizon expected observation registered by
predict().
- pymc_forecast.model.EXPECTED_OBSERVATION_VAR = 'expected_observation'#
Reserved name of the in-sample expected observation registered by
predict().It is the conditional expected observation in observed outcome units, excluding observation noise.
- pymc_forecast.model.FORECAST_VAR = 'forecast'#
Name of the forecast-horizon variable registered by
predict().Also registered by
predict_mvn().
- pymc_forecast.model.MU_FORECAST_VAR = 'mu_future'#
Reserved name of the forecast-horizon noise-free latent predictor from
predict().See
MU_VAR.
- pymc_forecast.model.MU_VAR = 'mu'#
Reserved name of the in-sample noise-free latent predictor registered by
predict().It is the latent passed to
predict(), before observation noise (for GLM-style models this is the linear predictor, not the distribution mean).
- pymc_forecast.model.OBS_VAR = 'obs'#
Name of the observed (in-sample) variable registered by
predict().Also registered by
predict_mvn().
- class pymc_forecast.model.ForecastingModel(priors=None)[source]#
Bases:
PriorConfig,ABCObject-oriented facade over the functional primitives.
Subclasses implement
model()and use the bound helpersinnovations(),predict()andmarkov_series(), which thread the currentHorizon(available ashorizon) automatically. An instance is a valid model function forbuild_model()and the forecaster classes.Priors are user-injectable (see
PriorConfig): a subclass declares its overridable defaults indefault_priorsand readsself.prior_config[...]in the model body; callers override any subset at construction time:import pytensor.tensor as pt from pymc_extras.prior import Prior from pymc_forecast.model import ForecastingModel class LocalLevel(ForecastingModel): default_priors = { "drift": Prior("Normal", mu=0, sigma=0.1), "noise": Prior("Normal", sigma=Prior("HalfNormal", sigma=1)), } def model(self, covariates, data=None): drift = self.innovations("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)})
- Parameters:
priors (
mapping, optional) – Named prior overrides merged overdefault_priors.Nonekeeps the defaults.
- abstractmethod model(covariates, data=None)[source]#
Define the generative model.
The body must register the
"obs"variable exactly once, normally by callingpredict()once (a second call raisesHorizonError).- Parameters:
covariates (
xarray.DataArray) – Normalized (time-first) covariates spanning the full horizon.data (
xarray.DataArray, optional) – Normalized (time-first) observed data;Nonefor prior-only builds.
- property horizon#
The
Horizonof the model build currently in progress.Raises
HorizonErrorwhen accessed outside a model build.
- innovations(name, dist, *, dims=())[source]#
Bound
innovations()using the current build’s horizon.- Parameters:
- Returns:
pytensor.tensor.TensorVariable– The latent over the full horizon, time on axis 0 (seeinnovations()).- Raises:
pymc_forecast.exceptions.HorizonError – If called outside a model build, or for the conditions listed in
innovations().
- predict(obs, latent, *, expected_observation=None, dims=None)[source]#
Bound
predict()using the current build’s horizon.- Parameters:
obs (
pymc_extras.prior.Prior,callableorpytensor.tensor.TensorVariable) – Observation specification (seepredict()).latent (
pytensor.tensor.TensorVariable) – Full-horizon predictor with time on axis 0 and lengthhorizon.duration.expected_observation (
pytensor.tensor.TensorVariable, optional) – Full-horizon conditional expected observation in observed outcome units.Noneregisters no expected-observation variables.dims (
tupleofstr, optional) – Extra (non-time) dims of the observation.Noneinfers them from the data’s non-time dims (()for prior-only builds).
- Raises:
pymc_forecast.exceptions.HorizonError – If called outside a model build, or for the conditions listed in
predict().ValueError – If
obsis aPriorwithmuset.KeyError – If an entry of
dimsis not a model coord.
- markov_series(name, init, transition, *, params=(), xs=None, dims=())[source]#
Bound
markov_series()using this build’s horizon.- Parameters:
name (
str) – Base variable name; the forecast segment isf"{name}_future".init (
float,array_likeorpytensor.tensor.TensorVariable) – Initial state fed to the first transition (cast to float64).transition (
callable) –(z_prev, *params) -> dist(withxs:(z_prev, x_t, *params) -> dist) returning a.dist().params (
sequence, default()) – Random variables the transition uses, passed as explicit inputs.xs (
array_like, optional) – Exogenous inputs, positional with time on axis 0 and at leasthorizon.durationrows.Nonemeans no inputs.dims (
tupleofstr, default()) – Extra (non-time) dims of the per-step state.
- Returns:
pytensor.tensor.TensorVariable– The latent over the full horizon, time on axis 0 (seemarkov_series()).- Raises:
pymc_forecast.exceptions.HorizonError – If called outside a model build, or for the conditions listed in
markov_series().
- class pymc_forecast.model.Horizon(data, time, time_future=<factory>)[source]#
Bases:
objectThe train/forecast split of a single model build, derived from coords.
Each parameter is available as an attribute of the same name.
- Parameters:
data (
xarray.DataArrayorNone) – Observed data, stored as given (time-first when built bybuild_model()), orNonefor prior-only builds.time (
numpy.ndarray) – Coordinate values of the in-sample window (lengtht_obs); in prior-only builds, the whole covariate span.time_future (
numpy.ndarray, optional) – Coordinate values of the forecast horizon. Empty whenever there is no horizon. Defaults to an empty array.
- property t_obs#
Number of in-sample time steps (the full covariate span in prior-only builds).
- property future#
Number of forecast time steps (
0when there is no forecast horizon).
- property duration#
Total horizon length
t_obs + future.
- classmethod from_data(covariates, data)[source]#
Derive the horizon from normalized data/covariate time coords.
covariatesspan the full horizon;data(if given) covers the observed prefix. Withdata=None(prior-only builds) the whole covariate span counts as observed time. No normalization is applied here (build_model()normalizes withas_dataarray()first).- Parameters:
covariates (
xarray.DataArray) – Normalized covariates with a"time"coord spanning the full horizon.data (
xarray.DataArrayorNone) – Normalized observed data with a"time"dim, orNonefor a prior-only build.
- Returns:
Horizon– The derived horizon;datais stored as given.- Raises:
pymc_forecast.exceptions.AlignmentError – If
datais given and is not aligned withcovariates(seevalidate_alignment()).
- pymc_forecast.model.build_model(model_fn, data, covariates, *, coords=None)[source]#
Build a
pm.Modelfrom 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 (data dims take precedence over covariate dims of the same name; unlabeled dims get integer coords), plus any usercoords, which are applied last and override the derived coords.- Parameters:
model_fn (
callableorForecastingModel) – The model body(covariates, data) -> Noneor aForecastingModelinstance. It is called with the normalized (time-first)xarray.DataArraycovariates and data (orNone).data (
xarray.DataArray,pandas.Series,pandas.DataFrameorarray_like) – Observed training series, normalized withas_dataarray()("time"first; 2-D input gets a"series"dim).Nonegives a prior-only build over the whole covariate span (obsis then unobserved).covariates (
xarray.DataArray,pandas.Series,pandas.DataFrameorarray_like) – Covariates spanning the full horizon, normalized withas_dataarray(2-D input gets a"covariate"dim). Rows past the end ofdatadefine the forecast horizon (usenull_covariates()if the model has none).coords (
mapping, optional) – Extra coords to register on the model; they override derived coords of the same name.Noneadds none.
- Returns:
pymc.Model– The built model (its context is no longer active on return).- Raises:
pymc_forecast.exceptions.AlignmentError – If
dataorcovariatescannot be normalized (a DataArray without a"time"dim, or an array that is not 1-D or 2-D), or if they are misaligned (seeHorizon.from_data()).pymc_forecast.exceptions.HorizonError – If the model body registered no
"obs"variable. Errors raised by the model body itself (e.g. bypredict()orinnovations()) propagate unchanged.
- pymc_forecast.model.innovations(h, name, dist, *, dims=())[source]#
Sample a per-step latent over the full horizon.
distis a pymc-extrasPrioror an unnamed.dist()tensor. APriorfollowsprior_rv_factory(), including one shared draw of nested hyper-priors. A.dist()is resized to("time", *dims)and registered asname, plus("time_future", *dims)as{name}_futurewhen forecasting. Its parameters broadcast against the trailingdims(e.g. a per-series scale), and a multivariate dist’s support fills the lastdims. The time axis belongs to this helper, so a.dist()with more axes thandimsis rejected. So are a model variable (passpm.Normal.dist(...), notpm.Normal(name, ...)) and a.dist()whose parameters are unnamed random variables: create a random scale as a model variable (sigma = pm.HalfNormal("sigma", 1)) and passpm.Normal.dist(0, sigma).- Parameters:
h (
Horizon) – The horizon of the current model build.name (
str) – Base variable name of the in-sample latent; the forecast segment is registered asf"{name}_future".dist (
pymc_extras.prior.Priororpytensor.tensor.TensorVariable) – A pymc-extrasPrioror an unnamed.dist()tensor.dims (
tupleofstr, default()) – Extra (non-time) dims of the per-step latent; each must be a model coord.
- Returns:
pytensor.tensor.TensorVariable– The latent over the full horizon, time on axis 0. Whenh.future == 0this is the registered model variablenameitself; otherwise it is an unnamed concatenation ofnameand{name}_futurewith shape(h.duration, *dim lengths).- Raises:
pymc_forecast.exceptions.HorizonError – If
distis neither aPriornor an unnamed.dist(), is a model variable, depends on unnamed random variables, or has more axes thandims; if a.dist()latent needs a dim that is not a model coord; or if aPriorhas a time-dimmed hyper-prior.
- pymc_forecast.model.predict(h, obs, latent, *, expected_observation=None, dims=None)[source]#
Register the observation and forecast variables of the model.
latentis 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)) thatpm.sample_posterior_predictivedraws.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 (seedocs/schema.md). For GLM-style models this is the linear predictor passed topredict, not the distribution mean. The names"mu"and"mu_future"are therefore reserved: a model body must not define variables with these names.Model authors may additionally pass
expected_observationto record the conditional expected observation in observed outcome units, excluding observation noise. It is emitted as"expected_observation"and"expected_observation_future"without changing the meaning of"mu"/"mu_future". This value is explicit becausepredictdoes not infer inverse links or distribution means fromobs.This single primitive covers both upstream
predict(location-family noise: passlambda name, mu, dims, observed: pm.Normal(name, mu, sigma, dims=dims, observed=observed)) and upstreampredict_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 (
Horizon) – The horizon of the current model build.obs (
pymc_extras.prior.Prior,callableorpytensor.tensor.TensorVariable) –Observation specification, dispatched in order:
a pymc-extras
Prior(mumust be left unset; nested hyper-priors are shared across segments);a 4-argument factory
(name, latent, dims, observed) -> RV: a callable with at least four positional parameters, or one that binds four positional arguments but not one (e.g. afunctools.partial). Parameter names do not matter;any other callable, called once per segment with the segment latent as its first positional argument and returning an unnamed
.dist(). A bare.distclassmethod is accepted only when that parameter ismu(e.g.pm.Poisson.dist);pm.StudentT.disttakesnufirst and is rejected. The callable must not create model variables: create them in the model body and close over them;a zero-centered
pm.Normal.dist/pm.StudentT.distwhose location is replaced by the segment latent.
latent (
pytensor.tensor.TensorVariable) – Full-horizon predictor with time on axis 0; its length along axis 0 must beh.duration.expected_observation (
pytensor.tensor.TensorVariable, optional) – Full-horizon conditional expected observation in observed outcome units, with time on axis 0. For a Poisson log-link model whoselatentiseta, passpt.exp(eta).Noneregisters no expected-observation variables; their names are reserved either way.dims (
tupleofstr, optional) – Extra (non-time) dims of the observation.Noneinfers them from the data’s non-time dims (()for prior-only builds).
- Raises:
pymc_forecast.exceptions.HorizonError – If the model already defines
"mu","mu_future","expected_observation"or"expected_observation_future"(including a secondpredictcall in one model); or ifobsis invalid: any other dist, a non-zero location, a bare.distclassmethod whose first parameter is notmu, a callable that creates model variables or does not return a.dist(), a model variable instead of a.dist(), a.dist()depending on unnamed random variables, or aPriorwith a time-dimmed hyper-prior.ValueError – If
obsis aPriorwithmuset.KeyError – If an entry of
dimsis not a model coord.