Skip to content

Commit

Permalink
Fix minor silent issues in TransformedTargetForecaster (#845)
Browse files Browse the repository at this point in the history
* Fix minor silent issues

* Apply inverse transform to pred_int

* Fix pydocstyle

Co-authored-by: Walter <walmar2@emea.corpdir.net>
  • Loading branch information
Martin Walter and Walter committed Aug 13, 2021
1 parent 6ee3a7a commit 582cc66
Showing 1 changed file with 88 additions and 29 deletions.
117 changes: 88 additions & 29 deletions sktime/forecasting/compose/_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class _Pipeline(
_HeterogenousMetaEstimator,
):
def _check_steps(self):
"""Check Steps
"""Check Steps.
Parameters
----------
Expand Down Expand Up @@ -70,23 +70,45 @@ def _iter_transformers(self, reverse=False):
yield idx, name, transformer

def __len__(self):
"""
Returns the length of the Pipeline
"""
"""Return the length of the Pipeline."""
return len(self.steps)

def _get_inverse_transform(self, y):
"""Iterate over transformers.
Inverse transform y (used for y_pred and pred_int)
Parameters
----------
y : pd.Series
Returns
-------
y : pd.Series
Inverse transformed y
"""
for _, _, transformer in self._iter_transformers(reverse=True):
# skip sktime transformers where inverse transform
# is not wanted ur meaningful (e.g. Imputer, HampelFilter)
skip_trafo = transformer.get_tag("skip-inverse-transform", False)
if not skip_trafo:
y = transformer.inverse_transform(y)
return y

@property
def named_steps(self):
"""Map the steps to a dictionary"""
"""Map the steps to a dictionary."""
return dict(self.steps)

def get_params(self, deep=True):
"""Get parameters for this estimator.
Parameters
----------
deep : boolean, optional
If True, will return the parameters for this estimator and
contained subobjects that are estimators.
Returns
-------
params : mapping of string to any
Expand All @@ -96,7 +118,9 @@ def get_params(self, deep=True):

def set_params(self, **kwargs):
"""Set the parameters of this estimator.
Valid parameter keys can be listed with ``get_params()``.
Returns
-------
self
Expand All @@ -106,12 +130,12 @@ def set_params(self, **kwargs):


class ForecastingPipeline(_Pipeline):
"""
Pipeline for forecasting with exogenous data to apply transformers
to the exogenous serieses. The forecaster can also be a
TransformedTargetForecaster containing transformers to
transform y. ForecastingPipeline is only applying the given transformers
to X.
"""Meta-estimator for forecasting with exogenous data.
ForecastingPipeline is apply transformers to the exogenous serieses.
The given forecaster as last step can also be a TransformedTargetForecaster
containing transformers to transform y. ForecastingPipeline is only applying
the given transformers to X.
Parameters
----------
Expand Down Expand Up @@ -207,16 +231,17 @@ def _predict(self, fh=None, X=None, return_pred_int=False, alpha=DEFAULT_ALPHA):
Prediction intervals
"""
forecaster = self.steps_[-1][1]

# If X is not given, just passthrough the data without transformation
if self._X is not None:
# transform X before doing prediction
for _, _, transformer in self._iter_transformers():
X = transformer.transform(X)
y_pred = forecaster.predict(fh, X, return_pred_int=return_pred_int, alpha=alpha)
return y_pred

return forecaster.predict(fh, X, return_pred_int=return_pred_int, alpha=alpha)

def _update(self, y, X=None, update_params=True):
"""Update fitted parameters
"""Update fitted parameters.
Parameters
----------
Expand Down Expand Up @@ -259,9 +284,11 @@ def _update(self, y, X=None, update_params=True):


class TransformedTargetForecaster(_Pipeline, _SeriesToSeriesTransformer):
"""
Meta-estimator for forecasting transformed time series.
Pipeline functionality to apply transformers to the target series.
"""Meta-estimator for forecasting transformed time series.
Pipeline functionality to apply transformers to the target series. The
X data is not transformed. If you want to transform X, please use the
ForecastingPipeline.
Parameters
----------
Expand Down Expand Up @@ -317,7 +344,7 @@ def _fit(self, y, X=None, fh=None):
# transform
for step_idx, name, transformer in self._iter_transformers():
t = clone(transformer)
y = t.fit_transform(y)
y = t.fit_transform(y, X)
self.steps_[step_idx] = (name, t)

# fit forecaster
Expand Down Expand Up @@ -348,18 +375,26 @@ def _predict(self, fh=None, X=None, return_pred_int=False, alpha=DEFAULT_ALPHA):
Prediction intervals
"""
forecaster = self.steps_[-1][1]
y_pred = forecaster.predict(fh, X, return_pred_int=return_pred_int, alpha=alpha)

for _, _, transformer in self._iter_transformers(reverse=True):
# skip sktime transformers where inverse transform
# is not wanted ur meaningful (e.g. Imputer, HampelFilter)
skip_trafo = transformer.get_tag("skip-inverse-transform", False)
if not skip_trafo:
y_pred = transformer.inverse_transform(y_pred)
return y_pred
if return_pred_int:
y_pred, pred_int = forecaster.predict(
fh, X, return_pred_int=return_pred_int, alpha=alpha
)
# inverse transform pred_int
pred_int["lower"] = self._get_inverse_transform(pred_int["lower"])
pred_int["upper"] = self._get_inverse_transform(pred_int["upper"])
else:
y_pred = forecaster.predict(
fh, X, return_pred_int=return_pred_int, alpha=alpha
)
# inverse transform y_pred
y_pred = self._get_inverse_transform(y_pred)
if return_pred_int:
return y_pred, pred_int
else:
return y_pred

def _update(self, y, X=None, update_params=True):
"""Update fitted parameters
"""Update fitted parameters.
Parameters
----------
Expand All @@ -371,7 +406,6 @@ def _update(self, y, X=None, update_params=True):
-------
self : an instance of self
"""

for step_idx, name, transformer in self._iter_transformers():
if hasattr(transformer, "update"):
transformer.update(y, X, update_params=update_params)
Expand All @@ -383,13 +417,38 @@ def _update(self, y, X=None, update_params=True):
return self

def transform(self, Z, X=None):
"""Transform data.
Returns a transformed version of Z.
Parameters
----------
Z : pd.Series, pd.DataFrame
Returns
-------
Z : pd.Series, pd.DataFrame
Transformed time series(es).
"""
self.check_is_fitted()
zt = check_series(Z, enforce_univariate=True)
for _, _, transformer in self._iter_transformers():
zt = transformer.transform(zt, X)
return zt

def inverse_transform(self, Z, X=None):
"""Reverse transformation on input series `Z`.
Parameters
----------
Z : pd.Series or pd.DataFrame
A time series to reverse the transformation on.
Returns
-------
Z_inv : pd.Series or pd.DataFrame
The reconstructed timeseries after the transformation has been reversed.
"""
self.check_is_fitted()
zt = check_series(Z, enforce_univariate=True)
for _, _, transformer in self._iter_transformers(reverse=True):
Expand Down

0 comments on commit 582cc66

Please sign in to comment.