Example #1
0
    def fit(self, y_train, fh=None, X_train=None):
        """Fit to training data.

        Parameters
        ----------
        y_train : pd.Series
            Target time series to which to fit the forecaster.
        fh : int, list or np.array, optional (default=None)
            The forecasters horizon with the steps ahead to to predict.
        X_train : pd.DataFrame, optional (default=None)
            Exogenous variables are ignored
        Returns
        -------
        self : returns an instance of self.
        """
        sp = check_sp(self.sp)
        if sp > 1 and not self.deseasonalise:
            warn("`sp` is ignored when `deseasonalise`=False")

        if self.deseasonalise:
            self.deseasonaliser_ = Deseasonalizer(sp=self.sp,
                                                  model="multiplicative")
            y_train = self.deseasonaliser_.fit_transform(y_train)

        # fit exponential smoothing forecaster
        # find theta lines: Theta lines are just SES + drift
        super(ThetaForecaster, self).fit(y_train, fh=fh)
        self.smoothing_level_ = self._fitted_forecaster.params[
            "smoothing_level"]

        # compute trend
        self.trend_ = self._compute_trend(y_train)
        self._is_fitted = True
        return self
Example #2
0
def test_deseasonalised_values(sp):
    t = Deseasonalizer(sp=sp)
    t.fit(y_train)
    a = t.transform(y_train)

    r = seasonal_decompose(y_train, period=sp)
    b = y_train - r.seasonal
    np.testing.assert_array_equal(a.values, b.values)
Example #3
0
def test_deseasonalised_values(sp):
    transformer = Deseasonalizer(sp=sp)
    transformer.fit(y_train)
    actual = transformer.transform(y_train)

    r = seasonal_decompose(y_train, period=sp)
    expected = y_train - r.seasonal
    np.testing.assert_array_equal(actual, expected)
Example #4
0
    def compute_expected_y_pred(y_train, fh):
        # fitting
        yt = y_train.copy()
        t1 = Deseasonalizer(sp=12, model="multiplicative")
        yt = t1.fit_transform(yt)
        t2 = Detrender(PolynomialTrendForecaster(degree=1))
        yt = t2.fit_transform(yt)
        f = NaiveForecaster()
        f.fit(yt, fh)

        # predicting
        y_pred = f.predict()
        y_pred = t2.inverse_transform(y_pred)
        y_pred = t1.inverse_transform(y_pred)
        return y_pred
Example #5
0
def test_pipeline():
    y = load_airline()
    y_train, y_test = temporal_train_test_split(y)

    f = TransformedTargetForecaster([
        ("t1", Deseasonalizer(sp=12, model="multiplicative")),
        ("t2", Detrender(PolynomialTrendForecaster(degree=1))),
        ("f", NaiveForecaster())
    ])
    fh = np.arange(len(y_test)) + 1
    f.fit(y_train, fh)
    actual = f.predict()

    def compute_expected_y_pred(y_train, fh):
        # fitting
        yt = y_train.copy()
        t1 = Deseasonalizer(sp=12, model="multiplicative")
        yt = t1.fit_transform(yt)
        t2 = Detrender(PolynomialTrendForecaster(degree=1))
        yt = t2.fit_transform(yt)
        f = NaiveForecaster()
        f.fit(yt, fh)

        # predicting
        y_pred = f.predict()
        y_pred = t2.inverse_transform(y_pred)
        y_pred = t1.inverse_transform(y_pred)
        return y_pred

    expected = compute_expected_y_pred(y_train, fh)
    np.testing.assert_array_equal(actual, expected)
Example #6
0
def learn(series_data):
    model = TransformedTargetForecaster([
        ("deseasonalise", Deseasonalizer(model="multiplicative", sp=7)),
        ("detrend", Detrender(forecaster=PolynomialTrendForecaster(degree=4))),
        ("forecast", PolynomialTrendForecaster(degree=4))
    ])
    model.fit(series_data[:-2])
    return model
