Skip to content

Explanatory Variables

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

Explanatory Variables

This page documents how to include external regressors (explanatory variables) in smooth models.

Read more about the implementation in ADAM and different options in Chapter 10 of Svetunkov (2023).

Overview

Smooth models support explanatory variables (exogenous regressors) that can improve forecasts when external factors influence the time series. This creates models like ETSX (ETS with regressors) or ARIMAX (ARIMA with regressors).

Parameters for Explanatory Variables

Parameter R Python Description
formula Yes — (use formula() from greybox) Formula for variable relationships
xreg / X Yes Yes (X arg to fit()) External regressors matrix
regressors Yes Partial ("use" / "select"; "adapt" not yet enforced — see note below) How to handle regressors: "use", "select", "adapt"

Note: In R, formula is not a compulsory argument and can be skipped if no variables transformation is required and the response variable is in the first column.

Using formula

R

The formula parameter uses R's formula syntax to specify the response and regressors. This is the primary interface for adam().

# Data with explanatory variables
data <- cbind(y, x1, x2, x3)

# Use a subset of variables
model <- adam(data, model="AAN", formula=y ~ x1 + x2)

# Include a deterministic trend (auto-generated, no need to have it in the data)
model <- adam(data, model="AAN", formula=y ~ x1 + x2 + trend)

# All variables plus trend
model <- adam(data, model="AAN", formula=y ~ . + trend)

# All variables except x3
model <- adam(data, model="AAN", formula=y ~ . - x3)

Python

Python has no native formula interface, but the greybox package provides a formula() function that parses R-style formula strings against a pandas DataFrame and returns (y, X).

Important: formula() includes an intercept column "(Intercept)" in X. Because smooth ADAM already captures the intercept through its level state, drop this column before calling fit().

import pandas as pd
from greybox import formula
from smooth import ADAM

# Build a DataFrame with the response and regressors
df = pd.DataFrame({"y": y, "x1": x1, "x2": x2})

# Parse formula — returns (y Series, X DataFrame with intercept column)
y_vec, X_mat = formula("y ~ x1 + x2", data=df)
X_mat = X_mat.drop(columns="(Intercept)")   # drop intercept; ADAM handles it via level

model = ADAM(model="AAN", regressors="use")
model.fit(y_vec, X_mat)

Special Terms (R/Python)

Term Description
trend Adds a deterministic trend column (1, 2, …, n) — auto-generated if absent from data
. All columns in the data except the response
-1 Remove intercept
log(x), sqrt(x) In-formula transformations of regressors
log(y) Transform the response variable
x^2 or I(x^2) Polynomial / protected arithmetic terms

Examples in Python

# Add a deterministic trend as an extra regressor
y_vec, X_mat = formula("y ~ x1 + x2 + trend", data=df)
X_mat = X_mat.drop(columns="(Intercept)")

# Log-transform a regressor
y_vec, X_mat = formula("y ~ log(x1) + x2", data=df)
X_mat = X_mat.drop(columns="(Intercept)")

# Polynomial regressor
y_vec, X_mat = formula("y ~ x1 + x1^2", data=df)
X_mat = X_mat.drop(columns="(Intercept)")

Using X matrix

For simpler cases, pass a regressor matrix directly — no formula parsing needed.

R

xreg is supported in es(), ssarima(), msarima(), ces(), and gum().

# Create regressor matrix
X <- cbind(promo=promo_data, holiday=holiday_dummy)

# ETS with regressors
model <- es(y, model="AAN", xreg=X)

# ARIMA with regressors
model <- ssarima(y, orders=c(1,1,1), xreg=X)

In ADAM, the formula can be skipped, which will tell the function to use all variables as is:

model <- adam(xreg, model="ANN")

Notes:

  • Columns are variables, rows are observations
  • Row count should match the in-sample length (or full series length including holdout)
  • For forecasting, provide new values via the newdata parameter in forecast()

Python

Pass a NumPy array or pandas DataFrame as X to fit(). Column names from a DataFrame are preserved as regressor names.

import numpy as np
import pandas as pd
from smooth import ADAM

# Pass X as a NumPy array
X = np.column_stack([promo_data, holiday_dummy])
model = ADAM(model="AAN", regressors="use")
model.fit(y, X)

# Pass X as a DataFrame — column names are preserved
X_df = pd.DataFrame({"promo": promo_data, "holiday": holiday_dummy})
model.fit(y, X_df)
print(model._explanatory["xreg_names"])   # ['promo', 'holiday']

Row count rules:

  • len(X) == len(y) — normal usage; with holdout=True, last h rows of X are the holdout regressors
  • len(X) > len(y) — silently trimmed to len(y)
  • len(X) < len(y) — last row repeated to fill, with a UserWarning

The regressors Parameter

The regressors parameter controls how explanatory variables are treated:

Value Description Use case
"use" Use all provided regressors with fixed coefficients Confident in your variable selection
"select" Stepwise selection based on an information criterion Unsure which variables are useful
"adapt" Time-varying coefficients updated via ETS-like smoothing Relationships change over time

