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()) 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 Form

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.

Link Functions (Occurrence Types)

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.

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".

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

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 — four occurrence types

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"

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)
frequency str/None None Pandas frequency string for datetime index (Python only)

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