Example #7
0
class ThetaForecaster(ExponentialSmoothing):
    """
    Theta method of forecasting.

    The theta method as defined in [1]_ is equivalent to simple exponential
    smoothing
    (SES) with drift. This is demonstrated in [2]_.

    The series is tested for seasonality using the test outlined in A&N. If
    deemed
    seasonal, the series is seasonally adjusted using a classical
    multiplicative
    decomposition before applying the theta method. The resulting forecasts
    are then
    reseasonalised.

    In cases where SES results in a constant forecast, the theta forecaster
    will revert
    to predicting the SES constant plus a linear trend derived from the
    training data.

    Prediction intervals are computed using the underlying state space model.

    Parameters
    ----------

    smoothing_level : float, optional
        The alpha value of the simple exponential smoothing, if the value is
        set then
        this will be used, otherwise it will be estimated from the data.

    deseasonalise : bool, optional (default=True)
        If True, data is seasonally adjusted.

    sp : int, optional (default=1)
        The number of observations that constitute a seasonal period for a
        multiplicative deseasonaliser, which is used if seasonality is
        detected in the
        training data. Ignored if a deseasonaliser transformer is provided.
        Default is
        1 (no seasonality).

    Attributes
    ----------

    smoothing_level_ : float
        The estimated alpha value of the SES fit.

    drift_ : float
        The estimated drift of the fitted model.

    se_ : float
        The standard error of the predictions. Used to calculate prediction
        intervals.

    References
    ----------

    .. [1] `Assimakopoulos, V. and Nikolopoulos, K. The theta model: a
    decomposition
           approach to forecasting. International Journal of Forecasting 16,
           521-530,
           2000.
           <https://www.sciencedirect.com/science/article/pii
           /S0169207000000662>`_

    .. [2] `Hyndman, Rob J., and Billah, Baki. Unmasking the Theta method.
           International J. Forecasting, 19, 287-290, 2003.
           <https://www.sciencedirect.com/science/article/pii
           /S0169207001001431>`_
    """

    _fitted_param_names = ("initial_level", "smoothing_level")

    def __init__(self, smoothing_level=None, deseasonalise=True, sp=1):

        self.sp = sp
        self.deseasonalise = deseasonalise

        self.deseasonaliser_ = None
        self.trend_ = None
        self.smoothing_level_ = None
        self.drift_ = None
        self.se_ = None
        super(ThetaForecaster, self).__init__(smoothing_level=smoothing_level,
                                              sp=sp)

    def fit(self, y_train, fh=None, X_train=None):
        """Fit to training data.

        Parameters
        ----------
        y_train : pd.Series
            Target time series to which to fit the forecaster.
        fh : int, list or np.array, optional (default=None)
            The forecasters horizon with the steps ahead to to predict.
        X_train : pd.DataFrame, optional (default=None)
            Exogenous variables are ignored
        Returns
        -------
        self : returns an instance of self.
        """
        sp = check_sp(self.sp)
        if sp > 1 and not self.deseasonalise:
            warn("`sp` is ignored when `deseasonalise`=False")

        if self.deseasonalise:
            self.deseasonaliser_ = Deseasonalizer(sp=self.sp,
                                                  model="multiplicative")
            y_train = self.deseasonaliser_.fit_transform(y_train)

        # fit exponential smoothing forecaster
        # find theta lines: Theta lines are just SES + drift
        super(ThetaForecaster, self).fit(y_train, fh=fh)
        self.smoothing_level_ = self._fitted_forecaster.params[
            "smoothing_level"]

        # compute trend
        self.trend_ = self._compute_trend(y_train)
        self._is_fitted = True
        return self

    def _predict(self, fh, X=None, return_pred_int=False, alpha=DEFAULT_ALPHA):
        """
        Make forecasts.

        Parameters
        ----------

        fh : array-like
            The forecasters horizon with the steps ahead to to predict.
            Default is
            one-step ahead forecast, i.e. np.array([1]).

        Returns
        -------

        y_pred : pandas.Series
            Returns series of predicted values.
        """
        y_pred = super(ThetaForecaster, self)._predict(fh, X=X,
                                                       return_pred_int=False,
                                                       alpha=alpha)

        # Add drift.
        drift = self._compute_drift()
        y_pred += drift

        if self.deseasonalise:
            y_pred = self.deseasonaliser_.inverse_transform(y_pred)

        if return_pred_int:
            pred_int = self.compute_pred_int(y_pred=y_pred, alpha=alpha)
            return y_pred, pred_int

        return y_pred

    @staticmethod
    def _compute_trend(y):
        # Trend calculated through least squares regression.
        coefs = fit_trend(y.values.reshape(1, -1), order=1)
        return coefs[0, 0] / 2

    def _compute_drift(self):
        if np.isclose(self.smoothing_level_, 0.0):
            # SES was constant, so revert to simple trend
            drift = self.trend_ * self.fh
        else:
            # Calculate drift from SES parameters
            n_timepoints = len(self._y)
            drift = self.trend_ * (
                    self.fh
                    + (1 - (
                        1 - self.smoothing_level_) ** n_timepoints) /
                    self.smoothing_level_
            )

        return drift

    def _compute_pred_err(self, alphas):
        """
        Get the prediction errors for the forecast.
        """
        self.check_is_fitted()

        n_timepoints = len(self._y)

        self.sigma_ = np.sqrt(self._fitted_forecaster.sse / (n_timepoints - 1))
        sem = self.sigma_ * np.sqrt(self._fh * self.smoothing_level_ ** 2 + 1)

        errors = []
        for alpha in alphas:
            z = zscore(1 - alpha)
            error = z * sem
            errors.append(
                pd.Series(error, index=self.fh.absolute(self.cutoff)))

        return errors

    def update(self, y_new, X_new=None, update_params=True):
        super(ThetaForecaster, self).update(y_new, X_new=X_new,
                                            update_params=update_params)
        if update_params:
            if self.deseasonalise:
                y_new = self.deseasonaliser_.transform(y_new)
            self.smoothing_level_ = self._fitted_forecaster.params[
                "smoothing_level"]
            self.trend_ = self._compute_trend(y_new)
        return self
