ThetaForecaster

class omnicast.ThetaForecaster(seasonal_period=None)[source]

Bases: BaseForecaster

Classical Theta method (Assimakopoulos & Nikolopoulos, 2000).

Compatible Python reimplementation of R’s forecast::thetaf (Hyndman, package forecast, GPL-3) – not a call into R, and not yet verified against its numerical output; see CONTRIBUTING.md. It decomposes the series into two “theta lines”: the theta=0 line is the long-term linear trend, and the theta=2 line doubles local curvature around that trend. The theta=2 line is extrapolated with simple exponential smoothing, the theta=0 line is extrapolated linearly, and the two forecasts are averaged with equal weight, following Assimakopoulos & Nikolopoulos (2000), “The theta model: a decomposition approach to forecasting”, International Journal of Forecasting 16(4):521-530.

When seasonal_period is given, the series is deseasonalized first with a multiplicative classical decomposition (statsmodels.seasonal_decompose) and forecasts are reseasonalized afterwards; this requires strictly positive values and at least two full seasonal cycles.

Prediction intervals use the same residual-variance random-walk scaling (sqrt(sigma2 * h)) as NaiveForecaster, an approximation rather than the exact ETS(A,N,N) state-space interval that R’s implementation derives from the SES equivalence proven by Hyndman & Billah (2003), “Unmasking the Theta method”, International Journal of Forecasting 19(2):287-290.

Supported indexes: any index accepted by future_index (PeriodIndex, DatetimeIndex with a regular frequency, RangeIndex, or numeric Index). Minimum sample size: 4 observations, or 2 * seasonal_period when seasonal.

Examples

>>> import pandas as pd
>>> from omnicast import ThetaForecaster
>>> y = pd.Series([10.0, 12.0, 11.0, 13.0, 15.0, 14.0])
>>> model = ThetaForecaster().fit(y)
>>> model.predict(horizon=2).mean.round(2).tolist()
[14.05, 14.49]

Pass seasonal_period to deseasonalize first (multiplicative decomposition) and reseasonalize the forecast afterward; this requires strictly positive values and at least two full seasonal cycles, e.g. ThetaForecaster(seasonal_period=12) on two years of monthly data.

Notes

When to use this model

Best for

A strong, fast default before reaching for a full state-space model; a good general-purpose replacement for the baselines

Avoid when

You need exact parity with R’s forecast::thetaf intervals, or a model that supports exogenous regressors

Handles trend

Yes (linear long-term trend line)

Handles seasonality

Yes, via seasonal_period (multiplicative decomposition)

Extra dependencies

None

Min. observations

4, or 2 * seasonal_period when seasonal

Parameters:

seasonal_period (int | None)

fit(y, X=None)
Return type:

BaseForecaster

Parameters:
fit_predict(y, horizon, **kwargs)
Return type:

ForecastResult

Parameters:
get_params()
Return type:

dict[str, object]

predict(horizon, X=None, level=(80, 95))
Return type:

ForecastResult

Parameters:

The package’s first R port: a compatible pure-Python reimplementation of R’s forecast::thetaf (classical Theta method, Assimakopoulos & Nikolopoulos 2000). It decomposes the series into a linear long-term trend and a curvature-doubled “theta line” extrapolated with simple exponential smoothing, then averages the two. Strong, fast, and a good default before reaching for a full state-space model.

Non-seasonal

from omnicast import ThetaForecaster

model = ThetaForecaster().fit(y)          # seasonal_period=None
forecast = model.predict(horizon=6)

Seasonal

Pass seasonal_period to deseasonalize first (multiplicative classical decomposition) and reseasonalize the forecast afterward. This requires strictly positive values and at least two full seasonal cycles.

model = ThetaForecaster(seasonal_period=12).fit(y)
forecast = model.predict(horizon=6, level=[80, 95])
print(forecast.to_frame())
           mean  lower_80  upper_80  lower_95  upper_95
2026-01  206.46    203.31    209.60    201.65    211.27
2026-02  216.66    212.21    221.11    209.86    223.46
2026-03  222.14    216.69    227.59    213.81    230.47
2026-04  227.11    220.82    233.40    217.49    236.73
2026-05  225.00    217.97    232.03    214.25    235.76
2026-06  219.82    212.11    227.52    208.03    231.60

Unlike the flat DriftForecaster trend, Theta’s forecast tracks both the upward trend and the yearly seasonal shape. alpha_ is the fitted exponential-smoothing weight for the theta=2 line:

model.alpha_   # smoothing_level chosen by SES's own MLE, not a hyperparameter you set

Known deviation from R

Prediction intervals here use the same residual-variance random-walk scaling (sqrt(sigma2 * h)) as NaiveForecaster, not the exact ETS(A,N,N) state-space interval R’s thetaf derives from the SES equivalence (Hyndman & Billah 2003). Point forecasts follow the same method; interval widths will differ slightly. See CONTRIBUTING.md for the parity-fixture policy.

Minimum sample size is 4 observations (or 2 * seasonal_period when seasonal); fit raises ValueError below that, or if seasonal values are non-positive.