reconnect moved files to git repo
This commit is contained in:
@ -0,0 +1,766 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.special import inv_boxcox
|
||||
from scipy.stats import (
|
||||
boxcox,
|
||||
rv_continuous,
|
||||
rv_discrete,
|
||||
)
|
||||
from scipy.stats.distributions import rv_frozen
|
||||
|
||||
from statsmodels.base.data import PandasData
|
||||
from statsmodels.base.model import Results
|
||||
from statsmodels.base.wrapper import (
|
||||
ResultsWrapper,
|
||||
populate_wrapper,
|
||||
union_dicts,
|
||||
)
|
||||
|
||||
|
||||
class HoltWintersResults(Results):
|
||||
"""
|
||||
Results from fitting Exponential Smoothing models.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
model : ExponentialSmoothing instance
|
||||
The fitted model instance.
|
||||
params : dict
|
||||
All the parameters for the Exponential Smoothing model.
|
||||
sse : float
|
||||
The sum of squared errors.
|
||||
aic : float
|
||||
The Akaike information criterion.
|
||||
aicc : float
|
||||
AIC with a correction for finite sample sizes.
|
||||
bic : float
|
||||
The Bayesian information criterion.
|
||||
optimized : bool
|
||||
Flag indicating whether the model parameters were optimized to fit
|
||||
the data.
|
||||
level : ndarray
|
||||
An array of the levels values that make up the fitted values.
|
||||
trend : ndarray
|
||||
An array of the trend values that make up the fitted values.
|
||||
season : ndarray
|
||||
An array of the seasonal values that make up the fitted values.
|
||||
params_formatted : pd.DataFrame
|
||||
DataFrame containing all parameters, their short names and a flag
|
||||
indicating whether the parameter's value was optimized to fit the data.
|
||||
resid : ndarray
|
||||
An array of the residuals of the fittedvalues and actual values.
|
||||
k : int
|
||||
The k parameter used to remove the bias in AIC, BIC etc.
|
||||
fittedvalues : ndarray
|
||||
An array of the fitted values. Fitted by the Exponential Smoothing
|
||||
model.
|
||||
fittedfcast : ndarray
|
||||
An array of both the fitted values and forecast values.
|
||||
fcastvalues : ndarray
|
||||
An array of the forecast values forecast by the Exponential Smoothing
|
||||
model.
|
||||
mle_retvals : {None, scipy.optimize.optimize.OptimizeResult}
|
||||
Optimization results if the parameters were optimized to fit the data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
params,
|
||||
sse,
|
||||
aic,
|
||||
aicc,
|
||||
bic,
|
||||
optimized,
|
||||
level,
|
||||
trend,
|
||||
season,
|
||||
params_formatted,
|
||||
resid,
|
||||
k,
|
||||
fittedvalues,
|
||||
fittedfcast,
|
||||
fcastvalues,
|
||||
mle_retvals=None,
|
||||
):
|
||||
self.data = model.data
|
||||
super().__init__(model, params)
|
||||
self._model = model
|
||||
self._sse = sse
|
||||
self._aic = aic
|
||||
self._aicc = aicc
|
||||
self._bic = bic
|
||||
self._optimized = optimized
|
||||
self._level = level
|
||||
self._trend = trend
|
||||
self._season = season
|
||||
self._params_formatted = params_formatted
|
||||
self._fittedvalues = fittedvalues
|
||||
self._fittedfcast = fittedfcast
|
||||
self._fcastvalues = fcastvalues
|
||||
self._resid = resid
|
||||
self._k = k
|
||||
self._mle_retvals = mle_retvals
|
||||
|
||||
@property
|
||||
def aic(self):
|
||||
"""
|
||||
The Akaike information criterion.
|
||||
"""
|
||||
return self._aic
|
||||
|
||||
@property
|
||||
def aicc(self):
|
||||
"""
|
||||
AIC with a correction for finite sample sizes.
|
||||
"""
|
||||
return self._aicc
|
||||
|
||||
@property
|
||||
def bic(self):
|
||||
"""
|
||||
The Bayesian information criterion.
|
||||
"""
|
||||
return self._bic
|
||||
|
||||
@property
|
||||
def sse(self):
|
||||
"""
|
||||
The sum of squared errors between the data and the fittted value.
|
||||
"""
|
||||
return self._sse
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
"""
|
||||
The model used to produce the results instance.
|
||||
"""
|
||||
return self._model
|
||||
|
||||
@model.setter
|
||||
def model(self, value):
|
||||
self._model = value
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
"""
|
||||
An array of the levels values that make up the fitted values.
|
||||
"""
|
||||
return self._level
|
||||
|
||||
@property
|
||||
def optimized(self):
|
||||
"""
|
||||
Flag indicating if model parameters were optimized to fit the data.
|
||||
"""
|
||||
return self._optimized
|
||||
|
||||
@property
|
||||
def trend(self):
|
||||
"""
|
||||
An array of the trend values that make up the fitted values.
|
||||
"""
|
||||
return self._trend
|
||||
|
||||
@property
|
||||
def season(self):
|
||||
"""
|
||||
An array of the seasonal values that make up the fitted values.
|
||||
"""
|
||||
return self._season
|
||||
|
||||
@property
|
||||
def params_formatted(self):
|
||||
"""
|
||||
DataFrame containing all parameters
|
||||
|
||||
Contains short names and a flag indicating whether the parameter's
|
||||
value was optimized to fit the data.
|
||||
"""
|
||||
return self._params_formatted
|
||||
|
||||
@property
|
||||
def fittedvalues(self):
|
||||
"""
|
||||
An array of the fitted values
|
||||
"""
|
||||
return self._fittedvalues
|
||||
|
||||
@property
|
||||
def fittedfcast(self):
|
||||
"""
|
||||
An array of both the fitted values and forecast values.
|
||||
"""
|
||||
return self._fittedfcast
|
||||
|
||||
@property
|
||||
def fcastvalues(self):
|
||||
"""
|
||||
An array of the forecast values
|
||||
"""
|
||||
return self._fcastvalues
|
||||
|
||||
@property
|
||||
def resid(self):
|
||||
"""
|
||||
An array of the residuals of the fittedvalues and actual values.
|
||||
"""
|
||||
return self._resid
|
||||
|
||||
@property
|
||||
def k(self):
|
||||
"""
|
||||
The k parameter used to remove the bias in AIC, BIC etc.
|
||||
"""
|
||||
return self._k
|
||||
|
||||
@property
|
||||
def mle_retvals(self):
|
||||
"""
|
||||
Optimization results if the parameters were optimized to fit the data.
|
||||
"""
|
||||
return self._mle_retvals
|
||||
|
||||
@mle_retvals.setter
|
||||
def mle_retvals(self, value):
|
||||
self._mle_retvals = value
|
||||
|
||||
def predict(self, start=None, end=None):
|
||||
"""
|
||||
In-sample prediction and out-of-sample forecasting
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : int, str, or datetime, optional
|
||||
Zero-indexed observation number at which to start forecasting, ie.,
|
||||
the first forecast is start. Can also be a date string to
|
||||
parse or a datetime type. Default is the the zeroth observation.
|
||||
end : int, str, or datetime, optional
|
||||
Zero-indexed observation number at which to end forecasting, ie.,
|
||||
the first forecast is start. Can also be a date string to
|
||||
parse or a datetime type. However, if the dates index does not
|
||||
have a fixed frequency, end must be an integer index if you
|
||||
want out of sample prediction. Default is the last observation in
|
||||
the sample.
|
||||
|
||||
Returns
|
||||
-------
|
||||
forecast : ndarray
|
||||
Array of out of sample forecasts.
|
||||
"""
|
||||
return self.model.predict(self.params, start, end)
|
||||
|
||||
def forecast(self, steps=1):
|
||||
"""
|
||||
Out-of-sample forecasts
|
||||
|
||||
Parameters
|
||||
----------
|
||||
steps : int
|
||||
The number of out of sample forecasts from the end of the
|
||||
sample.
|
||||
|
||||
Returns
|
||||
-------
|
||||
forecast : ndarray
|
||||
Array of out of sample forecasts
|
||||
"""
|
||||
try:
|
||||
freq = getattr(self.model._index, "freq", 1)
|
||||
if not isinstance(freq, int) and isinstance(
|
||||
self.model._index, (pd.DatetimeIndex, pd.PeriodIndex)
|
||||
):
|
||||
start = self.model._index[-1] + freq
|
||||
end = self.model._index[-1] + steps * freq
|
||||
else:
|
||||
start = self.model._index.shape[0]
|
||||
end = start + steps - 1
|
||||
return self.model.predict(self.params, start=start, end=end)
|
||||
except AttributeError:
|
||||
# May occur when the index does not have a freq
|
||||
return self.model._predict(h=steps, **self.params).fcastvalues
|
||||
|
||||
def summary(self):
|
||||
"""
|
||||
Summarize the fitted Model
|
||||
|
||||
Returns
|
||||
-------
|
||||
smry : Summary instance
|
||||
This holds the summary table and text, which can be printed or
|
||||
converted to various output formats.
|
||||
|
||||
See Also
|
||||
--------
|
||||
statsmodels.iolib.summary.Summary
|
||||
"""
|
||||
from statsmodels.iolib.summary import Summary
|
||||
from statsmodels.iolib.table import SimpleTable
|
||||
|
||||
model = self.model
|
||||
title = model.__class__.__name__ + " Model Results"
|
||||
|
||||
dep_variable = "endog"
|
||||
orig_endog = self.model.data.orig_endog
|
||||
if isinstance(orig_endog, pd.DataFrame):
|
||||
dep_variable = orig_endog.columns[0]
|
||||
elif isinstance(orig_endog, pd.Series):
|
||||
dep_variable = orig_endog.name
|
||||
seasonal_periods = (
|
||||
None
|
||||
if self.model.seasonal is None
|
||||
else self.model.seasonal_periods
|
||||
)
|
||||
lookup = {
|
||||
"add": "Additive",
|
||||
"additive": "Additive",
|
||||
"mul": "Multiplicative",
|
||||
"multiplicative": "Multiplicative",
|
||||
None: "None",
|
||||
}
|
||||
transform = self.params["use_boxcox"]
|
||||
box_cox_transform = True if transform else False
|
||||
box_cox_coeff = (
|
||||
transform if isinstance(transform, str) else self.params["lamda"]
|
||||
)
|
||||
if isinstance(box_cox_coeff, float):
|
||||
box_cox_coeff = f"{box_cox_coeff:>10.5f}"
|
||||
top_left = [
|
||||
("Dep. Variable:", [dep_variable]),
|
||||
("Model:", [model.__class__.__name__]),
|
||||
("Optimized:", [str(np.any(self.optimized))]),
|
||||
("Trend:", [lookup[self.model.trend]]),
|
||||
("Seasonal:", [lookup[self.model.seasonal]]),
|
||||
("Seasonal Periods:", [str(seasonal_periods)]),
|
||||
("Box-Cox:", [str(box_cox_transform)]),
|
||||
("Box-Cox Coeff.:", [str(box_cox_coeff)]),
|
||||
]
|
||||
|
||||
top_right = [
|
||||
("No. Observations:", [str(len(self.model.endog))]),
|
||||
("SSE", [f"{self.sse:5.3f}"]),
|
||||
("AIC", [f"{self.aic:5.3f}"]),
|
||||
("BIC", [f"{self.bic:5.3f}"]),
|
||||
("AICC", [f"{self.aicc:5.3f}"]),
|
||||
("Date:", None),
|
||||
("Time:", None),
|
||||
]
|
||||
|
||||
smry = Summary()
|
||||
smry.add_table_2cols(
|
||||
self, gleft=top_left, gright=top_right, title=title
|
||||
)
|
||||
formatted = self.params_formatted # type: pd.DataFrame
|
||||
|
||||
def _fmt(x):
|
||||
abs_x = np.abs(x)
|
||||
scale = 1
|
||||
if np.isnan(x):
|
||||
return f"{str(x):>20}"
|
||||
if abs_x != 0:
|
||||
scale = int(np.log10(abs_x))
|
||||
if scale > 4 or scale < -3:
|
||||
return f"{x:>20.5g}"
|
||||
dec = min(7 - scale, 7)
|
||||
fmt = f"{{:>20.{dec}f}}"
|
||||
return fmt.format(x)
|
||||
|
||||
tab = []
|
||||
for _, vals in formatted.iterrows():
|
||||
tab.append(
|
||||
[
|
||||
_fmt(vals.iloc[1]),
|
||||
f"{vals.iloc[0]:>20}",
|
||||
f"{str(bool(vals.iloc[2])):>20}",
|
||||
]
|
||||
)
|
||||
params_table = SimpleTable(
|
||||
tab,
|
||||
headers=["coeff", "code", "optimized"],
|
||||
title="",
|
||||
stubs=list(formatted.index),
|
||||
)
|
||||
|
||||
smry.tables.append(params_table)
|
||||
|
||||
return smry
|
||||
|
||||
def simulate(
|
||||
self,
|
||||
nsimulations,
|
||||
anchor=None,
|
||||
repetitions=1,
|
||||
error="add",
|
||||
random_errors=None,
|
||||
random_state=None,
|
||||
):
|
||||
r"""
|
||||
Random simulations using the state space formulation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsimulations : int
|
||||
The number of simulation steps.
|
||||
anchor : int, str, or datetime, optional
|
||||
First period for simulation. The simulation will be conditional on
|
||||
all existing datapoints prior to the `anchor`. Type depends on the
|
||||
index of the given `endog` in the model. Two special cases are the
|
||||
strings 'start' and 'end'. `start` refers to beginning the
|
||||
simulation at the first period of the sample, and `end` refers to
|
||||
beginning the simulation at the first period after the sample.
|
||||
Integer values can run from 0 to `nobs`, or can be negative to
|
||||
apply negative indexing. Finally, if a date/time index was provided
|
||||
to the model, then this argument can be a date string to parse or a
|
||||
datetime type. Default is 'end'.
|
||||
repetitions : int, optional
|
||||
Number of simulated paths to generate. Default is 1 simulated path.
|
||||
error : {"add", "mul", "additive", "multiplicative"}, optional
|
||||
Error model for state space formulation. Default is ``"add"``.
|
||||
random_errors : optional
|
||||
Specifies how the random errors should be obtained. Can be one of
|
||||
the following:
|
||||
|
||||
* ``None``: Random normally distributed values with variance
|
||||
estimated from the fit errors drawn from numpy's standard
|
||||
RNG (can be seeded with the `random_state` argument). This is the
|
||||
default option.
|
||||
* A distribution function from ``scipy.stats``, e.g.
|
||||
``scipy.stats.norm``: Fits the distribution function to the fit
|
||||
errors and draws from the fitted distribution.
|
||||
Note the difference between ``scipy.stats.norm`` and
|
||||
``scipy.stats.norm()``, the latter one is a frozen distribution
|
||||
function.
|
||||
* A frozen distribution function from ``scipy.stats``, e.g.
|
||||
``scipy.stats.norm(scale=2)``: Draws from the frozen distribution
|
||||
function.
|
||||
* A ``np.ndarray`` with shape (`nsimulations`, `repetitions`): Uses
|
||||
the given values as random errors.
|
||||
* ``"bootstrap"``: Samples the random errors from the fit errors.
|
||||
|
||||
random_state : int or np.random.RandomState, optional
|
||||
A seed for the random number generator or a
|
||||
``np.random.RandomState`` object. Only used if `random_errors` is
|
||||
``None``. Default is ``None``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
sim : pd.Series, pd.DataFrame or np.ndarray
|
||||
An ``np.ndarray``, ``pd.Series``, or ``pd.DataFrame`` of simulated
|
||||
values.
|
||||
If the original data was a ``pd.Series`` or ``pd.DataFrame``, `sim`
|
||||
will be a ``pd.Series`` if `repetitions` is 1, and a
|
||||
``pd.DataFrame`` of shape (`nsimulations`, `repetitions`) else.
|
||||
Otherwise, if `repetitions` is 1, a ``np.ndarray`` of shape
|
||||
(`nsimulations`,) is returned, and if `repetitions` is not 1 a
|
||||
``np.ndarray`` of shape (`nsimulations`, `repetitions`) is
|
||||
returned.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The simulation is based on the state space model of the Holt-Winter's
|
||||
methods. The state space model assumes that the true value at time
|
||||
:math:`t` is randomly distributed around the prediction value.
|
||||
If using the additive error model, this means:
|
||||
|
||||
.. math::
|
||||
|
||||
y_t &= \hat{y}_{t|t-1} + e_t\\
|
||||
e_t &\sim \mathcal{N}(0, \sigma^2)
|
||||
|
||||
Using the multiplicative error model:
|
||||
|
||||
.. math::
|
||||
|
||||
y_t &= \hat{y}_{t|t-1} \cdot (1 + e_t)\\
|
||||
e_t &\sim \mathcal{N}(0, \sigma^2)
|
||||
|
||||
Inserting these equations into the smoothing equation formulation leads
|
||||
to the state space equations. The notation used here follows
|
||||
[1]_.
|
||||
|
||||
Additionally,
|
||||
|
||||
.. math::
|
||||
|
||||
B_t &= b_{t-1} \circ_d \phi\\
|
||||
L_t &= l_{t-1} \circ_b B_t\\
|
||||
S_t &= s_{t-m}\\
|
||||
Y_t &= L_t \circ_s S_t,
|
||||
|
||||
where :math:`\circ_d` is the operation linking trend and damping
|
||||
parameter (multiplication if the trend is additive, power if the trend
|
||||
is multiplicative), :math:`\circ_b` is the operation linking level and
|
||||
trend (addition if the trend is additive, multiplication if the trend
|
||||
is multiplicative), and :math:`\circ_s` is the operation linking
|
||||
seasonality to the rest.
|
||||
|
||||
The state space equations can then be formulated as
|
||||
|
||||
.. math::
|
||||
|
||||
y_t &= Y_t + \eta \cdot e_t\\
|
||||
l_t &= L_t + \alpha \cdot (M_e \cdot L_t + \kappa_l) \cdot e_t\\
|
||||
b_t &= B_t + \beta \cdot (M_e \cdot B_t + \kappa_b) \cdot e_t\\
|
||||
s_t &= S_t + \gamma \cdot (M_e \cdot S_t + \kappa_s) \cdot e_t\\
|
||||
|
||||
with
|
||||
|
||||
.. math::
|
||||
|
||||
\eta &= \begin{cases}
|
||||
Y_t\quad\text{if error is multiplicative}\\
|
||||
1\quad\text{else}
|
||||
\end{cases}\\
|
||||
M_e &= \begin{cases}
|
||||
1\quad\text{if error is multiplicative}\\
|
||||
0\quad\text{else}
|
||||
\end{cases}\\
|
||||
|
||||
and, when using the additive error model,
|
||||
|
||||
.. math::
|
||||
|
||||
\kappa_l &= \begin{cases}
|
||||
\frac{1}{S_t}\quad
|
||||
\text{if seasonality is multiplicative}\\
|
||||
1\quad\text{else}
|
||||
\end{cases}\\
|
||||
\kappa_b &= \begin{cases}
|
||||
\frac{\kappa_l}{l_{t-1}}\quad
|
||||
\text{if trend is multiplicative}\\
|
||||
\kappa_l\quad\text{else}
|
||||
\end{cases}\\
|
||||
\kappa_s &= \begin{cases}
|
||||
\frac{1}{L_t}\quad\text{if seasonality is
|
||||
multiplicative}\\
|
||||
1\quad\text{else}
|
||||
\end{cases}
|
||||
|
||||
When using the multiplicative error model
|
||||
|
||||
.. math::
|
||||
|
||||
\kappa_l &= \begin{cases}
|
||||
0\quad
|
||||
\text{if seasonality is multiplicative}\\
|
||||
S_t\quad\text{else}
|
||||
\end{cases}\\
|
||||
\kappa_b &= \begin{cases}
|
||||
\frac{\kappa_l}{l_{t-1}}\quad
|
||||
\text{if trend is multiplicative}\\
|
||||
\kappa_l + l_{t-1}\quad\text{else}
|
||||
\end{cases}\\
|
||||
\kappa_s &= \begin{cases}
|
||||
0\quad\text{if seasonality is multiplicative}\\
|
||||
L_t\quad\text{else}
|
||||
\end{cases}
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Hyndman, R.J., & Athanasopoulos, G. (2018) *Forecasting:
|
||||
principles and practice*, 2nd edition, OTexts: Melbourne,
|
||||
Australia. OTexts.com/fpp2. Accessed on February 28th 2020.
|
||||
"""
|
||||
|
||||
# check inputs
|
||||
if error in ["additive", "multiplicative"]:
|
||||
error = {"additive": "add", "multiplicative": "mul"}[error]
|
||||
if error not in ["add", "mul"]:
|
||||
raise ValueError("error must be 'add' or 'mul'!")
|
||||
|
||||
# Get the starting location
|
||||
if anchor is None or anchor == "end":
|
||||
start_idx = self.model.nobs
|
||||
elif anchor == "start":
|
||||
start_idx = 0
|
||||
else:
|
||||
start_idx, _, _ = self.model._get_index_loc(anchor)
|
||||
if isinstance(start_idx, slice):
|
||||
start_idx = start_idx.start
|
||||
if start_idx < 0:
|
||||
start_idx += self.model.nobs
|
||||
if start_idx > self.model.nobs:
|
||||
raise ValueError("Cannot anchor simulation outside of the sample.")
|
||||
|
||||
# get Holt-Winters settings and parameters
|
||||
trend = self.model.trend
|
||||
damped = self.model.damped_trend
|
||||
seasonal = self.model.seasonal
|
||||
use_boxcox = self.params["use_boxcox"]
|
||||
lamda = self.params["lamda"]
|
||||
alpha = self.params["smoothing_level"]
|
||||
beta = self.params["smoothing_trend"]
|
||||
gamma = self.params["smoothing_seasonal"]
|
||||
phi = self.params["damping_trend"]
|
||||
# if model has no seasonal component, use 1 as period length
|
||||
m = max(self.model.seasonal_periods, 1)
|
||||
n_params = (
|
||||
2
|
||||
+ 2 * self.model.has_trend
|
||||
+ (m + 1) * self.model.has_seasonal
|
||||
+ damped
|
||||
)
|
||||
mul_seasonal = seasonal == "mul"
|
||||
mul_trend = trend == "mul"
|
||||
mul_error = error == "mul"
|
||||
|
||||
# define trend, damping and seasonality operations
|
||||
if mul_trend:
|
||||
op_b = np.multiply
|
||||
op_d = np.power
|
||||
neutral_b = 1
|
||||
else:
|
||||
op_b = np.add
|
||||
op_d = np.multiply
|
||||
neutral_b = 0
|
||||
if mul_seasonal:
|
||||
op_s = np.multiply
|
||||
neutral_s = 1
|
||||
else:
|
||||
op_s = np.add
|
||||
neutral_s = 0
|
||||
|
||||
# set initial values
|
||||
level = self.level
|
||||
_trend = self.trend
|
||||
season = self.season
|
||||
# (notation as in https://otexts.com/fpp2/ets.html)
|
||||
y = np.empty((nsimulations, repetitions))
|
||||
# lvl instead of l because of E741
|
||||
lvl = np.empty((nsimulations + 1, repetitions))
|
||||
b = np.empty((nsimulations + 1, repetitions))
|
||||
s = np.empty((nsimulations + m, repetitions))
|
||||
# the following uses python's index wrapping
|
||||
if start_idx == 0:
|
||||
lvl[-1, :] = self.params["initial_level"]
|
||||
b[-1, :] = self.params["initial_trend"]
|
||||
else:
|
||||
lvl[-1, :] = level[start_idx - 1]
|
||||
b[-1, :] = _trend[start_idx - 1]
|
||||
if 0 <= start_idx and start_idx <= m:
|
||||
initial_seasons = self.params["initial_seasons"]
|
||||
_s = np.concatenate(
|
||||
(initial_seasons[start_idx:], season[:start_idx])
|
||||
)
|
||||
s[-m:, :] = np.tile(_s, (repetitions, 1)).T
|
||||
else:
|
||||
s[-m:, :] = np.tile(
|
||||
season[start_idx - m : start_idx], (repetitions, 1)
|
||||
).T
|
||||
|
||||
# set neutral values for unused features
|
||||
if trend is None:
|
||||
b[:, :] = neutral_b
|
||||
phi = 1
|
||||
beta = 0
|
||||
if seasonal is None:
|
||||
s[:, :] = neutral_s
|
||||
gamma = 0
|
||||
if not damped:
|
||||
phi = 1
|
||||
|
||||
# calculate residuals for error covariance estimation
|
||||
if use_boxcox:
|
||||
fitted = boxcox(self.fittedvalues, lamda)
|
||||
else:
|
||||
fitted = self.fittedvalues
|
||||
if error == "add":
|
||||
resid = self.model._y - fitted
|
||||
else:
|
||||
resid = (self.model._y - fitted) / fitted
|
||||
sigma = np.sqrt(np.sum(resid**2) / (len(resid) - n_params))
|
||||
|
||||
# get random error eps
|
||||
if isinstance(random_errors, np.ndarray):
|
||||
if random_errors.shape != (nsimulations, repetitions):
|
||||
raise ValueError(
|
||||
"If random_errors is an ndarray, it must have shape "
|
||||
"(nsimulations, repetitions)"
|
||||
)
|
||||
eps = random_errors
|
||||
elif random_errors == "bootstrap":
|
||||
eps = np.random.choice(
|
||||
resid, size=(nsimulations, repetitions), replace=True
|
||||
)
|
||||
elif random_errors is None:
|
||||
if random_state is None:
|
||||
eps = np.random.randn(nsimulations, repetitions) * sigma
|
||||
elif isinstance(random_state, int):
|
||||
rng = np.random.RandomState(random_state)
|
||||
eps = rng.randn(nsimulations, repetitions) * sigma
|
||||
elif isinstance(random_state, np.random.RandomState):
|
||||
eps = random_state.randn(nsimulations, repetitions) * sigma
|
||||
else:
|
||||
raise ValueError(
|
||||
"Argument random_state must be None, an integer, "
|
||||
"or an instance of np.random.RandomState"
|
||||
)
|
||||
elif isinstance(random_errors, (rv_continuous, rv_discrete)):
|
||||
params = random_errors.fit(resid)
|
||||
eps = random_errors.rvs(*params, size=(nsimulations, repetitions))
|
||||
elif isinstance(random_errors, rv_frozen):
|
||||
eps = random_errors.rvs(size=(nsimulations, repetitions))
|
||||
else:
|
||||
raise ValueError("Argument random_errors has unexpected value!")
|
||||
|
||||
for t in range(nsimulations):
|
||||
b0 = op_d(b[t - 1, :], phi)
|
||||
l0 = op_b(lvl[t - 1, :], b0)
|
||||
s0 = s[t - m, :]
|
||||
y0 = op_s(l0, s0)
|
||||
if error == "add":
|
||||
eta = 1
|
||||
kappa_l = 1 / s0 if mul_seasonal else 1
|
||||
kappa_b = kappa_l / lvl[t - 1, :] if mul_trend else kappa_l
|
||||
kappa_s = 1 / l0 if mul_seasonal else 1
|
||||
else:
|
||||
eta = y0
|
||||
kappa_l = 0 if mul_seasonal else s0
|
||||
kappa_b = (
|
||||
kappa_l / lvl[t - 1, :]
|
||||
if mul_trend
|
||||
else kappa_l + lvl[t - 1, :]
|
||||
)
|
||||
kappa_s = 0 if mul_seasonal else l0
|
||||
|
||||
y[t, :] = y0 + eta * eps[t, :]
|
||||
lvl[t, :] = l0 + alpha * (mul_error * l0 + kappa_l) * eps[t, :]
|
||||
b[t, :] = b0 + beta * (mul_error * b0 + kappa_b) * eps[t, :]
|
||||
s[t, :] = s0 + gamma * (mul_error * s0 + kappa_s) * eps[t, :]
|
||||
|
||||
if use_boxcox:
|
||||
y = inv_boxcox(y, lamda)
|
||||
|
||||
sim = np.atleast_1d(np.squeeze(y))
|
||||
if y.shape[0] == 1 and y.size > 1:
|
||||
sim = sim[None, :]
|
||||
# Wrap data / squeeze where appropriate
|
||||
if not isinstance(self.model.data, PandasData):
|
||||
return sim
|
||||
|
||||
_, _, _, index = self.model._get_prediction_index(
|
||||
start_idx, start_idx + nsimulations - 1
|
||||
)
|
||||
if repetitions == 1:
|
||||
sim = pd.Series(sim, index=index, name=self.model.endog_names)
|
||||
else:
|
||||
sim = pd.DataFrame(sim, index=index)
|
||||
|
||||
return sim
|
||||
|
||||
|
||||
class HoltWintersResultsWrapper(ResultsWrapper):
|
||||
_attrs = {
|
||||
"fittedvalues": "rows",
|
||||
"level": "rows",
|
||||
"resid": "rows",
|
||||
"season": "rows",
|
||||
"trend": "rows",
|
||||
"slope": "rows",
|
||||
}
|
||||
_wrap_attrs = union_dicts(ResultsWrapper._wrap_attrs, _attrs)
|
||||
_methods = {"predict": "dates", "forecast": "dates"}
|
||||
_wrap_methods = union_dicts(ResultsWrapper._wrap_methods, _methods)
|
||||
|
||||
|
||||
populate_wrapper(HoltWintersResultsWrapper, HoltWintersResults)
|
||||
Reference in New Issue
Block a user