Skip to content

Refitting and Reforecasting

Ivan Svetunkov edited this page Jun 16, 2026 · 7 revisions

title: Refitting and Reforecasting Methods slug: Refitting-and-Reforecasting summary: reapply() and reforecast() — propagate parameter uncertainty into refit and reforecast. status: stable applies_to: [all] related: [ADAM, Coefficients-and-Parameters, Fitted-Values-and-Forecasts]

Refitting and Reforecasting Methods

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.

reapply()

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().

R Usage

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)

Python Usage

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)

Parameters

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)

Output

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

Example

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)

Plotting the refitted matrix

Both languages render a fan-chart of the refitted paths: five nested quantile bands (95% / 80% / 60% / 40% / 20%) shaded outermost-lightest to innermost-darkest, with the actuals overlaid as a thin black line and the original point-estimate fitted values as a dashed purple line.

library(smooth)

model <- adam(AirPassengers, "AAN", h=12, holdout=TRUE)
refitted <- reapply(model, nsim=500)
plot(refitted)
from smooth import ADAM

model = ADAM(model="AAN", lags=[12], h=12, holdout=True).fit(y)
refitted = model.reapply(nsim=500, seed=42)

fig = refitted.plot()              # returns a matplotlib Figure

# Or render on a specific Axes with custom kwargs
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 4))
refitted.plot(ax=ax, title="Custom title", legend=True)
Kwarg Default Effect
ax None Reuse an existing matplotlib Axes; otherwise a new figure is created
figsize (10, 5) Forwarded to plt.subplots when ax is None
title "Refitted values of {model}" Override the plot title
ylim data range (low, high) y-limits
ylabel "" Y-axis label
legend False Add a legend for the actuals/fitted lines

reforecast()

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

R Usage

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")

Python Usage

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)

Parameters

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()

Output

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)

Example

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 raise NotImplementedError.

predict(interval="complete" | "confidence")

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.

R Usage

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 only

Python Usage

from 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)

Comparison with Standard Intervals

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)

References

See Also

Clone this wiki locally