sktime.forecasting.compose._skforecast_reduce 源代码

# copyright: sktime developers, BSD-3-Clause License (see LICENSE file)
"""Implements back-adapters for skforecast reduction models."""

from collections.abc import Callable
from typing import Optional, Union

import numpy as np
import pandas as pd

from sktime.forecasting.base import BaseForecaster, ForecastingHorizon

__author__ = ["Abhay-Lejith", "yarnabrina"]


[文档]class SkforecastAutoreg(BaseForecaster): """Adapter for ``skforecast.ForecasterAutoreg.ForecasterAutoreg`` class [1]_. Parameters ---------- regressor : regressor or pipeline compatible with the scikit-learn API An instance of a regressor or pipeline compatible with the scikit-learn API lags : int, list, numpy ndarray, range Lags used as predictors. Index starts at 1, so lag 1 is equal to t-1. - ``int``: include lags from 1 to ``lags`` (included). - ``list``, ``1d numpy ndarray`` or ``range``: include only lags present in ``lags``, all elements must be int. transformer_y : object transformer (preprocessor), default ``None`` An instance of a transformer (preprocessor) compatible with the scikit-learn preprocessing API with methods: ``fit``, ``transform``, ``fit_transform`` and ``inverse_transform``. ``ColumnTransformers`` are not allowed since they do not have ``inverse_transform`` method. The transformation is applied to ``y`` before training the forecaster. transformer_exog : object transformer (preprocessor), default ``None`` An instance of a transformer (preprocessor) compatible with the scikit-learn preprocessing API. The transformation is applied to ``exog`` before training the forecaster. ``inverse_transform`` is not available when using ``ColumnTransformers``. weight_func : Callable, default ``None`` Function that defines the individual weights for each sample based on the index. For example, a function that assigns a lower weight to certain dates. Ignored if ``regressor`` does not have the argument ``sample_weight`` in its ``fit`` method. The resulting ``sample_weight`` cannot have negative values. differentiation : int, default ``None`` Order of differencing applied to the time series before training the forecaster. If ``None``, no differencing is applied. The order of differentiation is the number of times the differencing operation is applied to a time series. Differencing involves computing the differences between consecutive data points in the series. Differentiation is reversed in the output of ``predict()`` and ``predict_interval()``. fit_kwargs : dict, default ``None`` Additional arguments to be passed to the ``fit`` method of the regressor. binner_kwargs : dict, default `None` Additional arguments to pass to the `KBinsDiscretizer` used to discretize the residuals into k bins according to the predicted values associated with each residual. The `encode' argument is always set to 'ordinal' and `dtype' to np.float64. References ---------- .. [1] https://skforecast.org/latest/api/forecasterautoreg#forecasterautoreg Examples -------- >>> from sktime.forecasting.compose import SkforecastAutoreg Without exogenous features >>> from sklearn.linear_model import LinearRegression >>> from sktime.datasets import load_airline >>> y = load_airline() >>> forecaster = SkforecastAutoreg( # doctest: +SKIP ... LinearRegression(), 2 ... ) >>> forecaster.fit(y) # doctest: +SKIP SkforecastAutoreg(lags=2, regressor=LinearRegression()) >>> y_pred = forecaster.predict(fh=[1, 2, 3]) # doctest: +SKIP >>> y_pred_int = forecaster.predict_interval( # doctest: +SKIP ... fh=[2], coverage=[0.9, 0.95] ... ) >>> y_pred_qtl = forecaster.predict_quantiles( # doctest: +SKIP ... fh=[1, 3], alpha=[0.8, 0.3, 0.2, 0.7] ... ) With exogenous features >>> from sklearn.ensemble import RandomForestRegressor >>> from sktime.datasets import load_longley >>> y, X = load_longley() >>> y_train = y.head(n=12) >>> y_test = y.tail(n=4) >>> X_train = X.head(n=12) >>> X_test = X.tail(n=4) >>> forecaster = SkforecastAutoreg( # doctest: +SKIP ... RandomForestRegressor(), [2, 4] ... ) >>> forecaster.fit(y_train, X=X_train) # doctest: +SKIP SkforecastAutoreg(lags=[2, 4], regressor=RandomForestRegressor()) >>> y_pred = forecaster.predict(fh=[1, 2, 3], X=X_test) # doctest: +SKIP >>> y_pred_int = forecaster.predict_interval( # doctest: +SKIP ... fh=[1, 3], X=X_test, coverage=[0.6, 0.4] ... ) >>> y_pred_qtl = forecaster.predict_quantiles( # doctest: +SKIP ... fh=[1, 3], X=X_test, alpha=[0.01, 0.5] ... ) """ _tags = { # packaging info # -------------- "authors": [ "JoaquinAmatRodrigo", "JavierEscobarOrtiz", "FernandoCarazoMelo", "fernando-carazo", "Abhay-Lejith", "yarnabrina", ], # JoaquinAmatRodrigo, JavierEscobarOrtiz, FernandoCarazoMelo for skforecast "maintainers": ["Abhay-Lejith", "yarnabrina"], # estimator type # -------------- "y_inner_mtype": "pd.Series", "X_inner_mtype": "pd.DataFrame", "requires-fh-in-fit": False, "handles-missing-data": False, "capability:insample": False, "capability:pred_int": True, "capability:pred_int:insample": False, "capability:categorical_in_X": True, "python_version": ">=3.8,<3.13", "python_dependencies": ["skforecast>=0.12.1"], } def __init__( self: "SkforecastAutoreg", regressor: object, lags: Union[int, np.ndarray, list], transformer_y: Optional[object] = None, transformer_exog: Optional[object] = None, weight_func: Optional[Callable] = None, differentiation: Optional[int] = None, fit_kwargs: Optional[dict] = None, binner_kwargs: Optional[dict] = None, ) -> None: self.regressor = regressor self.lags = lags self.transformer_y = transformer_y self.transformer_exog = transformer_exog self.weight_func = weight_func self.differentiation = differentiation self.fit_kwargs = fit_kwargs self.binner_kwargs = binner_kwargs super().__init__() self._regressor = None self._forecaster = None self._transformer_y = None self._transformer_exog = None self._clone_estimators() def _clone_estimators(self: "SkforecastAutoreg"): """Clone the regressor and transformers.""" from sklearn.base import clone self._regressor = clone(self.regressor) if self.transformer_y: self._transformer_y = clone(self.transformer_y) if self.transformer_exog: self._transformer_exog = clone(self.transformer_exog) def _create_forecaster(self: "SkforecastAutoreg"): """Create ``skforecast.ForecasterAutoreg.ForecasterAutoreg`` model.""" from skforecast.ForecasterAutoreg import ForecasterAutoreg return ForecasterAutoreg( self._regressor, self.lags, transformer_y=self._transformer_y, transformer_exog=self._transformer_exog, weight_func=self.weight_func, differentiation=self.differentiation, fit_kwargs=self.fit_kwargs, binner_kwargs=self.binner_kwargs, ) @staticmethod def _coerce_column_names(X: Optional[pd.DataFrame]): if X is None: return None return X.rename(columns=lambda column_name: str(column_name)) @staticmethod def _coerce_int_to_range_index(df): new_df = df.copy(deep=True) start = new_df.index[0] stop = new_df.index[-1] + 1 if len(new_df.index) == 1: step = 1 else: step = new_df.index[1] - start new_index = pd.RangeIndex(start, stop, step) # testing if RangeIndex is matching the original integer Index. # this will fail if indices are not equally spaced apart. # exception is caught in _make_index_compatible. np.testing.assert_array_equal(new_df.index, new_index) # assigning RangeIndex to the new DataFrame new_df.index = new_index return new_df @staticmethod def _coerce_period_to_datetime_index(df): new_df = df.copy(deep=True) period_freq = new_df.index.freq # converting the period index to the timestamp at the 'end' of the period # Example: Period Datetime # '2024-01-12 00:00' --> '2024-01-12 00:59:59.999999999' # '2024-01-12 01:00' --> '2024-01-12 01:59:59.999999999' new_df.index = new_df.index.to_timestamp(how="e") new_df.index.freq = period_freq return new_df def _make_index_compatible(self, df, input_var): if df is None: return None if isinstance(df.index, (pd.RangeIndex, pd.DatetimeIndex)): return df if isinstance(df.index, pd.PeriodIndex): new_df = self._coerce_period_to_datetime_index(df) elif pd.api.types.is_integer_dtype(df.index): try: new_df = self._coerce_int_to_range_index(df) except AssertionError: raise ValueError( f"Coercion of index of {input_var} from integer pd.Index to " "pd.RangeIndex failed. Please ensure that indexes are equally " "spaced apart." ) else: raise ValueError( f"{input_var} must have one of the following index types: " "pd.RangeIndex, pd.DatetimeIndex, pd.PeriodIndex, pd.Index" f"(int dtype). Found index of type: {type(df.index)}" ) return new_df def _fit( self: "SkforecastAutoreg", y: pd.Series, X: Optional[pd.DataFrame], fh: Optional[ForecastingHorizon], ): """Fit forecaster to training data. private _fit containing the core logic, called from fit Writes to self: Sets fitted model attributes ending in "_". Parameters ---------- y : pd.Series Target time series to which to fit the forecaster. fh : guaranteed to be ForecastingHorizon or None, optional (default=None) The forecasting horizon with the steps ahead to to predict. Required (non-optional) here. X : pd.DataFrame, optional (default=None) Exogeneous time series to fit to. Returns ------- self : reference to self """ del fh # avoid being detected as unused by ``vulture`` like tools self._forecaster = self._create_forecaster() # skforecast does not support PeriodIndex and Integer Index. # So converting to supported index types here if necessary. y_new = self._make_index_compatible(y, "y") X_new = self._make_index_compatible(X, "X") self._forecaster.fit(y_new, exog=self._coerce_column_names(X_new)) return self def _get_horizon_details( self: "SkforecastAutoreg", fh: Optional[ForecastingHorizon] ): if not fh.is_all_out_of_sample(self.cutoff): raise NotImplementedError( f"{self.__class__.__name__} does not support in-sample predictions." ) out_of_sample_horizon = fh.to_out_of_sample(self.cutoff) maximum_forecast_horizon = out_of_sample_horizon.to_relative(self.cutoff)[-1] absolute_horizons = out_of_sample_horizon.to_absolute_index(self.cutoff) horizon_positions = out_of_sample_horizon.to_indexer(self.cutoff) return maximum_forecast_horizon, absolute_horizons, horizon_positions def _predict( self: "SkforecastAutoreg", fh: Optional[ForecastingHorizon], X: Optional[pd.DataFrame], ): """Forecast time series at future horizon. private _predict containing the core logic, called from predict State required: Requires state to be "fitted". Accesses in self: Fitted model attributes ending in "_" self.cutoff Parameters ---------- fh : guaranteed to be ForecastingHorizon or None, optional (default=None) The forecasting horizon with the steps ahead to to predict. X : pd.DataFrame, optional (default=None) Exogenous time series Returns ------- y_pred : pd.DataFrame Point predictions """ ( maximum_forecast_horizon, absolute_horizons, horizon_positions, ) = self._get_horizon_details(fh) X_new = self._make_index_compatible(X, "X") point_predictions = self._forecaster.predict( maximum_forecast_horizon, exog=self._coerce_column_names(X_new) ).to_numpy() final_point_predictions = pd.Series( point_predictions[horizon_positions], index=absolute_horizons, name=None if self._y.name is None else str(self._y.name), ) return final_point_predictions def _predict_quantiles( self: "SkforecastAutoreg", fh: Optional[ForecastingHorizon], X: Optional[pd.DataFrame], alpha: list[float], ): """Compute/return prediction quantiles for a forecast. private _predict_quantiles containing the core logic, called from predict_quantiles and possibly predict_interval State required: Requires state to be "fitted". Accesses in self: Fitted model attributes ending in "_" self.cutoff Parameters ---------- fh : guaranteed to be ForecastingHorizon or None, optional (default=None) The forecasting horizon with the steps ahead to to predict. X : sktime time series object, optional (default=None) guaranteed to be of an mtype in self.get_tag("X_inner_mtype") Exogeneous time series for the forecast alpha : list of float (guaranteed not None and floats in [0,1] interval) A list of probabilities at which quantile forecasts are computed. Returns ------- quantiles : pd.DataFrame Column has multi-index: first level is variable name from y in fit, second level being the values of alpha passed to the function. Row index is fh, with additional (upper) levels equal to instance levels, from y seen in fit, if y_inner_mtype is Panel or Hierarchical. Entries are quantile forecasts, for var in col index, at quantile probability in second col index, for the row index. """ ( maximum_forecast_horizon, absolute_horizons, horizon_positions, ) = self._get_horizon_details(fh) var_names = self._get_varnames() var_name = var_names[0] quantile_predictions_indices = pd.MultiIndex.from_product([var_names, alpha]) quantile_predictions = pd.DataFrame( index=absolute_horizons, columns=quantile_predictions_indices ) X_new = self._make_index_compatible(X, "X") quantile_pred = self._forecaster.predict_quantiles( maximum_forecast_horizon, exog=self._coerce_column_names(X_new), quantiles=alpha, ) for quantile in alpha: quantile_predictions[(var_name, quantile)] = ( quantile_pred[f"q_{quantile}"].iloc[horizon_positions].to_list() ) return quantile_predictions
[文档] @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. There are currently no reserved values for forecasters. 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` """ del parameter_set # to avoid being detected as unused by ``vulture`` etc. from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler param1 = { "regressor": LinearRegression(), "lags": 2, "transformer_exog": StandardScaler(), } param2 = { "regressor": RandomForestRegressor(), "lags": [1, 3], "differentiation": 2, } return [param1, param2]
__all__ = ["SkforecastAutoreg"]