-
Notifications
You must be signed in to change notification settings - Fork 22
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).
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).
| 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 | Yes | 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.
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 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)| 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 |
# 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)")For simpler cases, pass a regressor matrix directly — no formula parsing needed.
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
newdataparameter inforecast()
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; withholdout=True, lasthrows of X are the holdout regressors -
len(X) > len(y)— silently trimmed tolen(y) -
len(X) < len(y)— last row repeated to fill, with aUserWarning
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 |
model <- adam(data, model="AAN", regressors="use")
coef(model)model = ADAM(model="AAN", regressors="use")
model.fit(y, X)
print(model.coef) # includes xreg coefficientsAutomatically selects which regressors to retain using a stepwise procedure (stepwise() function from the greybox package) based on an information criterion.
model <- adam(data, model="AAN", regressors="select")
# Check which variables were retained
coef(model)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"])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).
model <- adam(data, model="AAN", formula=y ~ x1 + x2, regressors="adapt")model = ADAM(model="AAN", regressors="adapt")
model.fit(y, X)
# The state matrix includes one trajectory per regressor
print(model.states.shape) # (n_ets + n_xreg, T+1)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 |
# Provide initial values for regressor coefficients
model <- adam(data, model="AAN", formula=y ~ x1 + x2,
initial=list(xreg=c(0.5, -0.3)))# 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.
Future values of the regressors must be provided for the forecast horizon.
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)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().
| 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 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.
-
Variable selection: Start with
regressors="select"if unsure which variables are useful. -
Adaptive coefficients: Use
regressors="adapt"when relationships might change over time. - Future values: Ensure you have reliable forecasts or known values for regressors over the forecast horizon.
- Multicollinearity: Check for correlation among regressors; selection can help here.
-
Formula (Python): Always drop the
"(Intercept)"column returned bygreybox.formula()before passingXto smooth — the model level state already serves as the intercept.
- 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.
- ADAM - Main ADAM function
- Loss-Functions - Including LASSO/RIDGE regularization
- Initialisation - Setting initial values for regressors
- Model-Information - Extracting model components