-
Notifications
You must be signed in to change notification settings - Fork 22
Refitting and Reforecasting
This page documents the methods that propagate parameter uncertainty
into the fitted values and forecasts. They sit on top of
ADAM's state-space machinery and feed the
interval="complete" / interval="confidence" branches of
Fitted-Values-and-Forecasts.
The same C++ kernel (adamCore::reapply and adamCore::reforecast)
is called from both languages, so the per-replicate trajectories are
bit-equivalent given identical parameter draws. R uses MASS::mvrnorm
and Python uses numpy.random.Generator.multivariate_normal — the
RNGs differ, so individual paths won't match call-for-call, but the
distributional summaries (means, quantiles, intervals) converge to the
same limits.
Resamples the model's parameter vector from the multivariate normal
implied by Coefficients-and-Parameters's vcov(), clips each draw
to the admissible region, and re-runs the in-sample ADAM kernel once
per draw. The result is a bundle of nsim refitted paths plus the
per-draw state-space matrices used downstream by reforecast().
Read more in Section 16.5 of Svetunkov (2023).
library(smooth)
model <- adam(AirPassengers, "MMM", lags=12)
refitted <- reapply(model, nsim=100)
# Per-draw refitted matrix and the matching parameter samples
str(refitted$refitted) # T x nsim
str(refitted$randomParameters) # nsim x k
plot(refitted)from smooth import ADAM
import numpy as np
y = AirPassengers # numpy array
model = ADAM(model="MMM", lags=[12]).fit(y)
refitted = model.reapply(nsim=100, seed=42)
refitted.refitted # pd.DataFrame (T, nsim)
refitted.random_parameters # pd.DataFrame (nsim, k); columns = coef_names
refitted.states # np.ndarray (c, T+L, nsim)
refitted.transition # np.ndarray (c, c, nsim)
refitted.measurement # np.ndarray (T, c, nsim)
refitted.persistence # pd.DataFrame (c, nsim)
refitted.profile # np.ndarray (c, L, nsim)| Parameter | Type (R) | Type (Python) | Default (R / Python) | Description |
|---|---|---|---|---|
object |
adam |
ADAM (self) |
– | Fitted ADAM model |
nsim |
integer | int |
1000 / 1000 | Number of parameter draws |
bootstrap |
logical | bool |
FALSE / False | Use Coefficients-and-Parameters's coefbootstrap() for the covariance matrix instead of the inverse Fisher information |
heuristics |
numeric | float |
NULL / None | Heuristic proportion for diagonal-only covariance (`vcov = diag( |
seed |
– | int |
– / None | RNG seed for reproducible draws (Python only — R uses the global RNG) |
... |
– | **vcov_kwargs |
– | Other parameters forwarded to vcov() (step_size, bootstrap kwargs) |
The R object has S3 class "reapply"; the Python object is a
ReapplyResult dataclass. Field names match exactly (modulo R's
camelCase ↔ Python's snake_case convention).
| Field | Type (R) | Type (Python) | Shape | Description |
|---|---|---|---|---|
timeElapsed / time_elapsed
|
numeric | float |
scalar | Wall-clock seconds |
y |
ts | pd.Series |
(T,) | In-sample actuals |
states |
array | np.ndarray |
(c, T+L, nsim) | Refitted state trajectories per draw |
refitted |
ts matrix | pd.DataFrame |
(T, nsim) | Per-draw fitted paths; columns nsim1..nsimN
|
fitted |
ts | pd.Series |
(T,) | Original point-estimate fitted values |
model |
character | str |
scalar | Model spec string |
transition |
array | np.ndarray |
(c, c, nsim) | F matrix per draw |
measurement |
array | np.ndarray |
(T, c, nsim) | W matrix per draw |
persistence |
matrix | pd.DataFrame |
(c, nsim) | Persistence vector per draw |
profile |
array | np.ndarray |
(c, L, nsim) | Final profile per draw |
randomParameters / random_parameters
|
matrix | pd.DataFrame |
(nsim, k) | The sampled-from-MVN parameter matrix |
library(smooth)
model <- adam(AirPassengers, "AAN", h=12, holdout=TRUE)
refitted <- reapply(model, nsim=500)
# Pointwise 95% confidence band on the in-sample fitted line
lower <- apply(refitted$refitted, 1, quantile, probs=0.025)
upper <- apply(refitted$refitted, 1, quantile, probs=0.975)
plot(refitted)from smooth import ADAM
import numpy as np
model = ADAM(model="AAN", lags=[12], h=12, holdout=True).fit(y)
refitted = model.reapply(nsim=500, seed=42)
# Pointwise 95% confidence band on the in-sample fitted line
lower = refitted.refitted.quantile(0.025, axis=1)
upper = refitted.refitted.quantile(0.975, axis=1)Python scope (v1.0.5):
reapply()covers pure-ETS models withbounds="usual"or"none". ARIMA, external regressors, andbounds="admissible"raiseNotImplementedError. R supports all of these.
Uses reapply() to obtain per-draw state-space matrices,
samples future errors from the fitted error distribution, then runs
the C++ kernel forward h steps for every parameter × error
combination. The resulting (h, nsim, nsim) cube is reduced to a
point forecast (trimmed mean across all paths) plus either prediction
or confidence intervals.
| Interval | What it captures | Reduction |
|---|---|---|
"prediction" |
Parameter + future-error uncertainty | Quantile across all nsim*nsim paths per h-step |
"confidence" |
Parameter uncertainty only (line is uncertain, future errors are marginalised) | Mean across error draws first, then quantile across parameter draws |
"none" |
Point forecast only | Trimmed mean |
library(smooth)
model <- adam(AirPassengers, "MMM", lags=12)
# Prediction intervals - full uncertainty
fc <- reforecast(model, h=12, nsim=200, interval="prediction")
plot(fc)
# Confidence intervals - parameter uncertainty only
fc <- reforecast(model, h=12, nsim=200, interval="confidence")from smooth import ADAM
model = ADAM(model="MMM", lags=[12]).fit(y)
# Prediction intervals - full uncertainty
fc = model.reforecast(h=12, nsim=200, interval="prediction", seed=42)
fc.mean # pd.Series, length h
fc.lower # pd.DataFrame (h, n_levels)
fc.upper # pd.DataFrame (h, n_levels)
fc.paths # np.ndarray (h, nsim, nsim) — the full path cube
# Confidence intervals - parameter uncertainty only
fc = model.reforecast(h=12, nsim=200, interval="confidence", seed=42)| Parameter | Type (R) | Type (Python) | Default (R / Python) | Description |
|---|---|---|---|---|
object |
adam |
ADAM (self) |
– | Fitted ADAM model |
h |
integer | int |
10 / 10 | Forecast horizon |
newdata / X
|
matrix/data.frame | NDArray |
NULL / None | Future xreg values (Python: NotImplementedError for now) |
occurrence |
numeric | NDArray |
NULL / None | Future occurrence probabilities |
interval |
character | str |
"prediction" / "prediction"
|
"prediction", "confidence", or "none"
|
level |
numeric or vector |
float or list[float]
|
0.95 / 0.95 | Confidence level(s); values above 1 are interpreted as percentages |
side |
character | str |
"both" / "both"
|
"both", "upper", or "lower"
|
cumulative |
logical | bool |
FALSE / False | Sum the horizon into a single value |
nsim |
integer | int |
100 / 100 | Number of parameter draws; the cube is (h, nsim, nsim)
|
bootstrap |
logical | bool |
FALSE / False | Forwarded to reapply() / vcov()
|
heuristics |
numeric | float |
NULL / None | Forwarded to reapply() / vcov()
|
trim |
numeric | float |
0.01 / 0.01 | Trim proportion for the point-forecast mean |
seed |
– | int |
– / None | RNG seed for reproducible draws |
... |
– | **vcov_kwargs |
– | Other parameters forwarded to reapply()
|
The R object has S3 classes c("adam.forecast", "smooth.forecast", "forecast"); the Python object is a ReforecastResult dataclass with
a to_forecast_result() helper that projects onto the standard
ForecastResult shape used elsewhere in
Fitted-Values-and-Forecasts.
| Field | Type (R) | Type (Python) | Shape | Description |
|---|---|---|---|---|
mean |
ts | pd.Series |
(h,) or (1,) | Point forecast |
lower |
matrix |
pd.DataFrame | None
|
(h, n_levels) | Lower interval bounds; column labels match R (Lower bound (2.5%)) |
upper |
matrix |
pd.DataFrame | None
|
(h, n_levels) | Upper interval bounds |
level |
numeric | list[float] |
– | Confidence levels |
interval |
character | str |
– |
"prediction", "confidence", or "none"
|
side |
character | str |
– |
"both", "upper", or "lower"
|
cumulative |
logical | bool |
– | Whether the result is cumulative |
h |
integer | int |
– | Horizon |
paths |
array |
np.ndarray | None
|
(h, nsim, nsim) | Full path cube (R: $paths); None when h<=0
|
model |
adam object | str |
– | Model spec (R returns the original object; Python returns its string form) |
library(smooth)
model <- adam(AirPassengers, "MMM", lags=12, h=12, holdout=TRUE)
# Cumulative 12-step forecast with full uncertainty
fc <- reforecast(model, h=12, nsim=500,
interval="prediction", cumulative=TRUE)
fc$mean # single value: expected sum over the 12-step horizon
fc$lower # single quantile
fc$upper
# Multi-level intervals
fc <- reforecast(model, h=12, nsim=500, interval="prediction",
level=c(0.8, 0.95))from smooth import ADAM
model = ADAM(model="MMM", lags=[12], h=12, holdout=True).fit(y)
# Cumulative 12-step forecast with full uncertainty
fc = model.reforecast(
h=12, nsim=500, interval="prediction", cumulative=True, seed=42,
)
fc.mean # length-1 series
fc.lower
fc.upper
# Multi-level intervals
fc = model.reforecast(
h=12, nsim=500, interval="prediction", level=[0.8, 0.95], seed=42,
)
fc.lower # pd.DataFrame columns: "Lower bound (10%)", "Lower bound (2.5%)"
fc.upper # pd.DataFrame columns: "Upper bound (90%)", "Upper bound (97.5%)"Python scope (v1.0.5): Same as
reapply()— pure-ETS only. Occurrence models and external regressors raiseNotImplementedError.
predict() (Python) / forecast() (R) accepts two aliases that
delegate straight to reforecast() so the standard call site keeps
working:
-
interval="complete"→reforecast(interval="prediction")— parameter + future-error uncertainty. -
interval="confidence"→reforecast(interval="confidence")— parameter uncertainty only.
Both default to nsim=100 when the caller leaves nsim at the
predict() default (10000 in Python, NULL in R). The returned
object is the standard forecast.smooth / ForecastResult —
identical to other interval= modes — so plotting and accuracy code
doesn't need to special-case these branches.
library(smooth)
model <- adam(AirPassengers, "MMM", lags=12)
fc <- forecast(model, h=12, interval="complete") # full uncertainty
fc <- forecast(model, h=12, interval="confidence") # parameter onlyfrom smooth import ADAM
model = ADAM(model="MMM", lags=[12]).fit(y)
fc = model.predict(h=12, interval="complete") # full uncertainty
fc = model.predict(h=12, interval="confidence") # parameter only
# Same ForecastResult shape as other interval modes:
fc.mean # pd.Series
fc.lower # pd.DataFrame (h, n_levels) — columns labelled like
# R's "Lower bound (2.5%)" etc.
fc.upper
fc.interval # "prediction" or "confidence" (the underlying
# reforecast mode, mirroring R's behaviour)| Method | Parameter Uncertainty | Future Uncertainty | Speed |
|---|---|---|---|
predict(interval="approximate") (Python) / forecast(interval="parametric") (R) |
No | Yes (analytical) | Fast |
predict(interval="simulated") / forecast(interval="simulated")
|
No | Yes (Monte Carlo) | Moderate |
predict(interval="complete") / forecast(interval="complete") (= reforecast(..., interval="prediction")) |
Yes | Yes | Slow (≈ nsim² paths) |
predict(interval="confidence") / forecast(interval="confidence") (= reforecast(..., interval="confidence")) |
Yes | Marginalised | Slow |
Use interval="complete" (or reforecast() directly) when:
- Parameter uncertainty is non-negligible (small samples, complex models, or parameters close to bounds)
- You want calibrated prediction intervals that don't assume the point estimate is the true parameter vector
- Computational time is not a constraint (
nsim²C++ kernel calls per horizon step)
- ADAM — Main ADAM function
-
Fitted-Values-and-Forecasts — Standard forecast methods and the
full table of
interval=options -
Coefficients-and-Parameters —
vcov(),coefbootstrap()(the covariance sources sampled byreapply()) - Simulation-Functions — Pure simulation (no parameter resampling)
- Scale-Model — Heteroscedasticity (separate from parameter uncertainty)