Skip to content
Ivan Svetunkov edited this page May 10, 2026 · 16 revisions

OM - Occurrence Model

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(), omg(), auto.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.

Mathematical Framework

The iETS framework decomposes intermittent demand as:

y_t = o_t × z_t

Where o_t ∈ {0, 1} is the binary occurrence indicator and z_t is the demand size (modelled separately - see ADAM). OM focuses on the occurrence part:

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 follows its own ADAM model - which may be pure ETS, pure ARIMA, or their combination, optionally extended with external regressors. For example, for OM with ETS(M,N,N) sub-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}

And similarly for b_t. The link function and the restrictions on μ_{a,t} and μ_{b,t} determine the occurrence subtype.

Model Variants

Because each sub-model can be ETS, ARIMA, or their ETSX/ARIMAX variants (with regressors), the full class is called OM regardless of internal model type. The notation of specific variants follows the underlying sub-model:

Underlying sub-model Name Example model name
ETS only oETS oETS(MNN)[O]
ARIMA only oARIMA oARIMA(1,0,0)[O]
ETS + regressors oETSX oETSX(MNN)[O]
ARIMA + regressors oARIMAX oARIMAX(1,0,0)[O]
ETS + ARIMA oETS+ARIMA oETS(MNN)[O]+ARIMA(1,0,0)
ETS + ARIMA + regressors oETS+ARIMAX oETSX(MNN)[O]+ARIMA(1,0,0)

All variants are fitted through the same OM / om() interface by combining the model, orders, and formula / X parameters.

Link Functions (Occurrence Types)

Type Code Link formula Description
OM_F "fixed" p = T₁/T Constant probability; no optimisation
OM_O "odds-ratio" p = μₐ/(μₐ+1) Logistic-style; μ_b fixed at 1
OM_I "inverse-odds-ratio" p = 1/(1+μ_b) Inverse logistic; Croston-related; μ_a fixed at 1
OM_D "direct" p = μₐ Direct probability; TSB-like; μₐ ∈ [0,1]
OM_G "general" p = μₐ/(μₐ+μ_b) Both sub-models evolve - calls OMG
OM_A "auto" - Automatic selection between the five above via AutoOM

Where T₁ is the count of non-zero observations and T is the total number of observations.

OM_F (Fixed)

Constant probability - no optimisation, no state equation:

o_t ~ Bernoulli(p)
p̂ = T₁/T

For an ETS-based OM_F the sub-model is forced to "ANN" with α = 0.

OM_O (Odds-Ratio)

Sets μ_b = 1:

p_t = μ_{a,t} / (μ_{a,t} + 1)

Equivalent to a logistic transform of the latent state a_t. The a_t sub-model can be ETS, ARIMA, or ETSX/ARIMAX.

OM_I (Inverse Odds-Ratio)

Sets μ_a = 1:

p_t = 1 / (1 + μ_{b,t})

Related to Croston's method when 1 + b_t represents demand inter-arrival intervals.

OM_D (Direct)

Imposes μ_a + μ_b = 1 with μ_a ∈ [0, 1]:

p_t = μ_{a,t} = min(l_{a,t-1}, 1)

Analogous to the TSB (Teunter–Syntetos–Babai) method, modelling the probability directly as a bounded state.

OM_G (General)

No restrictions - both a_t and b_t evolve with their own independent ADAM models:

p_t = μ_{a,t} / (μ_{a,t} + μ_{b,t})

Most flexible but requires the most parameters. Implemented via OMG / omg().

iADAM Full Model Notation

iADAM is the model that joins the demand occurrence with the demand sizes parts. Here is an example with the iETS, i.e. model that has ETS components only:

iETS(E,T,S)_X(E,T,S)(E,T,S)

  • First brackets: ETS model for demand sizes (z_t component)
  • Subscript letter: Occurrence subtype (F, O, I, D, G)
  • Second brackets: ETS model for a_t
  • Third brackets: ETS model for b_t (only for OM_G)

Examples:

  • iETS(M,N,N)_F: Fixed probability, MNN for sizes
  • iETS(M,M,N)_O(M,N,N): Odds-ratio, MMN for sizes, MNN for occurrence
  • iETS(M,N,N)_G(M,N,N)(A,A,N): General, different sub-models for a_t and b_t

When discussing the occurrence part alone, the bracket shows the sub-model type, e.g. oETS(MNN)[O], oARIMA(1,0,0)[O], oETS(MNN)[O]+ARIMA(1,0,0).

Class Routing (Python)

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.

ETS Model Specification

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". Check ADAM for mode details.

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.

R Usage

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)

Python Usage

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)

OM

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"

OM with ARIMA components (oARIMA)

from smooth import OM

# Pure ARIMA(1,0,0) sub-model - oARIMA(1,0,0)[O]
m = OM(model="NNN", ar_order=1, i_order=0, ma_order=0, occurrence="odds-ratio")
m.fit(y)
print(m.model_name)   # "oARIMA(1,0,0)[O]"

# ETS + ARIMA - oETS(MNN)[O]+ARIMA(1,0,0)
m = OM(model="MNN", ar_order=1, occurrence="odds-ratio")
m.fit(y)
print(m.model_name)   # "oETS(MNN)[O]+ARIMA(1,0,0)"

OM with external regressors (oETSX / oARIMAX)

import numpy as np
from smooth import OM

n = len(y)
X = np.random.rand(n, 1)   # one external regressor (e.g. promotional flag)

# oETSX - ETS with fixed regressor
m = OM(model="MNN", occurrence="odds-ratio", regressors="use")
m.fit(y, X=X)
print(m.model_name)   # "oETS(MNN)[O]+X"

# oARIMAX - ARIMA with regressor
m = OM(model="NNN", ar_order=1, occurrence="odds-ratio", regressors="use")
m.fit(y, X=X)
print(m.model_name)   # "oARIMA(1,0,0)[O]+X"

# Automatic regressor selection
m = OM(model="MNN", occurrence="odds-ratio", regressors="select")
m.fit(y, X=X)

OMG - General model with two sub-models

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)

AutoOM - automatic selection

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)

Parameters

OM Core Parameters

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)

OMG-Specific Parameters (Python OMG class)

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

AutoOM-Specific Parameters (Python AutoOM class)

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

Fitted Attributes

OM Fitted Attributes

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.

OMG Fitted Attributes (additional)

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)

AutoOM Fitted Attributes (additional)

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)

Accessing Elements

R

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)

Python

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 model

Note on Interval Forecasts

OM.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)

References

See Also

Clone this wiki locally