-
Notifications
You must be signed in to change notification settings - Fork 22
Residuals and Errors
This page documents methods for extracting residuals, analysing forecast errors, and handling outliers in smooth models.
Note: In Python,
residuals,rstandard,rstudent,outlierdummy,rmultistep, andmulticovare all available.
Extracts residuals from the fitted model.
model <- adam(AirPassengers, "MMM", lags=12, h=12, holdout=TRUE)
# Get residuals
res <- residuals(model)
# Plot residuals
plot(res)from smooth import ADAM
model = ADAM(model="MMM", lags=12)
model.fit(y)
# Get residuals
res = model.residualsFor additive error models (E = "A"):
e_t = y_t - ŷ_t
For multiplicative error models (E = "M"):
e_t = (y_t - ŷ_t) / ŷ_t
This extracts the specific value aligning with the model.
R: Returns a time series object with residuals for each in-sample observation.
Python: Returns an NDArray with residuals for each in-sample observation.
Returns standardised residuals. The scaling formula is distribution-specific and corrects for degrees of freedom df = nobs - nparam:
| Distribution | Formula |
|---|---|
Normal (dnorm) |
(e − ē) / (σ √(n/df)) |
Laplace (dlaplace) |
e / σ · n/df |
S (ds) |
(e − ē) / (σ · n/df)² |
Gen. Normal (dgnorm) |
(e − ē) / (σ^β · n/df)^(1/β) |
Log-Normal (dlnorm) |
exp((log(e) + σ²/2 − mean(·)) / (σ √(n/df))) |
| Inv. Gaussian / Gamma | e / ē |
from smooth import ADAM
model = ADAM(model="MMM", lags=[12])
model.fit(y)
std_res = model.rstandard()model <- adam(AirPassengers, "MMM", lags=12)
stdres <- rstandard(model)
# Check the distribution
hist(stdres, freq=FALSE)
curve(dnorm(x), add=TRUE, col="red")R: Returns a time series object of standardised residuals.
Python: Returns an NDArray of standardised residuals, length nobs.
For a correctly specified model, the result is approximately distributed as the standardised version of the fitted distribution (e.g. approximately N(0, 1) for dnorm).
Returns studentised (leave-one-out) residuals. Each residual is scaled by the distribution-specific scale estimate recomputed without that observation, making it more sensitive to individual outliers.
from smooth import ADAM
model = ADAM(model="MMM", lags=[12])
model.fit(y)
stu_res = model.rstudent()model <- adam(AirPassengers, "AAN")
studres <- rstudent(model)R: Returns a time series object of studentised residuals.
Python: Returns an NDArray of studentised residuals, length nobs.
Studentised residuals are more appropriate for outlier detection in small samples because no single observation inflates the global scale estimate it is judged against.
Python: both
rmultistepandmulticovare available as methods on the fitted ADAM object (and inherited byES/MSARIMA/OM).
Extracts 1 to h steps ahead forecast errors from the model. Useful for analysing how forecast uncertainty changes with horizon. Typically, the variance of forecast errors increases with the increase of the horizon.
model <- adam(AirPassengers, "MMM", lags=12, h=12, holdout=TRUE)
# Get multi-step errors (requires holdout)
mserrors <- rmultistep(model)
# Matrix: rows = time, columns = horizons 1 to h
dim(mserrors)
# Plot errors per horizon
boxplot(mserrors, type="b", xlab="Horizon", ylab="Errors")from smooth import ADAM
model = ADAM(model="AAN", lags=[1, 12])
model.fit(y)
errors = model.rmultistep(h=12) # pd.DataFrame, shape (T-12, 12)
errors.shape # (132, 12) for AirPassengers (T=144)
# Variance grows with horizon (typical for AAN):
errors.std()Returns a matrix with:
- Rows: In-sample observations
- Columns: Forecast horizons (1, 2, ..., h)
Each cell contains the h-step ahead forecast error made at that time point.
R: Returns a time series or zoo object.
Python: Returns a pd.DataFrame of shape (T-h, h) with column names "h=1", "h=2", …, "h=h".
Computes the covariance matrix of 1 to h steps ahead forecast errors.
model <- adam(AirPassengers, "MMM", lags=12, h=12, holdout=TRUE)
# Covariance matrix of forecast errors
fcov <- multicov(model, h=12)
# Methods: "analytical", "empirical", "simulated"
fcov_emp <- multicov(model, h=12, type="empirical")
fcov_sim <- multicov(model, h=12, type="simulated", nsim=1000)from smooth import ADAM
model = ADAM(model="AAN", lags=[1, 12]).fit(y)
# Analytical (closed-form via the state-space matrices)
fcov = model.multicov(h=12)
# Empirical (rolling-origin residuals cross-product / (nobs - h))
fcov_emp = model.multicov(type="empirical", h=12)
# Simulated (averaged over `nsim` simulator paths)
fcov_sim = model.multicov(type="simulated", h=12, nsim=1000)
fcov.shape # (12, 12) — pd.DataFrame, labelled h1..h12
import numpy as np
np.sqrt(np.diag(fcov)) # per-horizon std-dev
np.diag(fcov) / np.outer(np.sqrt(np.diag(fcov)),
np.sqrt(np.diag(fcov))) # correlationInherited by ES, MSARIMA, and OM (OM's analytical path uses
sigma = sqrt(mean(residuals²)) on the link-transformed scale).
OMG.multicov() raises and points to model.model_a.multicov() /
model.model_b.multicov() — the joint two-sub-model distribution has
no closed-form covariance from the per-sub-model state-space matrices.
| Parameter | Type (R) | Type (Python) | Default | Description |
|---|---|---|---|---|
object |
adam | self |
- | Fitted model |
type |
character | str |
"analytical" |
"analytical", "empirical", or "simulated"
|
h |
integer | int |
10 | Maximum forecast horizon |
nsim |
integer | int |
1000 | Simulations (used when type="simulated") |
Returns an h × h covariance matrix where element (i,j) is the covariance
between i-step and j-step ahead forecast errors. R returns a base matrix;
Python returns a pandas.DataFrame labelled h1..hh along both axes.
# Use for optimal combination of forecasts
fcov <- multicov(model, h=12)
# Diagonal = variances at each horizon
sqrt(diag(fcov))
# Check correlation structure
cov2cor(fcov)-
type="simulated"onOMraises in Python — the occurrence-aware predict route does not yet populate the scenarios matrix the simulated branch reads from. The analytical and empirical paths both work on OM.
Python:
outlierdummy,rstandard, andrstudentare available. Outlier-based fitting viaoutliers="use"andoutliers="select"is available in bothADAMandAutoADAM.
Detects outliers by standardising residuals and flagging observations that fall outside the distribution-specific quantile bounds at the given confidence level. Returns a matrix of 0/1 dummy variables — one column per outlier — suitable for use as exogenous regressors in a refitted model.
from smooth import ADAM
model = ADAM(model="MMM", lags=[12])
model.fit(y)
# Detect outliers at 99% level (default type="rstandard")
od = model.outlierdummy(level=0.99)
od.id # 0-based indices of outlier observations
od.statistic # [lower, upper] quantile bounds
od.outliers # (n, k) ndarray of dummy variables, or None
# Use rstudent for more sensitive detection
od2 = model.outlierdummy(level=0.99, type="rstudent")
# Refit with outlier dummies as exogenous regressors
if od.outliers is not None:
model2 = ADAM(model="MMM", lags=[12])
model2.fit(y, X=od.outliers)model <- adam(AirPassengers, "MMM", lags=12)
# Detect outliers at 99% level
outliers <- outlierdummy(model, level=0.99)
# View detected outliers
outliers$id # Indices of outliers
outliers$outliers # Dummy matrix| Parameter | Type (R) | Type (Python) | Default | Description |
|---|---|---|---|---|
object |
adam | — | — | Fitted model (R: first arg; Python: the instance) |
level |
numeric | float | 0.999 | Two-sided confidence level for detection |
type |
character | str | "rstandard" | Residual type: "rstandard" or "rstudent"
|
R: Returns a list with id, outliers (dummy matrix), statistic, level, type.
Python: Returns an OutlierDummy dataclass with the same fields:
| Field | Type | Description |
|---|---|---|
outliers |
ndarray (n, m) or None | 0/1 dummy matrix, one column per outlier |
id |
ndarray of int | 0-based indices of outlier observations |
statistic |
ndarray (2,) |
[lower, upper] quantile bounds |
level |
float | Confidence level used |
type |
str | Residual type used |
Both ADAM and AutoADAM support the outliers parameter directly. After fitting the
model, outlier detection runs automatically and — if outliers are found — the model is
refit with the detected dummies appended to X.
-
outliers="use": detected dummies are included as fixed regressors (regressors="use"). -
outliers="select": each dummy is expanded into three columns (lag −1, t, lead +1) andregressors="select"lets the model choose which temporal offset matters.
from smooth import ADAM, AutoADAM
# ADAM: automatic outlier handling
model = ADAM(model="ZZZ", lags=[1, 12],
outliers="use", outliers_level=0.99)
model.fit(y)
# AutoADAM: distribution selection + outlier handling
model = AutoADAM(model="ZZZ", lags=[1, 12],
outliers="select", level=0.99)
model.fit(y)# Detect and use all outliers as dummies
model <- auto.adam(AirPassengers, "ZZZ",
outliers="use",
level=0.99,
h=12, holdout=TRUE)
# Detect outliers, create leads/lags, select significant ones
model <- auto.adam(AirPassengers, "ZZZ",
outliers="select",
level=0.99,
h=12, holdout=TRUE)The automatic outliers= pipeline is equivalent to this manual sequence:
from smooth import ADAM
# Step 1: Fit initial model
model1 = ADAM(model="ZZZ", lags=[1, 12])
model1.fit(y)
# Step 2: Detect outliers
od = model1.outlierdummy(level=0.99)
# Step 3: If outliers found, refit with dummies
if od.outliers is not None:
model2 = ADAM(model="ZZZ", lags=[1, 12], regressors="use")
model2.fit(y, X=od.outliers)# Step 1: Fit initial model
model1 <- adam(y, "ZZZ", lags=12)
# Step 2: Detect outliers
outliers <- outlierdummy(model1, level=0.99)
# Step 3: If outliers found, refit with dummies
# This is what outliers="use" does in auto.adam().
if(length(outliers$id) > 0) {
# Create data with outlier dummies
newdata <- cbind(y, outliers$outliers)
# Refit model
model2 <- adam(newdata, "ZZZ", lags=12)
}- Svetunkov, I. (2023). Forecasting and Analytics with the Augmented Dynamic Adaptive Model (ADAM). Chapman and Hall/CRC. Online: https://openforecast.org/adam/.
- ADAM - Main ADAM function
- AutoADAM - Automatic model selection with outlier handling
- Visualisation-and-Output - Residual plots
- Likelihood-and-Information-Criteria - Model comparison after outlier treatment