diff --git a/sktime/forecasting/compose/_pipeline.py b/sktime/forecasting/compose/_pipeline.py index cff5b8071ac..72807ea3e94 100644 --- a/sktime/forecasting/compose/_pipeline.py +++ b/sktime/forecasting/compose/_pipeline.py @@ -19,7 +19,7 @@ class _Pipeline( _HeterogenousMetaEstimator, ): def _check_steps(self): - """Check Steps + """Check Steps. Parameters ---------- @@ -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 @@ -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 @@ -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 ---------- @@ -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 ---------- @@ -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 ---------- @@ -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 @@ -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 ---------- @@ -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) @@ -383,6 +417,19 @@ 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(): @@ -390,6 +437,18 @@ def transform(self, Z, X=None): 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):