-
Notifications
You must be signed in to change notification settings - Fork 22
CES
title: CES — Complex Exponential Smoothing slug: CES summary: Alternative to ETS using complex-valued smoothing parameters. status: stable applies_to: [r, python] related: [ADAM, Persistence]
CES (Complex Exponential Smoothing) is an alternative to ETS that uses complex-valued smoothing parameters. It captures both the level and the "potential" of the time series, making it particularly useful for series with complex seasonal patterns or smooth trends without abrupt changes.
CES is available in both R (ces() / auto.ces()) and Python (CES / AutoCES). The Python port is a direct translation of the R algorithm and produces numerically equivalent results when the same NLopt version (nlopt>=2.10.0) is used on both sides.
ces(y, seasonality = c("none", "simple", "partial", "full"),
lags = c(frequency(y)),
initial = c("backcasting", "optimal", "two-stage", "complete"),
a = NULL, b = NULL,
loss = c("likelihood", "MSE", "MAE", "HAM",
"MSEh", "TMSE", "GTMSE", "MSCE", "GPL"),
h = 0, holdout = FALSE,
bounds = c("admissible", "none"),
silent = TRUE,
model = NULL,
xreg = NULL, regressors = c("use", "select", "adapt"),
initialX = NULL, ...)
auto.ces(y, seasonality = c("none", "simple", "partial", "full"),
lags = c(frequency(y)),
initial = c("backcasting", "optimal", "two-stage", "complete"),
ic = c("AICc", "AIC", "BIC", "BICc"),
loss = c("likelihood", "MSE", "MAE", "HAM",
"MSEh", "TMSE", "GTMSE", "MSCE", "GPL"),
h = 0, holdout = FALSE,
bounds = c("admissible", "none"),
silent = TRUE, xreg = NULL,
regressors = c("use", "select", "adapt"), ...)class CES:
def __init__(
self,
seasonality: Literal["none", "simple", "partial", "full"] = "none",
lags: list[int] | None = None,
initial: str = "backcasting",
a: complex | None = None,
b: complex | float | None = None,
loss: str = "likelihood",
h: int | None = None,
holdout: bool = False,
bounds: Literal["admissible", "none"] = "admissible",
ic: Literal["AIC", "AICc", "BIC", "BICc"] = "AICc",
verbose: int = 0,
regressors: Literal["use", "select"] = "use",
algorithm0: str = "NLOPT_LN_BOBYQA",
algorithm: str = "NLOPT_LN_NELDERMEAD",
# ... NLopt tolerances (maxeval, maxtime, xtol/ftol pairs for both stages)
) -> None: ...
class AutoCES:
def __init__(
self,
seasonality: list[str] | None = None,
lags: list[int] | None = None,
initial: str = "backcasting",
ic: Literal["AIC", "AICc", "BIC", "BICc"] = "AICc",
loss: str = "likelihood",
h: int | None = None,
holdout: bool = False,
bounds: Literal["admissible", "none"] = "admissible",
verbose: int = 0,
**kwargs,
) -> None: ...Unlike traditional ETS models that require explicit trend specification, CES uses complex numbers to automatically capture both stationary and non-stationary behaviour. The model estimates complex smoothing parameters a (and b, for partial and full seasonality) that determine the dynamics. The transition is driven in companion-matrix form by the real and imaginary parts of those parameters, so a single complex coefficient encodes both the speed of level adaptation and the "momentum" carried into the next period.
| Type | Code | Description |
|---|---|---|
| None | "none" |
No seasonality, simple CES |
| Simple | "simple" |
Lagged CES (uses t-m observation) |
| Partial | "partial" |
Real seasonal component (additive-like) |
| Full | "full" |
Complex seasonal component (adaptive) |
from smooth import CES, AutoCES
import numpy as np
y = np.array([112, 118, 132, 129, 121, 135, 148, 148, 136, 119, 104, 118,
115, 126, 141, 135, 125, 149, 170, 170, 158, 133, 114, 140])
# Simple CES (no seasonality)
model = CES(seasonality="none", h=8, holdout=True)
model.fit(y)
fc = model.predict(h=8)
# CES with simple seasonality
model = CES(seasonality="simple", lags=[1, 12], h=18, holdout=True)
model.fit(y)
print(f"a = {model.a_}") # complex smoothing parameter
print(model.model_name) # e.g. "CES(simple)"
# CES with partial seasonality (real seasonal component)
model = CES(seasonality="partial", lags=[1, 12], h=18, holdout=True)
model.fit(y)
print(f"b = {model.b_}") # real-valued for partial
# CES with full seasonality (complex seasonal component)
model = CES(seasonality="full", lags=[1, 12], h=18, holdout=True)
model.fit(y)
# Automatic seasonality selection
auto = AutoCES(lags=[1, 12], h=18, holdout=True, ic="AICc")
auto.fit(y)
print(auto.best_model_.model_name)
fc = auto.predict(h=18)Note: strict R-parity in the two-stage NLopt path (BOBYQA stage-1 → Nelder-Mead stage-2) requires
nlopt>=2.10.0. Older NLopt versions still fit, but the stage-1 trajectory may diverge slightly from R; the PythonCES.fit()emits aRuntimeWarningin that case.
library(smooth)
# Simple CES (no seasonality)
ces(BJsales, h=8, holdout=TRUE)
# CES with simple seasonality
ces(AirPassengers, h=18, holdout=TRUE, seasonality="simple")
# CES with partial seasonality (additive-like)
ces(AirPassengers, h=18, holdout=TRUE, seasonality="partial")
# CES with full seasonality (most flexible)
ces(AirPassengers, h=18, holdout=TRUE, seasonality="full")
# Automatic seasonality selection
auto.ces(AirPassengers, h=18, holdout=TRUE)Note: External regressors (
xregin R,Xin Python) are supported in R. The PythonCES.fit(y, X=...)argument is wired through to the C++ core but the widerregressors="select"/"adapt"paths are R-only for now.
# CES with explanatory variables (R)
ces(y, h=12, xreg=X, regressors="use")
# Automatic regressor selection (R)
ces(y, h=12, xreg=X, regressors="select")# CES with explanatory variables (Python — fixed coefficients only)
model = CES(seasonality="partial", lags=[1, 12], h=12)
model.fit(y, X=X)
fc = model.predict(h=12, X=X_future)| Parameter | Type (R) | Type (Python) | Default | Description |
|---|---|---|---|---|
y |
vector/ts | NDArray | - | Time series data |
seasonality |
character | str (Literal) | "none" |
Seasonality type: "none" / "simple" / "partial" / "full"
|
lags |
numeric vector | List[int]/None |
frequency(y) (R) / [1] (Python) |
Seasonal lag(s) |
a |
complex | complex/None | NULL | First smoothing parameter (estimated if NULL/None) |
b |
complex | complex/float/None | NULL | Second smoothing parameter (estimated if NULL/None; real for partial, complex for full) |
initial |
character | str | "backcasting" |
Initialisation method: "backcasting" / "optimal" / "two-stage" / "complete"
|
loss |
character | str | "likelihood" |
Loss function |
h |
integer | int/None | 0 | Forecast horizon |
holdout |
logical | bool | FALSE | Use holdout validation |
bounds |
character | str | "admissible" |
"admissible" (eigenvalue stability) or "none"
|
ic |
character | str | "AICc" |
Information criterion (AutoCES only in Python) |
xreg (R) / X (Python) |
matrix | NDArray/DataFrame | NULL/None | External regressors |
regressors |
character | str | "use" |
How to handle regressors ("use" / "select"; only "use" in Python) |
auto.ces() (R) and AutoCES (Python) fit the candidate seasonality types in turn and select the best by information criterion. Both prune the seasonality pool by sample size before fitting (e.g. "full" is skipped if n ≤ 2*frequency + 7).
# R: automatic selection between seasonality types
auto.ces(y, h=12, holdout=TRUE)
# R: specify which seasonality types to try
auto.ces(y, h=12, seasonality=c("none", "simple", "partial", "full"))# Python: automatic selection
AutoCES(lags=[1, 12], h=12, holdout=True).fit(y)
# Python: restrict the pool
AutoCES(seasonality=["none", "partial"], lags=[1, 12], h=12).fit(y)The complex smoothing parameters a = a_r + i·a_i have the following interpretation:
- Real part (a_r): Controls the speed of adaptation to level changes
- Imaginary part (a_i): Controls the "momentum" or potential energy in the system
The interplay between real and imaginary parts allows CES to capture both trending and oscillating behaviours without explicit trend specification.
- No explicit trend selection: CES automatically adapts to trends
- Fewer model variants: Only 4 seasonality types vs 30 ETS models
- Long memory processes: Performs well on time series without abrupt changes; has the long-memory property.
| Element (R) | Element (Python) | Type (R) | Type (Python) | Description |
|---|---|---|---|---|
modelName() |
model_name |
character | str | Full model name (e.g. "CES(partial)", "CESX(full)") |
parameters$a |
a_ |
complex | complex / list[complex] | First smoothing parameter (list for simple seasonality with multiple lags) |
parameters$b |
b_ |
complex / numeric | complex / float / list | Second smoothing parameter (partial/full only) |
persistence |
persistence_vector |
complex vector | NDArray | Persistence vector built from Re(a) ± Im(a)
|
transition |
transition_matrix |
matrix | NDArray | Companion-form transition matrix F |
measurement |
measurement_matrix |
matrix | NDArray | Measurement matrix W |
initial |
initial_states_ |
list | Dict[str, NDArray] | Initial state values ("nonseasonal" / "seasonal" / "xreg" keys) |
states |
states |
matrix | NDArray | State matrix over time |
fitted |
fitted |
vector | NDArray | Fitted values |
residuals |
residuals |
vector | NDArray | Residuals |
forecast |
predict() result |
vector | NDArray (via .mean) |
Point forecasts |
logLik |
loglik |
numeric | float | Log-likelihood |
AIC / AICc / BIC / BICc
|
aic / aicc / bic / bicc
|
numeric | float | Information criteria |
scale |
scale_ |
numeric | float | Residual scale |
B |
B / coef
|
vector | NDArray | All estimated parameters |
nParam |
n_param |
numeric | int | Number of estimated parameters |
ICs (auto.ces) |
ICs (AutoCES) |
numeric vector | Dict[str, float] | Information criteria for each candidate seasonality |
For AutoCES, the best fitted CES instance is exposed as .best_model_ (and .predict() / .summary() delegate to it), mirroring R's auto.ces returning the chosen ces object.
CES is mathematically distinct from the ETS family covered by ADAM and ES: the smoothing parameters are complex-valued and the state evolves through a companion-form transition matrix rather than the additive/multiplicative ETS recursion. It still uses the same C++ adamCore engine for state-space filtering and forecasting in the Python port (the _adamCore pybind module), which is why fitted attributes such as states / transition line up with the rest of the package.
- Svetunkov, I., Kourentzes, N., & Ord, J.K. (2022). Complex exponential smoothing. Naval Research Logistics, 69(5), 697-717. DOI: 10.1002/nav.22074
- ADAM - Main unified framework
- ES - Traditional Exponential Smoothing
- GUM - Generalised Univariate Model
- Loss-Functions - Loss function options
- Explanatory-Variables - Using external regressors
- Initialisation - State initialisation methods
- Bounds - Parameter restrictions