-
Notifications
You must be signed in to change notification settings - Fork 22
SMA
title: SMA — Simple Moving Average slug: SMA summary: Simple moving average in state-space form with automatic order selection. status: stable applies_to: [r, python] related: [ADAM]
SMA (Simple Moving Average) implements simple moving average in state-space form with automatic order selection. This provides a statistically rigorous foundation for the classic moving average method.
SMA is available in both R (
sma()) and Python (SMA). Both share the same algorithm and options.
sma(y, order = NULL,
ic = c("AICc", "AIC", "BIC", "BICc"),
h = 10, holdout = FALSE,
silent = TRUE, fast = TRUE, ...)class SMA(ADAM):
def __init__(
self,
order: int | None = None,
ic: Literal["AIC", "AICc", "BIC", "BICc"] = "AICc",
h: int = 10,
holdout: bool = False,
fast: bool = True,
verbose: int = 0,
**kwargs,
) -> None: ...The function constructs an AR model in state-space form based on the simple moving average concept:
yₜ = (1/n) Σⱼ₌₁ⁿ yₜ₋ⱼ
This is equivalent to an AR(n) process, allowing proper parameter estimation and prediction interval construction.
Note that the forecast from the SMA in the state space is not a straight line! It is a multistep conditional expectation from the respective AR(n) model with provided parameters. See Svetunkov & Petropoulos (2018) for more details.
How it works (both R and Python). SMA(m) is implemented as a thin wrapper over ADAM with model="NNN", an AR(m) component, and every AR coefficient hard-fixed at 1/m. Because of this it inherits the full ADAM machinery — recursive multi-step forecasts, proper forecast variance, prediction intervals, and residual diagnostics. When the order is left unspecified it is selected automatically by an information criterion (a fast modified ternary search by default, or an exhaustive scan). In Python this is the SMA class (from smooth import SMA), which subclasses ADAM and therefore exposes the same fit() / predict() / diagnostics surface.
library(smooth)
# SMA with specific order
sma(y, order=12, h=18, holdout=TRUE)
# Automatic order selection
sma(y, h=18, holdout=TRUE)
# With specific information criterion
sma(y, order=NULL, ic="BIC", h=18, holdout=TRUE)
# Fast search (modified ternary search)
sma(y, h=18, holdout=TRUE, fast=TRUE)
# Full search
sma(y, h=18, holdout=TRUE, fast=FALSE)model <- sma(y, order=12, h=18, holdout=TRUE)
# Point forecasts
forecast(model, h=18)
# With prediction intervals
plot(forecast(model, h=18, interval="empirical"))
plot(forecast(model, h=18, interval="parametric"))The Python examples below mirror the R ones one-to-one. The only structural
difference is the scikit-learn-style split: options go to the SMA(...)
constructor and the data is passed to .fit(y).
from smooth import SMA
# SMA with specific order
SMA(order=12, h=18, holdout=True).fit(y)
# Automatic order selection
SMA(h=18, holdout=True).fit(y)
# With specific information criterion
SMA(order=None, ic="BIC", h=18, holdout=True).fit(y)
# Fast search (modified ternary search)
SMA(h=18, holdout=True, fast=True).fit(y)
# Full search
SMA(h=18, holdout=True, fast=False).fit(y)model = SMA(order=12, h=18, holdout=True)
model.fit(y)
# Point forecasts
fc = model.predict(h=18)
fc.mean
# With prediction intervals (R "parametric" -> Python "prediction")
model.predict(h=18, interval="prediction")
model.predict(h=18, interval="simulated")| Parameter | Type (R) | Type (Python) | Default (R / Python) | Description |
|---|---|---|---|---|
y |
vector/ts | NDArray / pd.Series | - | Time series data. In Python it is passed to .fit(y), not the constructor |
order |
integer/NULL |
int / None
|
NULL / None
|
SMA order (NULL/None for auto-selection) |
ic |
character | str |
"AICc" | Information criterion for order selection |
h |
integer | int |
10 | Forecast horizon |
holdout |
logical | bool |
FALSE / False | Use holdout validation |
silent (R) / verbose (Python) |
logical | int |
TRUE / 0 | Suppress output. Python uses verbose (0 = silent) |
fast |
logical | bool |
TRUE / True | Use fast ternary search |
-
"AICc": Corrected AIC (default) -
"AIC": Akaike Information Criterion -
"BIC": Bayesian Information Criterion -
"BICc": Corrected BIC
When order=NULL, the function automatically selects the optimal order:
-
Fast mode (
fast=TRUE): Modified ternary search - finds local minimum quickly -
Full mode (
fast=FALSE): Exhaustive search - guarantees global minimum but slower
The sma() function internally uses adam() with model="NNN" and AR coefficients all equal to 1/n. It returns an object of class "adam". In Python, SMA subclasses ADAM, so the same elements are exposed as attributes/properties on the fitted object (Type (Python) columns below; — marks elements with no Python equivalent yet).
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
model |
character | str |
Model name, e.g. "SMA(12)" (model.model) |
timeElapsed |
difftime | float |
Time elapsed for model construction (model.time_elapsed_) |
call |
call | — | The function call (R only) |
orders |
integer | int |
The SMA order (R: orders(model); Python: embedded in model.model) |
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
states |
matrix | NDArray | State matrix, observations × states (model.states) |
transition |
matrix | NDArray | Transition matrix F, all elements = 1/n (model.transition) |
persistence |
numeric vector | NDArray | Persistence vector g, all elements = 1/n (model.persistence) |
measurement |
numeric vector | NDArray | Measurement vector w (model.measurement) |
initial |
numeric vector | dict | Initial state vector values (model.initial_value) |
initialType |
character | str |
Type of initial values used (model.initial_type) |
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
fitted |
vector | pd.Series / NDArray | Fitted values (model.fitted) |
forecast |
vector | pd.Series | Point forecasts for h steps ahead (model.predict(h).mean) |
lower |
vector | pd.DataFrame | Lower bound of prediction interval (model.predict(h, interval=...).lower) |
upper |
vector | pd.DataFrame | Upper bound of prediction interval (model.predict(h, interval=...).upper) |
residuals |
vector | NDArray | Model residuals (model.residuals) |
errors |
matrix | — | Matrix of 1 to h steps ahead errors (for multistep losses) |
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
s2 |
numeric | float |
Residual variance, adjusted for degrees of freedom (model.sigma ** 2) |
logLik |
numeric | float |
Log-likelihood value (model.loglik) |
lossValue |
numeric | float |
Cost function value (model.loss_value) |
loss |
character | str |
Type of loss function used (model.loss_) |
nParam |
matrix | int |
Number of estimated parameters (model.nparam) |
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
y |
vector/ts | NDArray | Original data (model.actuals) |
holdout |
vector/ts | NDArray | Holdout part of original data (model.holdout_data) |
interval |
character | str |
Type of interval requested (predict() argument) |
level |
numeric |
float / list |
Confidence level for interval (predict() argument) |
cumulative |
logical | bool |
Whether forecast was cumulative (predict() argument) |
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
ICs |
named numeric vector | dict | IC values for each tested order (R: model$ICs; Python: model.ICs_) |
| Element | Type (R) | Type (Python) | Description |
|---|---|---|---|
accuracy |
numeric vector | — | Accuracy measures (R only; not auto-computed in Python) |
model <- sma(y, h=18, holdout=TRUE)
# Model name
model$model
# Get the order
orders(model)
# Fitted values and residuals
fitted(model)
residuals(model)
# Information criteria
AIC(model)
BIC(model)
# If order was auto-selected, see all tested IC values
model$ICsThe same in Python (note the trailing-underscore fitted attributes):
model = SMA(h=18, holdout=True)
model.fit(y)
# Model name
model.model
# Fitted values and residuals
model.fitted
model.residuals
# Information criteria
model.aic
model.bic
# If order was auto-selected, see all tested IC values
model.ICs_SMA supports various interval types:
# Empirical intervals (bootstrap-based)
forecast(model, h=12, interval="empirical")
# Parametric intervals (normal assumption)
forecast(model, h=12, interval="parametric")
# No intervals
forecast(model, h=12, interval="none")In Python (R "parametric" → Python "prediction"):
# Parametric intervals (normal assumption)
model.predict(h=12, interval="prediction")
# Simulated intervals (Monte Carlo)
model.predict(h=12, interval="simulated")
# No intervals
model.predict(h=12, interval="none")Note: for the Python
SMA,interval="empirical"is not yet supported (use"prediction"or"simulated").
Unlike stats::filter() which only provides smoothed values, smooth::sma():
- Provides proper prediction intervals
- Allows automatic order selection
- Returns state-space components
- Offers information criteria
SMA is appropriate when:
- Simple averaging is conceptually appropriate
- You want automatic order selection
- You need proper uncertainty quantification
- The series has no clear trend or seasonality
For trend or seasonal data, consider ES or ADAM.
SMA is available in Python via the SMA class — see Python Usage above.
It mirrors the R sma() function, including automatic order selection, multi-step
forecasts, and prediction intervals. Unlike pandas' y.rolling(window=n).mean()
(which only smooths in-sample), SMA produces proper recursive forecasts and
uncertainty quantification.
from smooth import SMA
model = SMA(h=12) # order auto-selected by AICc
model.fit(y)
print(model.model) # e.g. "SMA(3)"
fc = model.predict(h=12)- Svetunkov, I., & Petropoulos, F. (2018). Old dog, new tricks: a modelling view of simple moving averages. International Journal of Production Research, 56(18), 6034-6047. DOI: 10.1080/00207543.2017.1380326