Example #8
0
# In[126]:

model = PolynomialTrendForecaster(degree=1)
transformer = Detrender(model)

yt = transformer.fit_transform(train)
trendline = model.fit(train).predict(fh=-np.arange(len(train)))

plot_ys(train, trendline, yt, labels=['series', 'trend', 'detrended'])

# ### Pipelining

# In[130]:

forecaster = TransformedTargetForecaster([
    ("deseasonalise", Deseasonalizer(model="multiplicative", sp=12)),
    ("detrend", Detrender(forecaster=PolynomialTrendForecaster(degree=1))),
    ("forecast",
     ReducedRegressionForecaster(regressor=regressor,
                                 window_length=12,
                                 strategy="recursive"))
])
forecaster.fit(train)
y_pred = forecaster.predict(fh)
plot_ys(train, test, y_pred, labels=["y_train", "y_test", "y_pred"])
smape_loss(test, y_pred)

# ## WORK IN PROGRESS

# ## Pipeline Tuning
Example #9
0
def test_transform_inverse_transform_equivalence(sp, model):
    t = Deseasonalizer(sp=sp, model=model)
    t.fit(y_train)
    yit = t.inverse_transform(t.transform(y_train))
    np.testing.assert_array_equal(y_train.index, yit.index)
    np.testing.assert_almost_equal(y_train.values, yit.values)
Example #10
0
def test_inverse_transform_time_index(sp, model):
    t = Deseasonalizer(sp=sp, model=model)
    t.fit(y_train)
    yit = t.inverse_transform(y_test)
    np.testing.assert_array_equal(yit.index, y_test.index)
Example #11
0
def test_transform_time_index(sp, model):
    transformer = Deseasonalizer(sp=sp, model=model)
    transformer.fit(y_train)
    yt = transformer.transform(y_test)
    np.testing.assert_array_equal(yt.index, y_test.index)