sktime.transformations.series.detrend._detrend 源代码

#!/usr/bin/env python3 -u
# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
"""Implements transformations to detrend a time series."""

__all__ = ["Detrender"]
__author__ = ["mloning", "SveaMeyer13", "KishManani", "fkiraly"]

import pandas as pd

from sktime.datatypes import update_data
from sktime.forecasting.base._fh import ForecastingHorizon
from sktime.forecasting.trend import PolynomialTrendForecaster
from sktime.transformations.base import BaseTransformer


[文档]class Detrender(BaseTransformer): """Remove a :term:`trend <Trend>` from a series. This transformer uses any forecaster and returns the in-sample residuals of the forecaster's predicted values. The Detrender works as follows: in "fit", the forecaster is fit to the input data, i.e., ``forecaster.fit(y=X)``. in "transform", returns forecast residuals of forecasts at the data index. That is, ``transform(X)`` returns ``X - forecaster.predict(fh=X.index)`` (additive) or ``X / forecaster.predict(fh=X.index)`` (multiplicative detrending). Depending on time indices, this can generate in-sample or out-of-sample residuals. For example, to remove the linear trend of a time series: forecaster = PolynomialTrendForecaster(degree=1) transformer = Detrender(forecaster=forecaster) yt = transformer.fit_transform(y_train) The detrender can also be used in a pipeline for residual boosting, by first detrending and then fitting another forecaster on residuals. Parameters ---------- forecaster : sktime forecaster, follows BaseForecaster, default = None. The forecasting model to remove the trend with (e.g. PolynomialTrendForecaster). If forecaster is None, PolynomialTrendForecaster(degree=1) is used. Must be a forecaster to which ``fh`` can be passed in ``predict``. model : {"additive", "multiplicative"}, default="additive" If ``model="additive"`` the ``forecaster.transform`` subtracts the trend, i.e., ``transform(X)`` returns ``X - forecaster.predict(fh=X.index)`` If ``model="multiplicative"`` the ``forecaster.transform`` divides by the trend, i.e., ``transform(X)`` returns ``X / forecaster.predict(fh=X.index)`` Attributes ---------- forecaster_ : Fitted forecaster Forecaster that defines the trend in the series. See Also -------- Deseasonalizer STLTransformer Examples -------- >>> from sktime.transformations.series.detrend import Detrender >>> from sktime.forecasting.trend import PolynomialTrendForecaster >>> from sktime.datasets import load_airline >>> y = load_airline() >>> transformer = Detrender(forecaster=PolynomialTrendForecaster(degree=1)) >>> y_hat = transformer.fit_transform(y) """ _tags = { # packaging info # -------------- "authors": ["mloning", "SveaMeyer13", "KishManani", "fkiraly"], "maintainers": ["SveaMeyer13", "KishManani"], # estimator type # -------------- "scitype:transform-input": "Series", # what is the scitype of X: Series, or Panel "scitype:transform-output": "Series", # what scitype is returned: Primitives, Series, Panel "scitype:instancewise": True, # is this an instance-wise transform? "X_inner_mtype": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"], # which mtypes do _fit/_predict support for X? "y_inner_mtype": ["pd.DataFrame", "pd-multiindex", "pd_multiindex_hier"], # which mtypes do _fit/_predict support for y? "univariate-only": False, "fit_is_empty": False, "capability:inverse_transform": True, "transform-returns-same-time-index": True, } def __init__(self, forecaster=None, model="additive"): self.forecaster = forecaster self.model = model super().__init__() # default for forecaster - written to forecaster_ to not overwrite param if self.forecaster is None: self.forecaster_ = PolynomialTrendForecaster(degree=1) else: self.forecaster_ = forecaster.clone() allowed_models = ("additive", "multiplicative") if model not in allowed_models: raise ValueError("`model` must be 'additive' or 'multiplicative'") def _fit(self, X, y=None): """Fit transformer to X and y. private _fit containing the core logic, called from fit Parameters ---------- X : pd.Series or pd.DataFrame Data to fit transform to y : pd.DataFrame, default=None Additional data, e.g., labels for transformation Returns ------- self: a fitted instance of the estimator """ if not self.forecaster_.get_tag("requires-fh-in-fit", True): self.forecaster_.fit(y=X, X=y) else: self._X = X self._y = y return self def _get_fh_from_X(self, X): """Obtain fh from X, which can be simple or hierarchical.""" if not isinstance(X.index, pd.MultiIndex): time_index = X.index else: time_index = X.index.get_level_values(-1).unique() return ForecastingHorizon(time_index, is_relative=False) def _get_fitted_forecaster(self, X, y, fh): """Obtain fitted forecaster from self.""" if self.forecaster_.get_tag("requires-fh-in-fit", True): X = update_data(self._X, X) y = update_data(self._y, y) forecaster = self.forecaster_.clone().fit(y=X, X=y, fh=fh) else: forecaster = self.forecaster_ return forecaster def _transform(self, X, y=None): """Transform X and return a transformed version. private _transform containing the core logic, called from transform Parameters ---------- X : pd.Series or pd.DataFrame Data to be transformed y : pd.DataFrame, default=None Additional data, e.g., labels for transformation Returns ------- Xt : pd.Series or pd.DataFrame, same type as X transformed version of X, detrended series """ fh = self._get_fh_from_X(X=X) forecaster = self._get_fitted_forecaster(X=X, y=y, fh=fh) X_pred = forecaster.predict(fh=fh, X=y) if self.model == "additive": return X - X_pred elif self.model == "multiplicative": return X / X_pred def _inverse_transform(self, X, y=None): """Logic used by ``inverse_transform`` to reverse transformation on ``X``. Parameters ---------- X : pd.Series or pd.DataFrame Data to be inverse transformed y : pd.DataFrame, default=None Additional data, e.g., labels for transformation Returns ------- Xt : pd.Series or pd.DataFrame, same type as X inverse transformed version of X """ fh = self._get_fh_from_X(X=X) # we pass X and y as None, since the X passed is inverse transformed (detrended) # the fit, in case fh needs be passed late, is done on remembered data from fit forecaster = self._get_fitted_forecaster(X=None, y=None, fh=fh) X_pred = forecaster.predict(fh=fh, X=y) if self.model == "additive": return X + X_pred elif self.model == "multiplicative": return X * X_pred def _update(self, X, y=None, update_params=True): """Update the parameters of the detrending estimator with new data. private _update containing the core logic, called from update Parameters ---------- X : pd.Series or pd.DataFrame Data to fit transform to y : pd.DataFrame, default=None Additional data, e.g., labels for transformation update_params : bool, default=True whether the model is updated. Yes if true, if false, simply skips call. argument exists for compatibility with forecasting module. Returns ------- self : an instance of self """ if not self.forecaster_.get_tag("requires-fh-in-fit", True): self.forecaster_.update(y=X, X=y, update_params=update_params) else: self._X = update_data(self._X, X) self._y = update_data(self._y, y) return self
[文档] @classmethod def get_test_params(cls, parameter_set="default"): """Return testing parameter settings for the estimator. Parameters ---------- parameter_set : str, default="default" Name of the set of test parameters to return, for use in tests. If no special parameters are defined for a value, will return ``"default"`` set. Returns ------- params : dict or list of dict, default = {} Parameters to create testing instances of the class Each dict are parameters to construct an "interesting" test instance, i.e., ``MyClass(**params)`` or ``MyClass(**params[i])`` creates a valid test instance. ``create_test_instance`` uses the first (or only) dictionary in ``params`` """ from sktime.forecasting.trend import TrendForecaster params1 = {"forecaster": TrendForecaster()} params2 = {"model": "multiplicative"} return [params1, params2]