-
Notifications
You must be signed in to change notification settings - Fork 22
OM models the probability of demand occurrence for intermittent time series. It implements the occurrence part of the iETS framework as a state-space model with a Bernoulli likelihood and one of several link functions. Available in both R (om()) and Python (OM, OMG, AutoOM).
Note: iETS refers to the full model for demand sizes and demand occurrence. OM (oETS) refers to the occurrence part only. See ADAM for the full iETS model.
Each observation is binary: o_t ∈ {0, 1} (1 = non-zero demand). The model is:
o_t ~ Bernoulli(p_t)
p_t = link(μ_{a,t}, μ_{b,t})
Where p_t ∈ (0, 1) is the probability of non-zero demand, and μ_{a,t}, μ_{b,t} are conditional expectations of unobservable latent variables a_t and b_t, each following their own ETS model:
a_t = l_{a,t-1}(1 + ε_{a,t})
l_{a,t} = l_{a,t-1}(1 + α_a ε_{a,t})
μ_{a,t} = l_{a,t-1}
The link function — and which sub-models evolve — depends on the occurrence type. See OES for the full mathematical framework.
| Type | Code | Link formula | Description |
|---|---|---|---|
| oETS_F | "fixed" |
p = T₁/T | Constant probability; no optimisation |
| oETS_O | "odds-ratio" |
p = μₐ/(μₐ+1) | Logistic-style; μ_b fixed at 1 |
| oETS_I | "inverse-odds-ratio" |
p = 1/(1+μ_b) | Croston-related; μ_a fixed at 1 |
| oETS_D | "direct" |
p = μₐ (TSB-like) | Direct probability; μₐ ∈ [0,1] |
| oETS_G | "general" |
p = μₐ/(μₐ+μ_b) | Both sub-models evolve — use OMG
|
| oETS_A | "auto" |
— | Automatic selection — use AutoOM
|
Where T₁ is the count of non-zero observations and T is the total number of observations.
OM is the single entry point. It transparently dispatches to specialised classes:
| Call | Returns |
|---|---|
OM(occurrence="fixed") |
OM instance |
OM(occurrence="odds-ratio") |
OM instance |
OM(occurrence="inverse-odds-ratio") |
OM instance |
OM(occurrence="direct") |
OM instance |
OM(occurrence="general") |
OMG instance |
OM(occurrence="auto") |
AutoOM instance |
Users only need to import OM; the other classes (OMG, AutoOM) are available for explicit use.
OM uses the same three-letter ETS codes as ADAM (Error–Trend–Seasonal). The same wildcards apply:
-
"Z"— auto-select from {A, M} -
"X"— auto-select from {N, A, Ad, M, Md} -
"N"— force None
Default: model="ZXZ".
For occurrence="fixed" the model is forced to "ANN" (constant probability, α = 0).
OM also supports ARIMA components (via orders, ar_order, i_order, ma_order) and external regressors (via formula / regressors). The oes() R function is an ETS-only wrapper of om() that disables ARIMA and formula support — see OES.
Model name format:
- Single OM:
"oETS(MNN)[O]"— ETS type in parentheses, bracket letter = occurrence code - With ARIMA:
"oETS(MNN)[O]+ARIMA(1,0,0)" - OMG:
"oETS[G](MNN)(MNN)"— two sub-model specs side by side
library(smooth)
y <- rbinom(120, 1, 0.6)
# Odds-ratio (default)
m <- om(y, model="MNN", occurrence="odds-ratio")
forecast(m, h=12)
# Fixed probability (no smoothing)
m_fixed <- om(y, occurrence="fixed")
# Direct (TSB-like)
m_direct <- om(y, model="MNN", occurrence="direct")
# General model (calls omg() internally)
m_gen <- om(y, model="MNN", occurrence="general", h=10, holdout=TRUE)
# Auto-selection
m_auto <- om(y, model="ZXZ", occurrence="auto", ic="AICc")
# With ARIMA
m_arima <- om(y, model="NNN", orders=list(ar=1, i=0, ma=0), occurrence="odds-ratio")
# With seasonal lags
m_seasonal <- om(y, model="MNA", lags=c(1, 7), occurrence="odds-ratio")Using omg() directly for different sub-model specs:
# Different ETS models for a_t and b_t
m <- omg(y, modelA="MNN", modelB="AAN", h=10)from smooth import OM, OMG, AutoOM
import numpy as np
y = np.array([0,1,0,0,1,1,0,1,0,0,1]*10, dtype=float)from smooth import OM
# Fixed (constant probability, no smoothing)
m = OM(occurrence="fixed")
m.fit(y)
print(m.model_name) # "oETS(ANN)[F]"
print(m.fitted[:5]) # constant p in [0,1]
# Odds-ratio
m = OM(model="MNN", occurrence="odds-ratio")
m.fit(y)
fc = m.predict(h=10)
fc.mean # pd.Series of probability forecasts ∈ [0,1]
# Inverse odds-ratio
m = OM(model="MNN", occurrence="inverse-odds-ratio")
m.fit(y)
# Direct (TSB-like)
m = OM(model="MNN", occurrence="direct")
m.fit(y)
# General (dispatches to OMG automatically)
m = OM(model="MNN", occurrence="general")
m.fit(y)
print(type(m)) # <class '...OMG'>
print(m.model_name) # "oETS[G](MNN)(MNN)"
# Auto-selection (dispatches to AutoOM automatically)
m = OM(model="ZXZ", occurrence="auto")
m.fit(y)
print(type(m)) # <class '...AutoOM'>
print(m.occurrence_) # selected type, e.g. "odds-ratio"from smooth import OMG
# Same model for both sub-models
m = OMG(model_a="MNN", model_b="MNN")
m.fit(y)
print(m.model_name) # "oETS[G](MNN)(MNN)"
print(m.fitted[:5]) # combined probabilities ∈ (0,1)
# Different models for A and B
m = OMG(model_a="MNN", model_b="ANN")
m.fit(y)
# Access individual sub-models
m.model_a.fitted # sub-model A probabilities (odds-ratio side)
m.model_b.fitted # sub-model B probabilities (inverse-odds-ratio side)
# Seasonal data (e.g. weekly with lags=[1,7])
m = OMG(model_a="MNA", model_b="MNA", lags=[1, 7])
m.fit(y_weekly)from smooth import AutoOM
# Try all 5 occurrence types, pick best by AICc
m = AutoOM(model="ZXZ", ic="AICc")
m.fit(y)
print(m.occurrence_) # winning type, e.g. "odds-ratio"
print(m.ic_values) # {"fixed": 12.3, "odds-ratio": 10.1, ...}
print(m.best_model) # the fitted OM or OMG instance
# Restrict to a subset of types
m = AutoOM(model="MNN", occurrence=["odds-ratio", "inverse-odds-ratio", "general"])
m.fit(y)| Parameter | Type (R) | Type (Python) | Default (R / Python) | Description |
|---|---|---|---|---|
data / y
|
vector/ts | NDArray | — | Binary or non-binary series; binarised automatically (non-zero → 1) |
model |
character | str | "ZXZ" |
ETS specification or wildcard |
lags |
vector | int/List[int]/None |
frequency(y) / [1]
|
Seasonal period(s) |
orders |
list | Dict/None | zeros | ARIMA orders as list(ar=, i=, ma=) / {"ar":, "i":, "ma":}
|
ar_order |
— | int/List[int] | 0 | AR order(s) (Python alternative to orders) |
i_order |
— | int/List[int] | 0 | Integration order(s) |
ma_order |
— | int/List[int] | 0 | MA order(s) |
occurrence |
character | str |
"auto" / "odds-ratio"
|
Link function type; see link table above |
constant |
logical | bool | FALSE / False | Include constant/drift term |
formula |
formula | str/None | NULL / None | External regressors formula |
regressors |
character | str | "use" |
Regressor handling: "use", "select", or "adapt"
|
persistence |
vector | Dict[str,float]/None | NULL / None | Fixed smoothing parameters; keys "alpha", "beta", "gamma"
|
phi |
numeric | float/None | NULL / None | Damping parameter |
initial |
character | str/Dict | "backcasting" |
Initialisation: "backcasting", "optimal", "two-stage", "complete"
|
arma |
list | Dict/None | NULL / None | Fixed ARMA coefficients |
loss |
character | str | "likelihood" |
"likelihood" (Bernoulli) or "MSE"
|
ic |
character | str | "AICc" |
Information criterion: "AIC", "AICc", "BIC", "BICc"
|
bounds |
character | str | "usual" |
Parameter bounds: "usual", "admissible", "none"
|
h |
integer | int | 0 | Forecast horizon |
holdout |
logical | bool | FALSE / False | Hold out last h observations for validation |
verbose |
— | int | 0 | Verbosity level (Python only; R uses silent) |
nlopt_kargs |
... |
Dict/None | NULL / None | Advanced NLopt options (Python) / extra ... args (R) |
frequency |
— | str/None | None | Pandas frequency string for datetime index (Python only) |
All OM parameters apply, but with _a / _b suffixes for the two sub-models:
| Parameter | Default | Description |
|---|---|---|
model_a |
"MNN" |
ETS spec for sub-model A (odds-ratio side) |
model_b |
same as model_a
|
ETS spec for sub-model B (inverse-odds-ratio side) |
orders_a, orders_b
|
None | ARIMA orders per sub-model |
constant_a, constant_b
|
False | Constant flag per sub-model |
persistence_a, persistence_b
|
None | Fixed smoothing per sub-model |
phi_a, phi_b
|
None | Damping per sub-model |
arma_a, arma_b
|
None | Fixed ARMA per sub-model |
formula_a, formula_b
|
None | Regressor formulae per sub-model |
regressors_a, regressors_b
|
"use" |
Regressor handling per sub-model |
| Parameter | Default | Description |
|---|---|---|
model |
"ZXZ" |
ETS spec for non-general candidates |
model_a, model_b
|
"MNN" |
ETS specs for the OMG (general) candidate |
occurrence |
all 5 types | List of occurrence types to try |
| Attribute (R) | Attribute (Python) | Type | Description |
|---|---|---|---|
modelName(m) |
m.model_name |
str | Full name, e.g. "oETS(MNN)[O]"
|
fitted(m) |
m.fitted |
NDArray | In-sample probability estimates ∈ (0,1) |
residuals(m) |
m.residuals |
NDArray | o_t − p̂_t |
m$states |
m.states |
NDArray | State matrix (components × T+lags) |
m$persistence |
m.persistence_vector |
Dict | Smoothing parameters {"alpha": ..., ...}
|
m$phi |
m.phi_ |
float/None | Damping parameter |
m$transition |
m.transition |
NDArray | Transition matrix F |
m$measurement |
m.measurement |
NDArray | Measurement matrix W |
m$initial |
m.initial_value |
Dict | Initial states |
coef(m) |
m.coef |
NDArray | Estimated parameter vector B |
logLik(m) |
m.loglik |
float | Log-likelihood (Bernoulli) |
AIC(m) |
m.aic |
float | AIC |
AICc(m) |
m.aicc |
float | Corrected AIC |
BIC(m) |
m.bic |
float | BIC |
BICc(m) |
m.bicc |
float | Corrected BIC |
m$lossValue |
m.loss_value |
float | Value of loss function |
m$distribution |
m.distribution_ |
str | Always "plogis"
|
m$scale |
m.scale / m.sigma
|
float |
nan (no scale for Bernoulli) |
m$occurrence |
m._om_occurrence |
str | Occurrence type used |
nobs(m) |
m.nobs |
int | Number of in-sample observations |
m$accuracy |
m.accuracy |
Dict/None | Holdout accuracy (when holdout=True) |
OM inherits all ADAM attributes; see ADAM for the full list.
| Attribute (Python) | Description |
|---|---|
m.model_a |
Fitted OM sub-model A (odds-ratio side) |
m.model_b |
Fitted OM sub-model B (inverse-odds-ratio side) |
m.model_name |
"oETS[G](MNN)(MNN)" |
m.fitted |
Combined probability aFit/(aFit+bFit)
|
m.coef |
Joint parameter vector concat(B_A, B_B)
|
| Attribute (Python) | Description |
|---|---|
m.best_model |
Best fitted OM or OMG instance |
m.occurrence_ |
Selected occurrence type (trailing underscore = post-fit value) |
m.ic_values |
Dict mapping each tried type to its IC value |
m.time_elapsed_ |
Total selection time (seconds) |
m <- om(y, model="MNN", occurrence="odds-ratio")
modelName(m) # "oETS(MNN)[O]"
fitted(m) # in-sample probabilities
residuals(m) # o_t - p̂_t
m$states # state matrix
m$persistence # smoothing parameters
logLik(m)
AIC(m); AICc(m); BIC(m)
coef(m)
forecast(m, h=12)m = OM(model="MNN", occurrence="odds-ratio")
m.fit(y)
m.model_name # "oETS(MNN)[O]"
m.fitted # in-sample probability estimates
m.residuals # o_t - p̂_t
m.states # state matrix
m.persistence_vector # {"alpha": ..., ...}
m.aic; m.aicc; m.bic; m.bicc
fc = m.predict(h=12)
fc.mean # pd.Series of probability forecasts ∈ [0,1]
# OMG sub-model access
g = OMG(model_a="MNN", model_b="MNN")
g.fit(y)
g.model_a.fitted # sub-model A probabilities
g.model_b.fitted # sub-model B probabilities
# AutoOM selection results
a = AutoOM(model="ZXZ")
a.fit(y)
a.occurrence_ # selected type
a.ic_values # all IC values compared
a.best_model # the winning fitted modelOM.predict() returns probability forecasts (mean ∈ [0,1]). The interval and level parameters are accepted for API compatibility but interval estimation for binary occurrence is not currently supported.
To forecast full intermittent demand (occurrence × demand sizes), pass the fitted OM as the occurrence argument of ADAM.fit():
occ_model = OM(model="MNN", occurrence="odds-ratio")
occ_model.fit(y)
from smooth import ADAM
demand_model = ADAM(model="MNN")
demand_model.fit(y, occurrence=occ_model)
fc = demand_model.predict(h=12)In R:
occ_model <- om(y, model="MNN", occurrence="odds-ratio")
demand_model <- adam(y, "MNN", occurrence=occ_model, h=12)- Svetunkov, I. (2023). Forecasting and Analytics with ADAM. Chapter 13: https://openforecast.org/adam/ADAMIntermittent.html
- Svetunkov, I. & Boylan, J.E. (2023). iETS: State space model for intermittent demand forecasting. International Journal of Production Economics, 265, 109013. DOI: 10.1016/j.ijpe.2023.109013
- ADAM — full iETS model (occurrence + demand sizes in one fit)
-
OES — mathematical framework and older R-only
oes()documentation -
Fitted-Values-and-Forecasts —
predict()output format -
Residuals-and-Errors —
residuals(),rmultistep(),outlierdummy()