regressors="use"

R

model <- adam(data, model="AAN", regressors="use")
coef(model)

Python

model = ADAM(model="AAN", regressors="use")
model.fit(y, X)
print(model.coef)   # includes xreg coefficients

regressors="select"

Automatically selects which regressors to retain using a stepwise procedure (stepwise() function from the greybox package) based on an information criterion.

R

model <- adam(data, model="AAN", regressors="select")
# Check which variables were retained
coef(model)

Python

model = ADAM(model="AAN", regressors="select")
model.fit(y, X)    # X may have more columns than needed
# Check which variables were kept
print(model._explanatory["xreg_names"])
print(model._explanatory["xreg_number"])

regressors="adapt"

Regressor coefficients are updated over time using a smoothing mechanism similar to ETS states. Useful when the effect of a regressor changes (e.g., promotional effect changes over time).

Python: not yet wired up. The regressors="adapt" path is on the Python ADAM API surface, but the cost-function bounds check that R applies to the regressor persistence (vec_g[components_ets + components_arima + 1:xreg_number] must lie in [0, 1]) is not yet implemented in python/src/smooth/adam_general/core/utils/cost_functions.py (the equivalent branch is commented out around lines 498–515). Until that guard is added, fitting an ADAMX model in Python with regressors="adapt" can let the regressor persistence drift outside [0, 1] without penalty, giving silently-wrong fits relative to the R implementation. Track for fix; use regressors="use" or regressors="select" in the meantime.

R

model <- adam(data, model="AAN", formula=y ~ x1 + x2, regressors="adapt")

Python

# regressors="adapt" is not yet enforced in Python — see note above.
# Until the bounds check lands, prefer "use" or "select".
model = ADAM(model="AAN", regressors="use")
model.fit(y, X)

Initial Values

There are several options for initialisation of models:

initial xreg in B? Notes
"backcasting" (default) Yes xreg coefficients optimised alongside α, β
"optimal" Yes ETS states also optimised
"two-stage" Yes Two-stage optimisation
"complete" No Complete backcasting, only smoothing parameters are estimated

R

# Provide initial values for regressor coefficients
model <- adam(data, model="AAN", formula=y ~ x1 + x2,
              initial=list(xreg=c(0.5, -0.3)))

Python

# Provide initial values for regressor coefficients
model = ADAM(model="AAN", regressors="use",
             initial={"xreg": [0.5, -0.3]})
model.fit(y, X)

When initial["xreg"] is not supplied, initial coefficients are estimated automatically via greybox.ALM using the in-sample data. The "xreg" key can be combined with other keys:

# Fix level and seed xreg initials
model = ADAM(model="AAN", regressors="use",
             initial={"level": 10.0, "xreg": [0.5, -0.3]})
model.fit(y, X)

The length of initial["xreg"] must match the number of regressors; a ValueError is raised otherwise. All four initialisation modes ("backcasting", "optimal", "two-stage", "complete") work correctly with regressors.

Forecasting with Explanatory Variables

Future values of the regressors must be provided for the forecast horizon.

R

model <- adam(data, model="AAN", formula=y ~ x1 + x2)

new_data <- data.frame(x1=new_x1, x2=new_x2)
fc <- forecast(model, h=12, newdata=new_data)

Python

model = ADAM(model="AAN", regressors="use")
model.fit(y, X)

# Forecast with known or projected future regressor values
X_future = pd.DataFrame({"x1": new_x1, "x2": new_x2})   # shape (h, p)
fc = model.predict(h=12, X=X_future)
print(fc.mean)

If X was a NumPy array during fit(), pass a NumPy array of shape (h, p) to predict().

Support by Function

Function formula xreg / X regressors Python
ADAM Yes ("use", "select", "adapt") Yes (X arg)
ES Yes ("use", "select") TBA
SSARIMA Yes ("use", "select", "adapt") TBA
MSARIMA Yes ("use", "select", "adapt") TBA
CES Yes ("use", "select") TBA
GUM Yes ("use", "select", "adapt", "integrate") TBA

GUM Special Option

GUM supports an additional regressors="integrate" option, which applies integrated updating to the explanatory variable parameters. In that case, the transition matrix for the regressors will be estimated.

Best Practices

  1. Variable selection: Start with regressors="select" if unsure which variables are useful.
  2. Adaptive coefficients: Use regressors="adapt" when relationships might change over time.
  3. Future values: Ensure you have reliable forecasts or known values for regressors over the forecast horizon.
  4. Multicollinearity: Check for correlation among regressors; selection can help here.
  5. Formula (Python): Always drop the "(Intercept)" column returned by greybox.formula() before passing X to smooth — the model level state already serves as the intercept.

References

  • Svetunkov, I. (2023). Forecasting and Analytics with the Augmented Dynamic Adaptive Model (ADAM). Online book: https://openforecast.org/adam/
  • Hyndman, R.J., et al. (2008). Forecasting with Exponential Smoothing: The State Space Approach. Springer.

See Also

Clone this wiki locally