Skip to content

Residuals and Errors

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

Residuals, Forecast Errors, and Outliers

This page documents methods for extracting residuals, analysing forecast errors, and handling outliers in smooth models.

Note: In Python, residuals, rstandard, rstudent, outlierdummy, rmultistep, and multicov are all available.

Residuals

residuals()

Extracts residuals from the fitted model.

R Usage

model <- adam(AirPassengers, "MMM", lags=12, h=12, holdout=TRUE)

# Get residuals
res <- residuals(model)

# Plot residuals
plot(res)

Python Usage

from smooth import ADAM

model = ADAM(model="MMM", lags=12)
model.fit(y)

# Get residuals
res = model.residuals

Residual Types

For 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.

Output

R: Returns a time series object with residuals for each in-sample observation.

Python: Returns an NDArray with residuals for each in-sample observation.

rstandard()

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 / ē

Python Usage

from smooth import ADAM

model = ADAM(model="MMM", lags=[12])
model.fit(y)

std_res = model.rstandard()

R Usage

model <- adam(AirPassengers, "MMM", lags=12)

stdres <- rstandard(model)

# Check the distribution
hist(stdres, freq=FALSE)
curve(dnorm(x), add=TRUE, col="red")

Output

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

rstudent()

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.

Python Usage

from smooth import ADAM

model = ADAM(model="MMM", lags=[12])
model.fit(y)

stu_res = model.rstudent()

R Usage

model <- adam(AirPassengers, "AAN")

studres <- rstudent(model)

Output

R: Returns a time series object of studentised residuals.

Python: Returns an NDArray of studentised residuals, length nobs.

Note

Studentised residuals are more appropriate for outlier detection in small samples because no single observation inflates the global scale estimate it is judged against.

Multi-step Forecast Errors

Python: both rmultistep and multicov are available as methods on the fitted ADAM object (and inherited by ES / MSARIMA / OM).

rmultistep()

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.

R Usage

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

Python Usage

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

Output

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

multicov()

Computes the covariance matrix of 1 to h steps ahead forecast errors.

R Usage

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)

Python Usage

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)))  # correlation

Inherited 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.

Parameters

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

Output

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 Cases

# Use for optimal combination of forecasts
fcov <- multicov(model, h=12)

# Diagonal = variances at each horizon
sqrt(diag(fcov))

# Check correlation structure
cov2cor(fcov)

Differences from R

  • type="simulated" on OM raises 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.

Outliers and Special Handling

Python: outlierdummy, rstandard, and rstudent are available. Outlier-based fitting via outliers="use" and outliers="select" is available in both ADAM and AutoADAM.

outlierdummy()

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.

Python Usage

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)

R Usage

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

Parameters

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"

Output

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

Integrated Outlier Handling in ADAM and AutoADAM

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) and regressors="select" lets the model choose which temporal offset matters.

Python Usage

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)

R Usage

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

Manual Outlier Handling

The automatic outliers= pipeline is equivalent to this manual sequence:

Python

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)

R

# 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)
}

References

  • Svetunkov, I. (2023). Forecasting and Analytics with the Augmented Dynamic Adaptive Model (ADAM). Chapman and Hall/CRC. Online: https://openforecast.org/adam/.

See Also

Clone this wiki locally