From a6b1a2bdd5bacd696415978e9f2c25494506d9f9 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Tue, 8 Feb 2022 17:10:08 +0100 Subject: [PATCH 01/32] [ADD] EnbPI Remark: "artifical" alpha=0.5 in partial_fit. Should change regression.py to avoid it --- HISTORY.rst | 1 + mapie/regression.py | 1 + mapie/tests/test_time_series_regression.py | 36 ++++ mapie/time_series_regression.py | 190 +++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 mapie/tests/test_time_series_regression.py create mode 100644 mapie/time_series_regression.py diff --git a/HISTORY.rst b/HISTORY.rst index a56d5d1f7..d2aee8b2e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -14,6 +14,7 @@ History "predict" in regression.py * Add replication of the Chen Xu's tutorial testing Jackknife+aB vs Jackknife+ * Add Jackknife+-after-Bootstrap documentation +* Add EnbPI method for Time Series Regression 0.3.1 (2021-11-19) ------------------ diff --git a/mapie/regression.py b/mapie/regression.py index 12f910cf5..674816ec3 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -182,6 +182,7 @@ class MapieRegressor(BaseEstimator, RegressorMixin): # type: ignore >>> print(y_pred) [ 5.28571429 7.17142857 9.05714286 10.94285714 12.82857143 14.71428571] """ + valid_methods_ = ["naive", "base", "plus", "minmax"] valid_agg_functions_ = [None, "median", "mean"] fit_attributes = [ diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py new file mode 100644 index 000000000..94411ed5b --- /dev/null +++ b/mapie/tests/test_time_series_regression.py @@ -0,0 +1,36 @@ +import numpy as np +from sklearn.linear_model import LinearRegression + +from mapie.time_series_regression import MapieTimeSeriesRegressor + + +def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: + """Test ``residuals_`` when ``ensemble`` is True.""" + X_toy = np.array([[0], [1], [2], [3], [4], [5]]) + y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) + mapie_ts_reg = MapieTimeSeriesRegressor(LinearRegression(), cv=-1).fit( + X_toy, y_toy + ) + assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) + mapie_ts_reg = mapie_ts_reg.partial_fit( + X=np.array([[6]]), y=np.array([17.5]), ensemble=True + ) + assert round(mapie_ts_reg.residuals_[-1], 2) == round( + np.abs(17.5 - 16.56), 2 + ) + + +def test_MapieTimeSeriesRegressor_partial_fit_ensemble_F() -> None: + """Test ``residuals_`` when ``ensemble`` is False.""" + X_toy = np.array([[0], [1], [2], [3], [4], [5]]) + y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) + mapie_ts_reg = MapieTimeSeriesRegressor(LinearRegression(), cv=-1).fit( + X_toy, y_toy + ) + assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) + mapie_ts_reg = mapie_ts_reg.partial_fit( + X=np.array([[6]]), y=np.array([17.5]), ensemble=False + ) + assert round(mapie_ts_reg.residuals_[-1], 2) == round( + np.abs(17.5 - 16.6), 2 + ) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py new file mode 100644 index 000000000..450841028 --- /dev/null +++ b/mapie/time_series_regression.py @@ -0,0 +1,190 @@ +from __future__ import annotations + +from typing import Optional, Union + +import numpy as np +from sklearn.base import RegressorMixin +from sklearn.model_selection import BaseCrossValidator + +from .regression import MapieRegressor +from ._typing import ArrayLike + + +class MapieTimeSeriesRegressor(MapieRegressor): + """ + Prediction interval with out-of-fold residuals for time series. + + This class implements the EnbPI strategy and some variations + for estimating prediction intervals on single-output time series. + It is ``MapieReegressor`` with one more method ``partial_fit``. + Actually, EnbPI only corresponds to MapieRegressor if the ``cv`` argument + if of type ``Subsample`` (Jackknife+-after-Bootstrap method). Moreover, for + the moment we consider the absolute values of the residuals of the model, + and consequently the prediction intervals are symetryc. + + Parameters + ---------- + estimator : Optional[RegressorMixin] + Any regressor with scikit-learn API + (i.e. with fit and predict methods), by default ``None``. + If ``None``, estimator defaults to a ``LinearRegression`` instance. + + method: str, optional + Method to choose for prediction interval estimates. + Choose among: + + - "naive", based on training set residuals, + - "base", based on validation sets residuals, + - "plus", based on validation residuals and testing predictions, + - "minmax", based on validation residuals and testing predictions + (min/max among cross-validation clones). + + By default "plus". + + cv: Optional[Union[int, str, BaseCrossValidator]] + The cross-validation strategy for computing residuals. + It directly drives the distinction between jackknife and cv variants. + Choose among: + + - ``None``, to use the default 5-fold cross-validation + - integer, to specify the number of folds. + If equal to -1, equivalent to + ``sklearn.model_selection.LeaveOneOut()``. + - CV splitter: any ``sklearn.model_selection.BaseCrossValidator`` + Main variants are: + - ``sklearn.model_selection.LeaveOneOut`` (jackknife), + - ``sklearn.model_selection.KFold`` (cross-validation), + - ``subsample.Subsample`` object (bootstrap). + - ``"prefit"``, assumes that ``estimator`` has been fitted already, + and the ``method`` parameter is ignored. + All data provided in the ``fit`` method is then used + for computing residuals only. + At prediction time, quantiles of these residuals are used to provide + a prediction interval with fixed width. + The user has to take care manually that data for model fitting and + residual estimate are disjoint. + + By default ``None``. + + n_jobs: Optional[int] + Number of jobs for parallel processing using joblib + via the "locky" backend. + If ``-1`` all CPUs are used. + If ``1`` is given, no parallel computing code is used at all, + which is useful for debugging. + For n_jobs below ``-1``, ``(n_cpus + 1 - n_jobs)`` are used. + None is a marker for `unset` that will be interpreted as ``n_jobs=1`` + (sequential execution). + + By default ``None``. + + agg_function : str + Determines how to aggregate predictions from perturbed models, both at + training and prediction time. + + If ``None``, it is ignored except if cv class is ``Subsample``, + in which case an error is raised. + If "mean" or "median", returns the mean or median of the predictions + computed from the out-of-folds models. + Note: if you plan to set the ``ensemble`` argument to ``True`` in the + ``predict`` method, you have to specify an aggregation function. + Otherwise an error would be raised. + + The Jackknife+ interval can be interpreted as an interval around the + median prediction, and is guaranteed to lie inside the interval, + unlike the single estimator predictions. + + When the cross-validation strategy is Subsample (i.e. for the + Jackknife+-after-Bootstrap method), this function is also used to + aggregate the training set in-sample predictions. + + If cv is ``"prefit"``, ``agg_function`` is ignored. + + By default "mean". + + verbose : int, optional + The verbosity level, used with joblib for multiprocessing. + The frequency of the messages increases with the verbosity level. + If it more than ``10``, all iterations are reported. + Above ``50``, the output is sent to stdout. + + By default ``0``. + + Attributes + ---------- + valid_methods: List[str] + List of all valid methods. + + single_estimator_ : sklearn.RegressorMixin + Estimator fitted on the whole training set. + + estimators_ : list + List of out-of-folds estimators. + + residuals_ : ArrayLike of shape (n_samples_train,) + Residuals between ``y_train`` and ``y_pred``. + + k_ : ArrayLike + - Array of nans, of shape (len(y), 1) if cv is ``"prefit"`` + (defined but not used) + - Dummy array of folds containing each training sample, otherwise. + Of shape (n_samples_train, cv.get_n_splits(X_train, y_train)). + + n_features_in_: int + Number of features passed to the fit method. + + n_samples_val_: List[int] + Number of samples passed to the fit method. + + References + ---------- + Chen Xu, and Yao Xie. + "Conformal prediction for dynamic time-series." + """ + + def __init__( + self, + estimator: Optional[RegressorMixin] = None, + method: str = "plus", + cv: Optional[Union[int, str, BaseCrossValidator]] = None, + n_jobs: Optional[int] = None, + agg_function: Optional[str] = "mean", + verbose: int = 0, + ) -> None: + super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) + + def partial_fit( + self, X: ArrayLike, y: ArrayLike, ensemble: bool = True + ) -> MapieTimeSeriesRegressor: + """ + Update the ``residuals_`` attribute when data with known labels are + available + + Parameters + ---------- + X : ArrayLike of shape (n_samples, n_features) + Input data. + + y : ArrayLike of shape (n_samples,) + Input labels. + + ensemble : bool + Boolean corresponing to the ``ensemble`` argument of ``predict`` + method, determining whether the predictions computed to determine + the new ``residuals_`` are ensembled or not. + If False, predictions are those of the model trained on the whole + training set. + + Returns + ------- + MapieTimeSeriesRegressor + The model itself. + """ + y_pred, y_pis = self.predict(X, alpha=0.5, ensemble=ensemble) + new_residuals = np.abs(y - y_pred) + + cut_index = min(len(new_residuals), len(self.residuals_)) + self.residuals_ = np.concatenate( + [self.residuals_[cut_index:], new_residuals], axis=0 + ) + return self From 14552cb99d72dd6e693ce249218caa640da3367b Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Fri, 11 Feb 2022 16:16:55 +0100 Subject: [PATCH 02/32] [CHANGE] partial_fit -> partial_update --- mapie/tests/test_time_series_regression.py | 19 +++++++++---------- mapie/time_series_regression.py | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 94411ed5b..a673d0fb9 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -3,16 +3,17 @@ from mapie.time_series_regression import MapieTimeSeriesRegressor +X_toy = np.array([[0], [1], [2], [3], [4], [5]]) +y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) -def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: - """Test ``residuals_`` when ``ensemble`` is True.""" - X_toy = np.array([[0], [1], [2], [3], [4], [5]]) - y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) + +def test_MapieTimeSeriesRegressor_partial_update_ensemble_T() -> None: + """Test ``partial_update`` when ``ensemble`` is True.""" mapie_ts_reg = MapieTimeSeriesRegressor(LinearRegression(), cv=-1).fit( X_toy, y_toy ) assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) - mapie_ts_reg = mapie_ts_reg.partial_fit( + mapie_ts_reg = mapie_ts_reg.partial_update( X=np.array([[6]]), y=np.array([17.5]), ensemble=True ) assert round(mapie_ts_reg.residuals_[-1], 2) == round( @@ -20,15 +21,13 @@ def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: ) -def test_MapieTimeSeriesRegressor_partial_fit_ensemble_F() -> None: - """Test ``residuals_`` when ``ensemble`` is False.""" - X_toy = np.array([[0], [1], [2], [3], [4], [5]]) - y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) +def test_MapieTimeSeriesRegressor_partial_update_ensemble_F() -> None: + """Test ``partial_update`` when ``ensemble`` is False.""" mapie_ts_reg = MapieTimeSeriesRegressor(LinearRegression(), cv=-1).fit( X_toy, y_toy ) assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) - mapie_ts_reg = mapie_ts_reg.partial_fit( + mapie_ts_reg = mapie_ts_reg.partial_update( X=np.array([[6]]), y=np.array([17.5]), ensemble=False ) assert round(mapie_ts_reg.residuals_[-1], 2) == round( diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 450841028..7722a4583 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -153,12 +153,12 @@ def __init__( ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) - def partial_fit( + def partial_update( self, X: ArrayLike, y: ArrayLike, ensemble: bool = True ) -> MapieTimeSeriesRegressor: """ Update the ``residuals_`` attribute when data with known labels are - available + available. Parameters ---------- @@ -180,7 +180,7 @@ def partial_fit( MapieTimeSeriesRegressor The model itself. """ - y_pred, y_pis = self.predict(X, alpha=0.5, ensemble=ensemble) + y_pred, _ = self.predict(X, alpha=0.5, ensemble=ensemble) new_residuals = np.abs(y - y_pred) cut_index = min(len(new_residuals), len(self.residuals_)) From e23a4d127136f0a5a6616bc8b83d5bcf56532120 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 28 Feb 2022 11:06:49 +0100 Subject: [PATCH 03/32] GMA & VTA's first remarks taken into account --- mapie/tests/test_time_series_regression.py | 17 ++++++----------- mapie/time_series_regression.py | 10 ++++++---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index a673d0fb9..2d7a8ef28 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -1,5 +1,4 @@ import numpy as np -from sklearn.linear_model import LinearRegression from mapie.time_series_regression import MapieTimeSeriesRegressor @@ -7,13 +6,11 @@ y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) -def test_MapieTimeSeriesRegressor_partial_update_ensemble_T() -> None: +def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: """Test ``partial_update`` when ``ensemble`` is True.""" - mapie_ts_reg = MapieTimeSeriesRegressor(LinearRegression(), cv=-1).fit( - X_toy, y_toy - ) + mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) - mapie_ts_reg = mapie_ts_reg.partial_update( + mapie_ts_reg = mapie_ts_reg.partial_fit( X=np.array([[6]]), y=np.array([17.5]), ensemble=True ) assert round(mapie_ts_reg.residuals_[-1], 2) == round( @@ -21,13 +18,11 @@ def test_MapieTimeSeriesRegressor_partial_update_ensemble_T() -> None: ) -def test_MapieTimeSeriesRegressor_partial_update_ensemble_F() -> None: +def test_MapieTimeSeriesRegressor_partial_fit_ensemble_F() -> None: """Test ``partial_update`` when ``ensemble`` is False.""" - mapie_ts_reg = MapieTimeSeriesRegressor(LinearRegression(), cv=-1).fit( - X_toy, y_toy - ) + mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) - mapie_ts_reg = mapie_ts_reg.partial_update( + mapie_ts_reg = mapie_ts_reg.partial_fit( X=np.array([[6]]), y=np.array([17.5]), ensemble=False ) assert round(mapie_ts_reg.residuals_[-1], 2) == round( diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 7722a4583..4540a6dcc 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -16,11 +16,13 @@ class MapieTimeSeriesRegressor(MapieRegressor): This class implements the EnbPI strategy and some variations for estimating prediction intervals on single-output time series. - It is ``MapieReegressor`` with one more method ``partial_fit``. + It is ``MapieRegressor`` with one more method ``partial_fit``. Actually, EnbPI only corresponds to MapieRegressor if the ``cv`` argument if of type ``Subsample`` (Jackknife+-after-Bootstrap method). Moreover, for the moment we consider the absolute values of the residuals of the model, - and consequently the prediction intervals are symetryc. + and consequently the prediction intervals are symmetryc. Moreover we did + not implement the PI's optimization to the oracle interval yet. It is still + a first step before implementing the actual EnbPI. Parameters ---------- @@ -153,7 +155,7 @@ def __init__( ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) - def partial_update( + def partial_fit( self, X: ArrayLike, y: ArrayLike, ensemble: bool = True ) -> MapieTimeSeriesRegressor: """ @@ -169,7 +171,7 @@ def partial_update( Input labels. ensemble : bool - Boolean corresponing to the ``ensemble`` argument of ``predict`` + Boolean corresponding to the ``ensemble`` argument of ``predict`` method, determining whether the predictions computed to determine the new ``residuals_`` are ensembled or not. If False, predictions are those of the model trained on the whole From db21938318b1ec7d95390ed7fa47ad9a24632bde Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 28 Feb 2022 13:30:34 +0100 Subject: [PATCH 04/32] I made a confusion deleting the doc oc regression.py... --- mapie/time_series_regression.py | 114 -------------------------------- 1 file changed, 114 deletions(-) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 4540a6dcc..c69c954f6 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -24,120 +24,6 @@ class MapieTimeSeriesRegressor(MapieRegressor): not implement the PI's optimization to the oracle interval yet. It is still a first step before implementing the actual EnbPI. - Parameters - ---------- - estimator : Optional[RegressorMixin] - Any regressor with scikit-learn API - (i.e. with fit and predict methods), by default ``None``. - If ``None``, estimator defaults to a ``LinearRegression`` instance. - - method: str, optional - Method to choose for prediction interval estimates. - Choose among: - - - "naive", based on training set residuals, - - "base", based on validation sets residuals, - - "plus", based on validation residuals and testing predictions, - - "minmax", based on validation residuals and testing predictions - (min/max among cross-validation clones). - - By default "plus". - - cv: Optional[Union[int, str, BaseCrossValidator]] - The cross-validation strategy for computing residuals. - It directly drives the distinction between jackknife and cv variants. - Choose among: - - - ``None``, to use the default 5-fold cross-validation - - integer, to specify the number of folds. - If equal to -1, equivalent to - ``sklearn.model_selection.LeaveOneOut()``. - - CV splitter: any ``sklearn.model_selection.BaseCrossValidator`` - Main variants are: - - ``sklearn.model_selection.LeaveOneOut`` (jackknife), - - ``sklearn.model_selection.KFold`` (cross-validation), - - ``subsample.Subsample`` object (bootstrap). - - ``"prefit"``, assumes that ``estimator`` has been fitted already, - and the ``method`` parameter is ignored. - All data provided in the ``fit`` method is then used - for computing residuals only. - At prediction time, quantiles of these residuals are used to provide - a prediction interval with fixed width. - The user has to take care manually that data for model fitting and - residual estimate are disjoint. - - By default ``None``. - - n_jobs: Optional[int] - Number of jobs for parallel processing using joblib - via the "locky" backend. - If ``-1`` all CPUs are used. - If ``1`` is given, no parallel computing code is used at all, - which is useful for debugging. - For n_jobs below ``-1``, ``(n_cpus + 1 - n_jobs)`` are used. - None is a marker for `unset` that will be interpreted as ``n_jobs=1`` - (sequential execution). - - By default ``None``. - - agg_function : str - Determines how to aggregate predictions from perturbed models, both at - training and prediction time. - - If ``None``, it is ignored except if cv class is ``Subsample``, - in which case an error is raised. - If "mean" or "median", returns the mean or median of the predictions - computed from the out-of-folds models. - Note: if you plan to set the ``ensemble`` argument to ``True`` in the - ``predict`` method, you have to specify an aggregation function. - Otherwise an error would be raised. - - The Jackknife+ interval can be interpreted as an interval around the - median prediction, and is guaranteed to lie inside the interval, - unlike the single estimator predictions. - - When the cross-validation strategy is Subsample (i.e. for the - Jackknife+-after-Bootstrap method), this function is also used to - aggregate the training set in-sample predictions. - - If cv is ``"prefit"``, ``agg_function`` is ignored. - - By default "mean". - - verbose : int, optional - The verbosity level, used with joblib for multiprocessing. - The frequency of the messages increases with the verbosity level. - If it more than ``10``, all iterations are reported. - Above ``50``, the output is sent to stdout. - - By default ``0``. - - Attributes - ---------- - valid_methods: List[str] - List of all valid methods. - - single_estimator_ : sklearn.RegressorMixin - Estimator fitted on the whole training set. - - estimators_ : list - List of out-of-folds estimators. - - residuals_ : ArrayLike of shape (n_samples_train,) - Residuals between ``y_train`` and ``y_pred``. - - k_ : ArrayLike - - Array of nans, of shape (len(y), 1) if cv is ``"prefit"`` - (defined but not used) - - Dummy array of folds containing each training sample, otherwise. - Of shape (n_samples_train, cv.get_n_splits(X_train, y_train)). - - n_features_in_: int - Number of features passed to the fit method. - - n_samples_val_: List[int] - Number of samples passed to the fit method. - References ---------- Chen Xu, and Yao Xie. From 666b8df52b7a6833fc39146accd6a0ea35f86050 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 28 Feb 2022 18:51:34 +0100 Subject: [PATCH 05/32] ADD examples/regression/plot_timeseries_enbpi.py --- examples/regression/plot_timeseries_enbpi.py | 162 ++++++++++++++++++ .../regression/plot_timeseries_example.py | 2 +- mapie/regression.py | 1 + 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 examples/regression/plot_timeseries_enbpi.py diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py new file mode 100644 index 000000000..a07518584 --- /dev/null +++ b/examples/regression/plot_timeseries_enbpi.py @@ -0,0 +1,162 @@ +""" +================================================================== +Estimating prediction intervals of time series forecast with EnbPI +================================================================== +This example uses +:class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate +prediction intervals associated with time series forecast. The implementation +is still at its first step, based on Jackknife+-after-bootsrtap, to estimate +residuals and associated prediction intervals. + +We use here the Victoria electricity demand dataset used in the book +"Forecasting: Principles and Practice" by R. J. Hyndman and G. Athanasopoulos. +The electricity demand features daily and weekly seasonalities and is impacted +by the temperature, considered here as a exogeneous variable. + +The data is modelled by a Random Forest model with a +:class:`sklearn.model_selection.RandomizedSearchCV` using a sequential +:class:`sklearn.model_selection.TimeSeriesSplit` cross validation, in which the +training set is prior to the validation set. +The best model is then feeded into +:class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the +associated prediction intervals. We compare two approaches: one with no +`partial_fit` call and one with `partial_fit` every 5 steps. +""" +import warnings + +import numpy as np +import pandas as pd +from matplotlib import pylab as plt +from scipy.stats import randint +from sklearn.ensemble import RandomForestRegressor +from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit + +from mapie.metrics import regression_coverage_score +from mapie.subsample import Subsample +from mapie.time_series_regression import MapieTimeSeriesRegressor + +warnings.simplefilter("ignore") + +# Load input data and feature engineering +demand_df = pd.read_csv( + "../data/demand_temperature.csv", parse_dates=True, index_col=0 +) +demand_df["Date"] = pd.to_datetime(demand_df.index) +demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") +demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") +demand_df["Hour"] = demand_df.index.hour +for hour in range(1, 5): + demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) + +# Train/validation/test split +num_test_steps = 24 * 7 * 2 +demand_train = demand_df.iloc[:-num_test_steps, :].copy() +demand_test = demand_df.iloc[-num_test_steps:, :].copy() +features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ + f"Lag_{hour}" for hour in range(1, 5) +] +X_train = demand_train.loc[ + ~np.any(demand_train[features].isnull(), axis=1), features +] +y_train = demand_train.loc[X_train.index, "Demand"] +X_test = demand_test.loc[:, features] +y_test = demand_test["Demand"] + +# CV parameter search +n_iter = 10 +n_splits = 5 +tscv = TimeSeriesSplit(n_splits=n_splits) +random_state = 59 +rf_model = RandomForestRegressor(random_state=random_state) +rf_params = {"max_depth": randint(2, 30), "n_estimators": randint(10, 1e3)} +cv_obj = RandomizedSearchCV( + rf_model, + param_distributions=rf_params, + n_iter=n_iter, + cv=tscv, + scoring="neg_root_mean_squared_error", + random_state=random_state, + verbose=0, + n_jobs=-1, +) +cv_obj.fit(X_train, y_train) +best_est = cv_obj.best_estimator_ + +# Estimate prediction intervals on test set with best estimator +alpha = 0.1 +cv_Mapie = Subsample(30, random_state=random_state) +mapie = MapieTimeSeriesRegressor( + best_est, method="plus", cv=cv_Mapie, agg_function="median", n_jobs=-1 +) +mapie.fit(X_train, y_train) + +# With no partial_fit +y_pred, y_pis = mapie.predict(X_test, alpha=alpha) +coverage = regression_coverage_score(y_test, y_pis[:, 0, 0], y_pis[:, 1, 0]) +width = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() + +# With partial_fit every five hours +y_pred_5_steps, y_pis_5_steps = mapie.predict(X_test.iloc[:5, :], alpha=alpha) + +for step in range(5, len(X_test), 5): + mapie.partial_fit( + X_test.iloc[(step - 5):step, :], y_test.iloc[(step - 5):step] + ) + y_pred_step, y_pis_step = mapie.predict( + X_test.iloc[step:(step + 5), :], alpha=alpha + ) + y_pred_5_steps = np.concatenate((y_pred_5_steps, y_pred_step), axis=0) + y_pis_5_steps = np.concatenate((y_pis_5_steps, y_pis_step), axis=0) + +coverage_5_step = regression_coverage_score( + y_test, y_pis_5_steps[:, 0, 0], y_pis_5_steps[:, 1, 0] +) +width_5_step = (y_pis_5_steps[:, 1, 0] - y_pis_5_steps[:, 0, 0]).mean() + + +# Print results +print( + "Coverage and prediction interval width mean for MapieTimeSeriesRegressor:" + "\nWithout any partial_fit:" + f"{coverage:.3f}, {width:.3f}" +) + +# Plot estimated prediction intervals on test set +fig = plt.figure(figsize=(15, 5)) +ax = fig.add_subplot(1, 1, 1) +ax.set_ylabel("Hourly demand (GW)") +ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +ax.plot(demand_test.index, y_pred, lw=2, c="C2", label="Predictions") +ax.fill_between( + demand_test.index, + y_pis[:, 0, 0], + y_pis[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", +) +ax.legend() +plt.show() + +print( + "Coverage and prediction interval width mean for MapieTimeSeriesRegressor:" + "\nWith partial_fit every 5 steps:" + f"{coverage_5_step:.3f}, {width_5_step:.3f}" +) + +# Plot estimated prediction intervals on test set +fig = plt.figure(figsize=(15, 5)) +ax = fig.add_subplot(1, 1, 1) +ax.set_ylabel("Hourly demand (GW)") +ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +ax.plot(demand_test.index, y_pred_5_steps, lw=2, c="C2", label="Predictions") +ax.fill_between( + demand_test.index, + y_pis_5_steps[:, 0, 0], + y_pis_5_steps[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", +) +ax.legend() +plt.show() diff --git a/examples/regression/plot_timeseries_example.py b/examples/regression/plot_timeseries_example.py index 8e9f5c269..4db03d2a6 100644 --- a/examples/regression/plot_timeseries_example.py +++ b/examples/regression/plot_timeseries_example.py @@ -25,7 +25,7 @@ intervals, through the `sklearn.model_selection.KFold()` object. Residuals are therefore estimated using models trained on data with higher indices than the validation data, which is inappropriate for time-series data. -Howerver, using a `sklearn.model_selection.TimeSeriesSplit` cross validation +However, using a `sklearn.model_selection.TimeSeriesSplit` cross validation object for estimating the residuals breaks the theoretical guarantees of the Jackknife+ and CV+ methods. """ diff --git a/mapie/regression.py b/mapie/regression.py index 674816ec3..bf3299e97 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -660,6 +660,7 @@ def predict( for _alpha in alpha_ ] ) + y_pred_up = np.column_stack( [ np.quantile( From b23484a861e2e2f406cd766befdebbfea83114e0 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Fri, 4 Mar 2022 18:03:16 +0100 Subject: [PATCH 06/32] Implement 3 predict method --- examples/regression/plot_timeseries_enbpi.py | 31 +- mapie/regression.py | 24 +- mapie/subsample.py | 108 ++++++ mapie/time_series_regression.py | 363 ++++++++++++++++++- 4 files changed, 481 insertions(+), 45 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index a07518584..164374496 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -27,9 +27,7 @@ import numpy as np import pandas as pd from matplotlib import pylab as plt -from scipy.stats import randint from sklearn.ensemble import RandomForestRegressor -from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit from mapie.metrics import regression_coverage_score from mapie.subsample import Subsample @@ -62,31 +60,14 @@ X_test = demand_test.loc[:, features] y_test = demand_test["Demand"] -# CV parameter search -n_iter = 10 -n_splits = 5 -tscv = TimeSeriesSplit(n_splits=n_splits) -random_state = 59 -rf_model = RandomForestRegressor(random_state=random_state) -rf_params = {"max_depth": randint(2, 30), "n_estimators": randint(10, 1e3)} -cv_obj = RandomizedSearchCV( - rf_model, - param_distributions=rf_params, - n_iter=n_iter, - cv=tscv, - scoring="neg_root_mean_squared_error", - random_state=random_state, - verbose=0, - n_jobs=-1, -) -cv_obj.fit(X_train, y_train) -best_est = cv_obj.best_estimator_ +# Model +model = RandomForestRegressor(max_depth=15, n_estimators=673, random_state=59) # Estimate prediction intervals on test set with best estimator alpha = 0.1 -cv_Mapie = Subsample(30, random_state=random_state) +cv_Mapie = Subsample(30, random_state=59) mapie = MapieTimeSeriesRegressor( - best_est, method="plus", cv=cv_Mapie, agg_function="median", n_jobs=-1 + model, method="plus", cv=cv_Mapie, agg_function="median", n_jobs=-1 ) mapie.fit(X_train, y_train) @@ -100,10 +81,10 @@ for step in range(5, len(X_test), 5): mapie.partial_fit( - X_test.iloc[(step - 5):step, :], y_test.iloc[(step - 5):step] + X_test.iloc[(step - 5) : step, :], y_test.iloc[(step - 5) : step] ) y_pred_step, y_pis_step = mapie.predict( - X_test.iloc[step:(step + 5), :], alpha=alpha + X_test.iloc[step : (step + 5), :], alpha=alpha ) y_pred_5_steps = np.concatenate((y_pred_5_steps, y_pred_step), axis=0) y_pis_5_steps = np.concatenate((y_pis_5_steps, y_pis_step), axis=0) diff --git a/mapie/regression.py b/mapie/regression.py index bf3299e97..7345b9b5a 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -182,7 +182,7 @@ class MapieRegressor(BaseEstimator, RegressorMixin): # type: ignore >>> print(y_pred) [ 5.28571429 7.17142857 9.05714286 10.94285714 12.82857143 14.71428571] """ - + cv_need_agg_function = [Subsample] valid_methods_ = ["naive", "base", "plus", "minmax"] valid_agg_functions_ = [None, "median", "mean"] fit_attributes = [ @@ -249,7 +249,7 @@ def _check_agg_function( ------ ValueError If ``agg_function`` is not in [``None``, ``"mean"``, ``"median"``], - or is ``None`` while cv class is ``Subsample``. + or is ``None`` while cv class is in ``cv_need_agg_function``. """ if agg_function not in self.valid_agg_functions_: raise ValueError( @@ -257,10 +257,11 @@ def _check_agg_function( "Allowed values are None, 'mean', 'median'." ) - if isinstance(self.cv, Subsample) and (agg_function is None): + if ((agg_function is None) and + (type(self.cv) in self.cv_need_agg_function)): raise ValueError( - "You need to specify an aggregation function when " - "cv is a Subsample. " + f"You need to specify an aggregation function when " + "cv's type is in {self.cv_need_agg_function}. " ) if (agg_function is not None) or (self.cv == "prefit"): return agg_function @@ -543,7 +544,6 @@ def fit( check_nan_in_aposteriori_prediction(pred_matrix) y_pred = aggregate_all(agg_function, pred_matrix) - self.residuals_ = np.abs(y - y_pred) return self @@ -626,15 +626,9 @@ def predict( ) # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). - # If ``method`` is "plus": - # - if ``cv`` is not a ``Subsample``, - # we enforce y_pred_multi to be of shape - # (n_samples_test, n_samples_train), - # thanks to the folds identifier. - # - if ``cv``is a ``Subsample``, the methode - # ``aggregate_with_mask`` fits it to the right size - # thanks to the shape of k_. + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size thanks to + # the shape of k_. y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) diff --git a/mapie/subsample.py b/mapie/subsample.py index abb7b31c8..1541f2854 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -3,6 +3,7 @@ from typing import Any, Generator, Optional, Tuple, Union import numpy as np +from numpy.lib.stride_tricks import sliding_window_view from numpy.random import RandomState from sklearn.model_selection import BaseCrossValidator from sklearn.utils import check_random_state, resample @@ -99,3 +100,110 @@ def get_n_splits(self, *args: Any, **kargs: Any) -> int: Returns the number of splitting iterations in the cross-validator. """ return self.n_resamplings + + +class BlockBootstrap(BaseCrossValidator): # type: ignore + """ + Generate a sampling method, that block bootstraps the training set. + It can replace KFold, LeaveOneOut or SubSample as cv argument in the MAPIE + class. + + Parameters + ---------- + n_resamplings : int + Number of resamplings. By default ``30``. + length: int + Length of the blocks. + overlapping: bool + Whether the blocks can overlapp or not. By default ``False``. + n_blocsk: int + Number of blocks in each resampling. By default ``None``, + the size of the training set divided by ``length``. + random_state: Optional + int or RandomState instance. + + + Examples + -------- + >>> import numpy as np + >>> from mapie.subsample import BlockBootstrap + >>> cv = BlockBootstrap(n_resamplings=2, length = 3, random_state=0) + >>> X = np.array([1,2,3,4,5,6,7,8,9,10]) + >>> for train_index, test_index in cv.split(X): + ... print(f"train index is {train_index}, test index is {test_index}") + train index is [5 0 3 3 7 9 3 5 2 4], test index is [8 1 6] + train index is [7 6 8 8 1 6 7 7 8 1], test index is [0 2 3 4 5 9] + """ + + def __init__( + self, + n_resamplings: int = 30, + length: int = 1, + n_blocks: Optional[int] = None, + overlapping: bool = False, + random_state: Optional[Union[int, RandomState]] = None, + ) -> None: + self.n_resamplings = n_resamplings + self.length = length + self.n_blocks = n_blocks + self.overlapping = overlapping + self.random_state = random_state + + def split( + self, X: ArrayLike + ) -> Generator[Tuple[Any, ArrayLike], None, None]: + """ + Generate indices to split data into training and test sets. + + Parameters + ---------- + X : ArrayLike of shape (n_samples, n_features) + Training data. + + Yields + ------ + train : ArrayLike of shape (n_indices_training,) + The training set indices for that split. + test : ArrayLike of shape (n_indices_test,) + The testing set indices for that split. + """ + indices = np.arange(len(X)) + if self.length > len(indices): + raise ValueError( + "The length of blocks is greater than the lenght" + "of training set." + ) + + if self.overlapping: + blocks = sliding_window_view(indices, window_shape=self.length) + else: + indices = indices[len(indices) % self.length :] + blocks_number = len(indices) // self.length + blocks = np.array_split(indices, indices_or_sections=blocks_number) + + random_state = check_random_state(self.random_state) + n_blocks = ( + self.n_blocks + if self.n_blocks is not None + else (len(indices) // self.length) + 1 + ) + for k in range(self.n_resamplings): + block_indices = np.random.randint(len(blocks), size=n_blocks) + train_index = np.concatenate( + [blocks[k] for k in block_indices], axis=0 + ) + test_index = np.array( + list(set(indices) - set(train_index)), dtype=np.int64 + ) + yield train_index, test_index + + def get_n_splits(self, *args: Any, **kargs: Any) -> int: + """ + Returns the number of splitting iterations in the cross-validator. + + Returns + ------- + int + Returns the number of splitting iterations in the cross-validator. + """ + return self.n_resamplings diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index c69c954f6..d4343c970 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -1,18 +1,28 @@ from __future__ import annotations +from argparse import ArgumentDefaultsHelpFormatter -from typing import Optional, Union +from typing import Iterable, Optional, Tuple, Union, cast import numpy as np +import numpy.ma as ma from sklearn.base import RegressorMixin from sklearn.model_selection import BaseCrossValidator +from sklearn.utils import check_array +from sklearn.utils.validation import check_is_fitted +from .aggregation_functions import aggregate_all from .regression import MapieRegressor +from .subsample import Subsample, BlockBootstrap from ._typing import ArrayLike +from .utils import ( + check_alpha, + check_alpha_and_n_samples, +) class MapieTimeSeriesRegressor(MapieRegressor): """ - Prediction interval with out-of-fold residuals for time series. + Prediction interval with out-of-fold residuals for time series. This class implements the EnbPI strategy and some variations for estimating prediction intervals on single-output time series. @@ -30,6 +40,18 @@ class MapieTimeSeriesRegressor(MapieRegressor): "Conformal prediction for dynamic time-series." """ + cv_need_agg_function = [BlockBootstrap, Subsample] + valid_methods_ = ["plus"] + valid_agg_functions_ = [None, "median", "mean"] + fit_attributes = [ + "single_estimator_", + "estimators_", + "k_", + "residuals_", + "n_features_in_", + "n_samples_val_", + ] + def __init__( self, estimator: Optional[RegressorMixin] = None, @@ -41,6 +63,23 @@ def __init__( ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) + def fit( + self, + X: ArrayLike, + y: ArrayLike, + sample_weight: Optional[ArrayLike] = None, + ) -> MapieTimeSeriesRegressor: + """ + Returns + ------- + MapieTimeSeriesRegressor + The model itself. + """ + self = super().fit(X=X, y=y, sample_weight=sample_weight) + y_pred = super().predict(X=X) + self.residuals_ = y.values - y_pred + return self + def partial_fit( self, X: ArrayLike, y: ArrayLike, ensemble: bool = True ) -> MapieTimeSeriesRegressor: @@ -69,10 +108,324 @@ def partial_fit( The model itself. """ y_pred, _ = self.predict(X, alpha=0.5, ensemble=ensemble) - new_residuals = np.abs(y - y_pred) + new_residuals = y - y_pred - cut_index = min(len(new_residuals), len(self.residuals_)) + cut_index = min( + len(new_residuals[~np.isnan(new_residuals)]), len(self.residuals_) + ) self.residuals_ = np.concatenate( - [self.residuals_[cut_index:], new_residuals], axis=0 + [ + self.residuals_[cut_index:], + new_residuals[~np.isnan(new_residuals)], + ], + axis=0, ) return self + + def predict( + self, + X: ArrayLike, + ensemble: bool = False, + alpha: Optional[Union[float, Iterable[float]]] = None, + ) -> Union[ArrayLike, Tuple[ArrayLike, ArrayLike]]: + + # Checks + check_is_fitted(self, self.fit_attributes) + self._check_ensemble(ensemble) + alpha_ = check_alpha(alpha) + X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) + y_pred = self.single_estimator_.predict(X) + + if alpha is None: + return np.array(y_pred) + else: + alpha_ = cast(ArrayLike, alpha_) + check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) + + betas_0 = np.full_like(alpha_, np.nan, dtype=float) + + for ind, _alpha in enumerate(alpha_): + betas = np.linspace(0.0, _alpha, num=len(self.residuals_) + 2) + + one_alpha_beta = np.quantile( + self.residuals_, + 1 - _alpha + betas, + axis=0, + interpolation="higher", + ) + + beta = np.quantile( + self.residuals_, + betas, + axis=0, + interpolation="lower", + ) + betas_0[ind] = betas[np.argmin(one_alpha_beta - beta, axis=0)] + + lower_quantiles = np.quantile( + self.residuals_, + betas_0, + axis=0, + interpolation="lower", + ) + higher_quantiles = np.quantile( + self.residuals_, + 1 - _alpha + betas_0, + axis=0, + interpolation="higher", + ) + + if self.method in ["naive", "base"] or self.cv == "prefit": + y_pred_low = np.column_stack( + [ + y_pred[:, np.newaxis] + lower_quantiles[k] + for k in range(len(alpha_)) + ] + ) + y_pred_up = np.column_stack( + [ + y_pred[:, np.newaxis] + higher_quantiles[k] + for k in range(len(alpha_)) + ] + ) + else: + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size thanks to + # the shape of k_. + + y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + + if self.method == "plus": + pred = aggregate_all(self.agg_function, y_pred_multi) + y_pred_low = np.column_stack( + [pred + lower_quantiles[k] for k in range(len(alpha_))] + ) + y_pred_up = np.column_stack( + [ + pred + higher_quantiles[k] + for k in range(len(alpha_)) + ] + ) + + if self.method == "minmax": + lower_bounds = np.min(y_pred_multi, axis=1, keepdims=True) + upper_bounds = np.max(y_pred_multi, axis=1, keepdims=True) + y_pred_low = np.column_stack( + [ + lower_bounds + lower_quantiles[k] + for k in range(len(alpha_)) + ] + ) + y_pred_up = np.column_stack( + [ + upper_bounds + higher_quantiles[k] + for k in range(len(alpha_)) + ] + ) + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) + return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) + + def predict2( + self, + X: ArrayLike, + ensemble: bool = False, + alpha: Optional[Union[float, Iterable[float]]] = None, + ) -> Union[ArrayLike, Tuple[ArrayLike, ArrayLike]]: + # Checks + check_is_fitted(self, self.fit_attributes) + self._check_ensemble(ensemble) + alpha_ = check_alpha(alpha) + X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) + y_pred = self.single_estimator_.predict(X) + + if alpha is None: + return np.array(y_pred) + else: + alpha_ = cast(ArrayLike, alpha_) + check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) + if self.method in ["naive", "base"] or self.cv == "prefit": + quantile = np.quantile( + self.residuals_, 1 - alpha_, interpolation="higher" + ) + y_pred_low = y_pred[:, np.newaxis] - quantile + y_pred_up = y_pred[:, np.newaxis] + quantile + else: + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size thanks to + # the shape of k_. + + y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + + if self.method == "plus": + lower_bounds = y_pred_multi + self.residuals_ + upper_bounds = y_pred_multi + self.residuals_ + if self.method == "minmax": + lower_bounds = np.min(y_pred_multi, axis=1, keepdims=True) + upper_bounds = np.max(y_pred_multi, axis=1, keepdims=True) + lower_bounds = lower_bounds + self.residuals_ + upper_bounds = upper_bounds + self.residuals_ + + y_pred_low = np.column_stack( + [ + np.quantile( + ma.masked_invalid(lower_bounds), + _alpha, + axis=1, + interpolation="lower", + ) + for _alpha in alpha_ + ] + ) + + y_pred_up = np.column_stack( + [ + np.quantile( + ma.masked_invalid(upper_bounds), + 1 - _alpha, + axis=1, + interpolation="higher", + ) + for _alpha in alpha_ + ] + ) + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) + return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) + + def predict3( + self, + X: ArrayLike, + ensemble: bool = False, + alpha: Optional[Union[float, Iterable[float]]] = None, + ) -> Union[ArrayLike, Tuple[ArrayLike, ArrayLike]]: + # Checks + check_is_fitted(self, self.fit_attributes) + self._check_ensemble(ensemble) + alpha_ = check_alpha(alpha) + X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) + y_pred = self.single_estimator_.predict(X) + + if alpha is None: + return np.array(y_pred) + else: + alpha_ = cast(ArrayLike, alpha_) + check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) + + y_pred_low = [] + y_pred_up = [] + + for _alpha in alpha_: + betas = np.linspace(0.0, _alpha, num=len(self.residuals_) + 2) + + if self.method in ["naive", "base"] or self.cv == "prefit": + one_alpha_beta = np.quantile( + self.residuals_, + 1 - _alpha + betas, + axis=0, + interpolation="higher", + ) + beta = np.quantile( + self.residuals_, + betas, + axis=0, + interpolation="lower", + ) + + beta_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] + + lower_quantiles = np.quantile( + self.residuals_, + beta_0, + axis=0, + interpolation="lower", + ) + higher_quantiles = np.quantile( + self.residuals_, + 1 - _alpha + beta_0, + axis=0, + interpolation="higher", + ) + y_pred_low.append(y_pred[:, np.newaxis] + lower_quantiles) + y_pred_up.append(y_pred[:, np.newaxis] + higher_quantiles) + else: + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size thanks to + # the shape of k_. + + y_pred_multi = self.aggregate_with_mask( + y_pred_multi, self.k_ + ) + + if self.method == "plus": + lower_bounds = y_pred_multi + self.residuals_ + upper_bounds = y_pred_multi + self.residuals_ + + if self.method == "minmax": + lower_bounds = np.min( + y_pred_multi, axis=1, keepdims=True + ) + upper_bounds = np.max( + y_pred_multi, axis=1, keepdims=True + ) + lower_bounds = lower_bounds + self.residuals_ + upper_bounds = upper_bounds + self.residuals_ + + one_alpha_beta = np.quantile( + upper_bounds, + 1 - _alpha + betas, + axis=1, + interpolation="higher", + ) + + beta = np.quantile( + lower_bounds, + betas, + axis=1, + interpolation="lower", + ) + + betas_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] + + lower_quantiles = np.empty((len(betas_0),)) + upper_quantiles = np.empty((len(betas_0),)) + + for ind, beta_0 in enumerate(betas_0): + lower_quantiles[ind] = np.quantile( + lower_bounds[ind, :], + beta_0, + axis=0, + interpolation="lower", + ) + upper_quantiles[ind] = np.quantile( + upper_bounds[ind, :], + 1 - _alpha + beta_0, + axis=0, + interpolation="higher", + ) + y_pred_low.append(lower_quantiles) + y_pred_up.append(upper_quantiles) + + y_pred_low = np.column_stack(y_pred_low) + y_pred_up = np.column_stack(y_pred_up) + + print(y_pred_low.shape) + print(y_pred_up.shape) + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) + return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) From ac1a94b8c05585ae3519f4237d48df5525cd4db2 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 21 Mar 2022 11:19:58 +0100 Subject: [PATCH 07/32] [commit before update with master] --- examples/regression/plot_timeseries_enbpi.py | 4 +- mapie/regression.py | 6 +- mapie/subsample.py | 48 ++- mapie/tests/test_time_series_regression.py | 374 ++++++++++++++++++- mapie/time_series_regression.py | 212 +---------- 5 files changed, 418 insertions(+), 226 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 164374496..f5b3a1125 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -81,10 +81,10 @@ for step in range(5, len(X_test), 5): mapie.partial_fit( - X_test.iloc[(step - 5) : step, :], y_test.iloc[(step - 5) : step] + X_test.iloc[(step - 5): step, :], y_test.iloc[(step - 5):step] ) y_pred_step, y_pis_step = mapie.predict( - X_test.iloc[step : (step + 5), :], alpha=alpha + X_test.iloc[step: (step + 5), :], alpha=alpha ) y_pred_5_steps = np.concatenate((y_pred_5_steps, y_pred_step), axis=0) y_pis_5_steps = np.concatenate((y_pis_5_steps, y_pis_step), axis=0) diff --git a/mapie/regression.py b/mapie/regression.py index 7345b9b5a..77b9a3e3b 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -258,10 +258,10 @@ def _check_agg_function( ) if ((agg_function is None) and - (type(self.cv) in self.cv_need_agg_function)): + (type(self.cv) in self.cv_need_agg_function)): raise ValueError( - f"You need to specify an aggregation function when " - "cv's type is in {self.cv_need_agg_function}. " + "You need to specify an aggregation function when " + f"cv's type is in {self.cv_need_agg_function}." ) if (agg_function is not None) or (self.cv == "prefit"): return agg_function diff --git a/mapie/subsample.py b/mapie/subsample.py index 1541f2854..de9b97933 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -113,7 +113,8 @@ class BlockBootstrap(BaseCrossValidator): # type: ignore n_resamplings : int Number of resamplings. By default ``30``. length: int - Length of the blocks. + Length of the blocks. By default ``None``, + the length of the training set divided by ``n_blocks``. overlapping: bool Whether the blocks can overlapp or not. By default ``False``. n_blocsk: int @@ -122,6 +123,10 @@ class BlockBootstrap(BaseCrossValidator): # type: ignore random_state: Optional int or RandomState instance. + Raises + ------ + ValueError + If both ``length`` and ``n_blocks`` are ``None``. Examples -------- @@ -138,11 +143,16 @@ class BlockBootstrap(BaseCrossValidator): # type: ignore def __init__( self, n_resamplings: int = 30, - length: int = 1, + length: Optional[int] = None, n_blocks: Optional[int] = None, overlapping: bool = False, random_state: Optional[Union[int, RandomState]] = None, ) -> None: + if length is None and n_blocks is None: + raise ValueError( + "At least one argument in ['length', 'n_blocks]" + "has to be not None." + ) self.n_resamplings = n_resamplings self.length = length self.n_blocks = n_blocks @@ -166,29 +176,43 @@ def split( The training set indices for that split. test : ArrayLike of shape (n_indices_test,) The testing set indices for that split. + Raises + ------ + ValueError + If ``length`` is greater than the train set size. """ + length = ( + self.length if self.length is not None else len(X) // self.n_blocks + ) + n_blocks = ( + self.n_blocks + if self.n_blocks is not None + else (len(X) // length) + 1 + ) indices = np.arange(len(X)) - if self.length > len(indices): + if length > len(indices): raise ValueError( "The length of blocks is greater than the lenght" "of training set." ) if self.overlapping: - blocks = sliding_window_view(indices, window_shape=self.length) + blocks = sliding_window_view(indices, window_shape=length) else: - indices = indices[len(indices) % self.length :] - blocks_number = len(indices) // self.length + indices = indices[len(indices) % length:] + blocks_number = len(indices) // length blocks = np.array_split(indices, indices_or_sections=blocks_number) random_state = check_random_state(self.random_state) - n_blocks = ( - self.n_blocks - if self.n_blocks is not None - else (len(indices) // self.length) + 1 - ) + for k in range(self.n_resamplings): - block_indices = np.random.randint(len(blocks), size=n_blocks) + block_indices = resample( + range(len(blocks)), + replace=True, + n_samples=n_blocks, + random_state=random_state, + stratify=None, + ) train_index = np.concatenate( [blocks[k] for k in block_indices], axis=0 ) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 2d7a8ef28..65cd59bcb 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -1,9 +1,379 @@ +from __future__ import annotations + +from itertools import combinations +from typing import Any, List, Optional, Tuple, Union + import numpy as np +import pytest +from sklearn.datasets import make_regression +from sklearn.linear_model import LinearRegression +from sklearn.model_selection import KFold, LeaveOneOut, train_test_split +from typing_extensions import TypedDict +from mapie._typing import ArrayLike +from mapie.aggregation_functions import aggregate_all +from mapie.metrics import regression_coverage_score from mapie.time_series_regression import MapieTimeSeriesRegressor +from mapie.subsample import BlockBootstrap + +X_toy = np.array(range(50)).reshape(-1, 1) +y_toy = (5.0 + 2.0*X_toy).flatten() +X, y = make_regression(n_samples=500, n_features=10, noise=1.0, random_state=1) +k = np.ones(shape=(5, X.shape[1])) +METHODS = ["naive", "base", "plus", "minmax"] + +Params = TypedDict( + "Params", + { + "method": str, + "agg_function": str, + "cv": Optional[Union[int, KFold, BlockBootstrap]], + }, +) +STRATEGIES = { + "naive": Params(method="naive", agg_function="median", cv=None), + "jackknife": Params(method="base", agg_function="mean", cv=-1), + "jackknife_plus": Params(method="plus", agg_function="mean", cv=-1), + "jackknife_minmax": Params(method="minmax", agg_function="mean", cv=-1), + "cv": Params( + method="base", + agg_function="mean", + cv=KFold(n_splits=3, shuffle=True, random_state=1), + ), + "cv_plus": Params( + method="plus", + agg_function="mean", + cv=KFold(n_splits=3, shuffle=True, random_state=1), + ), + "cv_minmax": Params( + method="minmax", + agg_function="mean", + cv=KFold(n_splits=3, shuffle=True, random_state=1), + ), + "jackknife_plus_ab": Params( + method="plus", + agg_function="mean", + cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), + ), + "jackknife_minmax_ab": Params( + method="minmax", + agg_function="mean", + cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), + ), + "jackknife_plus_median_ab": Params( + method="plus", + agg_function="median", + cv=BlockBootstrap( + n_resamplings=30, + n_blocks=5, + random_state=1, + ), + ), +} + +WIDTHS = { + "naive": 3.76, + "jackknife": 3.76, + "jackknife_plus": 3.76, + "jackknife_minmax": 3.82, + "cv": 3.76, + "cv_plus": 3.76, + "cv_minmax": 3.95, + "prefit": 4.81, + "cv_plus_median": 3.90, + "jackknife_plus_ab": 3.76, + "jackknife_minmax_ab": 3.96, + "jackknife_plus_median_ab": 3.76, +} + +COVERAGES = { + "naive": 0.952, + "jackknife": 0.952, + "jackknife_plus": 0.952, + "jackknife_minmax": 0.952, + "cv": 0.958, + "cv_plus": 0.956, + "cv_minmax": 0.966, + "prefit": 0.980, + "cv_plus_median": 0.954, + "jackknife_plus_ab": 0.952, + "jackknife_minmax_ab": 0.970, + "jackknife_plus_median_ab": 0.960, +} + + +@pytest.mark.parametrize("agg_function", ["dummy", 0, 1, 2.5, [1, 2]]) +def test_invalid_agg_function(agg_function: Any) -> None: + """Test that invalid agg_functions raise errors.""" + + mapie_ts_reg = MapieTimeSeriesRegressor(agg_function=None) + with pytest.raises(ValueError, match=r".*If ensemble is True*"): + mapie_ts_reg.fit(X_toy, y_toy) + mapie_ts_reg.predict(X_toy, ensemble=True) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +@pytest.mark.parametrize("dataset", [(X, y), (X_toy, y_toy)]) +@pytest.mark.parametrize("alpha", [0.2, [0.2, 0.4], (0.2, 0.4)]) +def test_predict_output_shape( + strategy: str, alpha: Any, dataset: Tuple[ArrayLike, ArrayLike] +) -> None: + """Test predict output shape.""" + mapie_ts_reg = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + (X, y) = dataset + mapie_ts_reg.fit(X, y) + y_pred, y_pis = mapie_ts_reg.predict(X, alpha=alpha) + n_alpha = len(alpha) if hasattr(alpha, "__len__") else 1 + assert y_pred.shape == (X.shape[0],) + assert y_pis.shape == (X.shape[0], 2, n_alpha) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +def test_results_for_same_alpha(strategy: str) -> None: + """ + Test that predictions and intervals + are similar with two equal values of alpha. + """ + mapie_ts_reg = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie_ts_reg.fit(X, y) + _, y_pis = mapie_ts_reg.predict(X, alpha=[0.1, 0.1]) + np.testing.assert_allclose(y_pis[:, 0, 0], y_pis[:, 0, 1]) + np.testing.assert_allclose(y_pis[:, 1, 0], y_pis[:, 1, 1]) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +@pytest.mark.parametrize( + "alpha", [np.array([0.05, 0.1]), [0.05, 0.1], (0.05, 0.1)] +) +def test_results_for_alpha_as_float_and_arraylike( + strategy: str, alpha: Any +) -> None: + """Test that output values do not depend on type of alpha.""" + mapie_ts_reg = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie_ts_reg.fit(X, y) + y_pred_float1, y_pis_float1 = mapie_ts_reg.predict(X, alpha=alpha[0]) + y_pred_float2, y_pis_float2 = mapie_ts_reg.predict(X, alpha=alpha[1]) + y_pred_array, y_pis_array = mapie_ts_reg.predict(X, alpha=alpha) + np.testing.assert_allclose(y_pred_float1, y_pred_array) + np.testing.assert_allclose(y_pred_float2, y_pred_array) + np.testing.assert_allclose(y_pis_float1[:, :, 0], y_pis_array[:, :, 0]) + np.testing.assert_allclose(y_pis_float2[:, :, 0], y_pis_array[:, :, 1]) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +def test_results_for_ordered_alpha(strategy: str) -> None: + """ + Test that prediction intervals lower (upper) bounds give + consistent results for ordered alphas. + """ + mapie = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie.fit(X, y) + y_pred, y_pis = mapie.predict(X, alpha=[0.05, 0.1]) + assert (y_pis[:, 0, 0] <= y_pis[:, 0, 1]).all() + assert (y_pis[:, 1, 0] >= y_pis[:, 1, 1]).all() + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +def test_results_single_and_multi_jobs(strategy: str) -> None: + """ + Test that MapieTimeSeriesRegressor gives equal predictions + regardless of number of parallel jobs. + """ + mapie_single = MapieTimeSeriesRegressor(n_jobs=1, **STRATEGIES[strategy]) + mapie_multi = MapieTimeSeriesRegressor(n_jobs=-1, **STRATEGIES[strategy]) + mapie_single.fit(X_toy, y_toy) + mapie_multi.fit(X_toy, y_toy) + y_pred_single, y_pis_single = mapie_single.predict(X_toy, alpha=0.2) + y_pred_multi, y_pis_multi = mapie_multi.predict(X_toy, alpha=0.2) + np.testing.assert_allclose(y_pred_single, y_pred_multi) + np.testing.assert_allclose(y_pis_single, y_pis_multi) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +def test_results_with_constant_sample_weights(strategy: str) -> None: + """ + Test predictions when sample weights are None + or constant with different values. + """ + n_samples = len(X) + mapie0 = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie1 = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie2 = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie0.fit(X, y, sample_weight=None) + mapie1.fit(X, y, sample_weight=np.ones(shape=n_samples)) + mapie2.fit(X, y, sample_weight=np.ones(shape=n_samples) * 5) + y_pred0, y_pis0 = mapie0.predict(X, alpha=0.05) + y_pred1, y_pis1 = mapie1.predict(X, alpha=0.05) + y_pred2, y_pis2 = mapie2.predict(X, alpha=0.05) + np.testing.assert_allclose(y_pred0, y_pred1) + np.testing.assert_allclose(y_pred1, y_pred2) + np.testing.assert_allclose(y_pis0, y_pis1) + np.testing.assert_allclose(y_pis1, y_pis2) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +def test_prediction_between_low_up(strategy: str) -> None: + """Test that prediction lies between low and up prediction intervals.""" + mapie = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie.fit(X, y) + y_pred, y_pis = mapie.predict(X, alpha=0.1) + assert (y_pred >= y_pis[:, 0, 0]).all() + assert (y_pred <= y_pis[:, 1, 0]).all() + -X_toy = np.array([[0], [1], [2], [3], [4], [5]]) -y_toy = np.array([5, 7.5, 9.5, 10.5, 12.5, 15]) +@pytest.mark.parametrize("method", ["plus", "minmax"]) +@pytest.mark.parametrize("cv", [-1, 2, 3, 5]) +@pytest.mark.parametrize("agg_function", ["mean", "median"]) +@pytest.mark.parametrize("alpha", [0.05, 0.1, 0.2]) +def test_prediction_agg_function( + method: str, cv: Union[LeaveOneOut, KFold], agg_function: str, alpha: int +) -> None: + """ + Test that predictions differ when ensemble is True/False, + but not prediction intervals. + """ + mapie = MapieTimeSeriesRegressor( + method=method, cv=cv, agg_function=agg_function + ) + mapie.fit(X, y) + y_pred_1, y_pis_1 = mapie.predict(X, ensemble=True, alpha=alpha) + y_pred_2, y_pis_2 = mapie.predict(X, ensemble=False, alpha=alpha) + np.testing.assert_allclose(y_pis_1[:, 0, 0], y_pis_2[:, 0, 0]) + np.testing.assert_allclose(y_pis_1[:, 1, 0], y_pis_2[:, 1, 0]) + with pytest.raises(AssertionError): + np.testing.assert_allclose(y_pred_1, y_pred_2) + + +@pytest.mark.parametrize("strategy", [*STRATEGIES]) +def test_linear_regression_results(strategy: str) -> None: + """ + Test expected prediction intervals for + a multivariate linear regression problem + with fixed random state. + """ + mapie_ts = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) + mapie_ts.fit(X, y) + _, y_pis = mapie_ts.predict(X, alpha=0.05) + y_pred_low, y_pred_up = y_pis[:, 0, 0], y_pis[:, 1, 0] + width_mean = (y_pred_up - y_pred_low).mean() + coverage = regression_coverage_score(y, y_pred_low, y_pred_up) + np.testing.assert_allclose(width_mean, WIDTHS[strategy], rtol=1e-2) + np.testing.assert_allclose(coverage, COVERAGES[strategy], rtol=1e-2) + + +def test_results_prefit_ignore_method() -> None: + """Test that method is ignored when ``cv="prefit"``.""" + estimator = LinearRegression().fit(X, y) + all_y_pis: List[ArrayLike] = [] + for method in METHODS: + mapie_ts_reg = MapieTimeSeriesRegressor( + estimator=estimator, cv="prefit", method=method + ) + mapie_ts_reg.fit(X, y) + _, y_pis = mapie_ts_reg.predict(X, alpha=0.1) + all_y_pis.append(y_pis) + for y_pis1, y_pis2 in combinations(all_y_pis, 2): + np.testing.assert_allclose(y_pis1, y_pis2) + + +def test_results_prefit_naive() -> None: + """ + Test that prefit, fit and predict on the same dataset + is equivalent to the "naive" method. + """ + estimator = LinearRegression().fit(X, y) + mapie_ts_reg = MapieTimeSeriesRegressor(estimator=estimator, cv="prefit") + mapie_ts_reg.fit(X, y) + _, y_pis = mapie_ts_reg.predict(X, alpha=0.05) + width_mean = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() + coverage = regression_coverage_score(y, y_pis[:, 0, 0], y_pis[:, 1, 0]) + np.testing.assert_allclose(width_mean, WIDTHS["naive"], rtol=1e-2) + np.testing.assert_allclose(coverage, COVERAGES["naive"], rtol=1e-2) + + +def test_results_prefit() -> None: + """Test prefit results on a standard train/validation/test split.""" + X_train_val, X_test, y_train_val, y_test = train_test_split( + X, y, test_size=1 / 10, random_state=1 + ) + X_train, X_val, y_train, y_val = train_test_split( + X_train_val, y_train_val, test_size=1 / 9, random_state=1 + ) + estimator = LinearRegression().fit(X_train, y_train) + mapie_ts_reg = MapieTimeSeriesRegressor(estimator=estimator, cv="prefit") + mapie_ts_reg.fit(X_val, y_val) + _, y_pis = mapie_ts_reg.predict(X_test, alpha=0.05) + width_mean = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() + coverage = regression_coverage_score( + y_test, y_pis[:, 0, 0], y_pis[:, 1, 0] + ) + np.testing.assert_allclose(width_mean, WIDTHS["prefit"], rtol=1e-2) + np.testing.assert_allclose(coverage, COVERAGES["prefit"], rtol=1e-2) + + +def test_not_enough_resamplings() -> None: + """Test that a warning is raised if at least one residual is nan.""" + with pytest.warns(UserWarning, match=r"WARNING: at least one point of*"): + mapie_ts_reg = MapieTimeSeriesRegressor( + cv=BlockBootstrap(n_resamplings=1, n_blocks=1), agg_function="mean" + ) + mapie_ts_reg.fit(X, y) + + +def test_no_agg_fx_specified_with_subsample() -> None: + """Test that a warning is raised if at least one residual is nan.""" + with pytest.raises( + ValueError, match=r"You need to specify an aggregation*" + ): + mapie_ts_reg = MapieTimeSeriesRegressor( + cv=BlockBootstrap(n_resamplings=1, n_blocks=1), + agg_function=None, + ) + mapie_ts_reg.fit(X, y) + + +def test_invalid_aggregate_all() -> None: + """ + Test that wrong aggregation in MAPIE raise errors. + """ + with pytest.raises( + ValueError, + match=r".*Aggregation function called but not defined.*", + ): + aggregate_all(None, X) + + +def test_aggregate_with_mask_with_prefit() -> None: + """ + Test ``aggregate_with_mask`` in case ``cv`` is ``"prefit"``. + """ + mapie_ts_reg = MapieTimeSeriesRegressor(cv="prefit") + with pytest.raises( + ValueError, + match=r".*There should not be aggregation of predictions if cv is*", + ): + mapie_ts_reg.aggregate_with_mask(k, k) + + mapie_ts_reg = MapieTimeSeriesRegressor(agg_function="nonsense") + with pytest.raises( + ValueError, + match=r".*The value of self.agg_function is not correct*", + ): + mapie_ts_reg.aggregate_with_mask(k, k) + + +def test_pred_loof_isnan() -> None: + """Test that if validation set is empty then prediction is empty.""" + mapie_ts_reg = MapieTimeSeriesRegressor() + _, y_pred, _, _ = mapie_ts_reg._fit_and_predict_oof_model( + estimator=mapie_ts_reg(), + X=X_toy, + y=y_toy, + train_index=[0, 1, 2, 3, 4], + val_index=[], + k=0, + ) + assert len(y_pred) == 0 def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index d4343c970..e6b42ab5a 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -1,10 +1,9 @@ from __future__ import annotations -from argparse import ArgumentDefaultsHelpFormatter +from configparser import Interpolation from typing import Iterable, Optional, Tuple, Union, cast import numpy as np -import numpy.ma as ma from sklearn.base import RegressorMixin from sklearn.model_selection import BaseCrossValidator from sklearn.utils import check_array @@ -41,7 +40,7 @@ class MapieTimeSeriesRegressor(MapieRegressor): """ cv_need_agg_function = [BlockBootstrap, Subsample] - valid_methods_ = ["plus"] + valid_methods_ = ["naive", "base", "plus", "minmax"] valid_agg_functions_ = [None, "median", "mean"] fit_attributes = [ "single_estimator_", @@ -77,7 +76,7 @@ def fit( """ self = super().fit(X=X, y=y, sample_weight=sample_weight) y_pred = super().predict(X=X) - self.residuals_ = y.values - y_pred + self.residuals_ = y - y_pred return self def partial_fit( @@ -141,11 +140,10 @@ def predict( else: alpha_ = cast(ArrayLike, alpha_) check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) - betas_0 = np.full_like(alpha_, np.nan, dtype=float) for ind, _alpha in enumerate(alpha_): - betas = np.linspace(0.0, _alpha, num=len(self.residuals_) + 2) + betas = np.linspace(0.0, _alpha, num=len(self.residuals_)+2) one_alpha_beta = np.quantile( self.residuals_, @@ -161,7 +159,6 @@ def predict( interpolation="lower", ) betas_0[ind] = betas[np.argmin(one_alpha_beta - beta, axis=0)] - lower_quantiles = np.quantile( self.residuals_, betas_0, @@ -170,7 +167,7 @@ def predict( ) higher_quantiles = np.quantile( self.residuals_, - 1 - _alpha + betas_0, + 1 - alpha_ + betas_0, axis=0, interpolation="higher", ) @@ -230,202 +227,3 @@ def predict( if ensemble: y_pred = aggregate_all(self.agg_function, y_pred_multi) return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) - - def predict2( - self, - X: ArrayLike, - ensemble: bool = False, - alpha: Optional[Union[float, Iterable[float]]] = None, - ) -> Union[ArrayLike, Tuple[ArrayLike, ArrayLike]]: - # Checks - check_is_fitted(self, self.fit_attributes) - self._check_ensemble(ensemble) - alpha_ = check_alpha(alpha) - X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) - y_pred = self.single_estimator_.predict(X) - - if alpha is None: - return np.array(y_pred) - else: - alpha_ = cast(ArrayLike, alpha_) - check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) - if self.method in ["naive", "base"] or self.cv == "prefit": - quantile = np.quantile( - self.residuals_, 1 - alpha_, interpolation="higher" - ) - y_pred_low = y_pred[:, np.newaxis] - quantile - y_pred_up = y_pred[:, np.newaxis] + quantile - else: - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size thanks to - # the shape of k_. - - y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) - - if self.method == "plus": - lower_bounds = y_pred_multi + self.residuals_ - upper_bounds = y_pred_multi + self.residuals_ - if self.method == "minmax": - lower_bounds = np.min(y_pred_multi, axis=1, keepdims=True) - upper_bounds = np.max(y_pred_multi, axis=1, keepdims=True) - lower_bounds = lower_bounds + self.residuals_ - upper_bounds = upper_bounds + self.residuals_ - - y_pred_low = np.column_stack( - [ - np.quantile( - ma.masked_invalid(lower_bounds), - _alpha, - axis=1, - interpolation="lower", - ) - for _alpha in alpha_ - ] - ) - - y_pred_up = np.column_stack( - [ - np.quantile( - ma.masked_invalid(upper_bounds), - 1 - _alpha, - axis=1, - interpolation="higher", - ) - for _alpha in alpha_ - ] - ) - if ensemble: - y_pred = aggregate_all(self.agg_function, y_pred_multi) - return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) - - def predict3( - self, - X: ArrayLike, - ensemble: bool = False, - alpha: Optional[Union[float, Iterable[float]]] = None, - ) -> Union[ArrayLike, Tuple[ArrayLike, ArrayLike]]: - # Checks - check_is_fitted(self, self.fit_attributes) - self._check_ensemble(ensemble) - alpha_ = check_alpha(alpha) - X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) - y_pred = self.single_estimator_.predict(X) - - if alpha is None: - return np.array(y_pred) - else: - alpha_ = cast(ArrayLike, alpha_) - check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) - - y_pred_low = [] - y_pred_up = [] - - for _alpha in alpha_: - betas = np.linspace(0.0, _alpha, num=len(self.residuals_) + 2) - - if self.method in ["naive", "base"] or self.cv == "prefit": - one_alpha_beta = np.quantile( - self.residuals_, - 1 - _alpha + betas, - axis=0, - interpolation="higher", - ) - beta = np.quantile( - self.residuals_, - betas, - axis=0, - interpolation="lower", - ) - - beta_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] - - lower_quantiles = np.quantile( - self.residuals_, - beta_0, - axis=0, - interpolation="lower", - ) - higher_quantiles = np.quantile( - self.residuals_, - 1 - _alpha + beta_0, - axis=0, - interpolation="higher", - ) - y_pred_low.append(y_pred[:, np.newaxis] + lower_quantiles) - y_pred_up.append(y_pred[:, np.newaxis] + higher_quantiles) - else: - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size thanks to - # the shape of k_. - - y_pred_multi = self.aggregate_with_mask( - y_pred_multi, self.k_ - ) - - if self.method == "plus": - lower_bounds = y_pred_multi + self.residuals_ - upper_bounds = y_pred_multi + self.residuals_ - - if self.method == "minmax": - lower_bounds = np.min( - y_pred_multi, axis=1, keepdims=True - ) - upper_bounds = np.max( - y_pred_multi, axis=1, keepdims=True - ) - lower_bounds = lower_bounds + self.residuals_ - upper_bounds = upper_bounds + self.residuals_ - - one_alpha_beta = np.quantile( - upper_bounds, - 1 - _alpha + betas, - axis=1, - interpolation="higher", - ) - - beta = np.quantile( - lower_bounds, - betas, - axis=1, - interpolation="lower", - ) - - betas_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] - - lower_quantiles = np.empty((len(betas_0),)) - upper_quantiles = np.empty((len(betas_0),)) - - for ind, beta_0 in enumerate(betas_0): - lower_quantiles[ind] = np.quantile( - lower_bounds[ind, :], - beta_0, - axis=0, - interpolation="lower", - ) - upper_quantiles[ind] = np.quantile( - upper_bounds[ind, :], - 1 - _alpha + beta_0, - axis=0, - interpolation="higher", - ) - y_pred_low.append(lower_quantiles) - y_pred_up.append(upper_quantiles) - - y_pred_low = np.column_stack(y_pred_low) - y_pred_up = np.column_stack(y_pred_up) - - print(y_pred_low.shape) - print(y_pred_up.shape) - if ensemble: - y_pred = aggregate_all(self.agg_function, y_pred_multi) - return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) From 8749fcb5927a2b7deaa7886c86250fbfdffe8262 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 21 Mar 2022 19:06:05 +0100 Subject: [PATCH 08/32] unit test OK, example not OK --- examples/regression/plot_timeseries_enbpi.py | 73 +++++++++++--------- mapie/regression.py | 6 +- mapie/subsample.py | 20 +++--- mapie/tests/test_subsample.py | 63 +++++++++++++++-- mapie/tests/test_time_series_regression.py | 41 ++++++----- mapie/time_series_regression.py | 13 +--- 6 files changed, 140 insertions(+), 76 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index f5b3a1125..1b9879c89 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -13,14 +13,14 @@ The electricity demand features daily and weekly seasonalities and is impacted by the temperature, considered here as a exogeneous variable. -The data is modelled by a Random Forest model with a -:class:`sklearn.model_selection.RandomizedSearchCV` using a sequential +A Random Forest model is fitted on data. The hyper-parameters are optimized +with a :class:`sklearn.model_selection.RandomizedSearchCV` using a sequential :class:`sklearn.model_selection.TimeSeriesSplit` cross validation, in which the training set is prior to the validation set. The best model is then feeded into :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the associated prediction intervals. We compare two approaches: one with no -`partial_fit` call and one with `partial_fit` every 5 steps. +`partial_fit` call and one with `partial_fit` every step. """ import warnings @@ -30,7 +30,7 @@ from sklearn.ensemble import RandomForestRegressor from mapie.metrics import regression_coverage_score -from mapie.subsample import Subsample +from mapie.subsample import BlockBootstrap from mapie.time_series_regression import MapieTimeSeriesRegressor warnings.simplefilter("ignore") @@ -39,20 +39,21 @@ demand_df = pd.read_csv( "../data/demand_temperature.csv", parse_dates=True, index_col=0 ) + +print(demand_df.shape) demand_df["Date"] = pd.to_datetime(demand_df.index) demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") demand_df["Hour"] = demand_df.index.hour -for hour in range(1, 5): +for hour in range(1, 3): demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) # Train/validation/test split -num_test_steps = 24 * 7 * 2 +num_test_steps = 24 * 7 demand_train = demand_df.iloc[:-num_test_steps, :].copy() demand_test = demand_df.iloc[-num_test_steps:, :].copy() -features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ - f"Lag_{hour}" for hour in range(1, 5) -] +features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + X_train = demand_train.loc[ ~np.any(demand_train[features].isnull(), axis=1), features ] @@ -60,14 +61,18 @@ X_test = demand_test.loc[:, features] y_test = demand_test["Demand"] -# Model -model = RandomForestRegressor(max_depth=15, n_estimators=673, random_state=59) +# Model: Random Forest previously optimized with a cross-validation +model = RandomForestRegressor(max_depth=10, n_estimators=50, random_state=59) # Estimate prediction intervals on test set with best estimator alpha = 0.1 -cv_Mapie = Subsample(30, random_state=59) +cv_MapieTimeSeries = BlockBootstrap(200, length=24, random_state=59) mapie = MapieTimeSeriesRegressor( - model, method="plus", cv=cv_Mapie, agg_function="median", n_jobs=-1 + model, + method="plus", + cv=cv_MapieTimeSeries, + agg_function="mean", + n_jobs=-1 ) mapie.fit(X_train, y_train) @@ -76,28 +81,30 @@ coverage = regression_coverage_score(y_test, y_pis[:, 0, 0], y_pis[:, 1, 0]) width = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() -# With partial_fit every five hours -y_pred_5_steps, y_pis_5_steps = mapie.predict(X_test.iloc[:5, :], alpha=alpha) +# With partial_fit every 2 hours +gap = 1 -for step in range(5, len(X_test), 5): +y_pred_steps, y_pis_steps = mapie.predict(X_test.iloc[:gap, :], alpha=alpha) + +for step in range(gap, len(X_test), gap): mapie.partial_fit( - X_test.iloc[(step - 5): step, :], y_test.iloc[(step - 5):step] + X_test.iloc[(step - gap):step, :], y_test.iloc[(step - gap):step] ) - y_pred_step, y_pis_step = mapie.predict( - X_test.iloc[step: (step + 5), :], alpha=alpha + y_pred_gap_step, y_pis_gap_step = mapie.predict( + X_test.iloc[step:(step+gap), :], + alpha=alpha, ) - y_pred_5_steps = np.concatenate((y_pred_5_steps, y_pred_step), axis=0) - y_pis_5_steps = np.concatenate((y_pis_5_steps, y_pis_step), axis=0) + y_pred_steps = np.concatenate((y_pred_steps, y_pred_gap_step), axis=0) + y_pis_steps = np.concatenate((y_pis_steps, y_pis_gap_step), axis=0) -coverage_5_step = regression_coverage_score( - y_test, y_pis_5_steps[:, 0, 0], y_pis_5_steps[:, 1, 0] +coverage_steps = regression_coverage_score( + y_test, y_pis_steps[:, 0, 0], y_pis_steps[:, 1, 0] ) -width_5_step = (y_pis_5_steps[:, 1, 0] - y_pis_5_steps[:, 0, 0]).mean() - +width_steps = (y_pis_steps[:, 1, 0] - y_pis_steps[:, 0, 0]).mean() # Print results print( - "Coverage and prediction interval width mean for MapieTimeSeriesRegressor:" + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " "\nWithout any partial_fit:" f"{coverage:.3f}, {width:.3f}" ) @@ -117,12 +124,13 @@ label="MapieTimeSeriesRegressor PIs", ) ax.legend() +plt.title("Without partial_fit") plt.show() print( - "Coverage and prediction interval width mean for MapieTimeSeriesRegressor:" - "\nWith partial_fit every 5 steps:" - f"{coverage_5_step:.3f}, {width_5_step:.3f}" + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor " + "\nWith partial_fit every step: " + f"{coverage_steps:.3f}, {width_steps:.3f}" ) # Plot estimated prediction intervals on test set @@ -130,14 +138,15 @@ ax = fig.add_subplot(1, 1, 1) ax.set_ylabel("Hourly demand (GW)") ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") -ax.plot(demand_test.index, y_pred_5_steps, lw=2, c="C2", label="Predictions") +ax.plot(demand_test.index, y_pred_steps, lw=2, c="C2", label="Predictions") ax.fill_between( demand_test.index, - y_pis_5_steps[:, 0, 0], - y_pis_5_steps[:, 1, 0], + y_pis_steps[:, 0, 0], + y_pis_steps[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) ax.legend() +plt.title("With partial_fit") plt.show() diff --git a/mapie/regression.py b/mapie/regression.py index 2bc0e7270..661c858f6 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -187,6 +187,7 @@ class MapieRegressor(BaseEstimator, RegressorMixin): # type: ignore >>> print(y_pred) [ 5.28571429 7.17142857 9.05714286 10.94285714 12.82857143 14.71428571] """ + cv_need_agg_function = [Subsample] valid_methods_ = ["naive", "base", "plus", "minmax"] valid_agg_functions_ = [None, "median", "mean"] @@ -262,8 +263,9 @@ def _check_agg_function( "Allowed values are None, 'mean', 'median'." ) - if ((agg_function is None) and - (type(self.cv) in self.cv_need_agg_function)): + if (agg_function is None) and ( + type(self.cv) in self.cv_need_agg_function + ): raise ValueError( "You need to specify an aggregation function when " f"cv's type is in {self.cv_need_agg_function}." diff --git a/mapie/subsample.py b/mapie/subsample.py index de9b97933..e52778769 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -136,8 +136,8 @@ class BlockBootstrap(BaseCrossValidator): # type: ignore >>> X = np.array([1,2,3,4,5,6,7,8,9,10]) >>> for train_index, test_index in cv.split(X): ... print(f"train index is {train_index}, test index is {test_index}") - train index is [5 0 3 3 7 9 3 5 2 4], test index is [8 1 6] - train index is [7 6 8 8 1 6 7 7 8 1], test index is [0 2 3 4 5 9] + train index is [1 2 3 4 5 6 1 2 3 4 5 6], test index is [8 9 7] + train index is [4 5 6 7 8 9 1 2 3 7 8 9], test index is [] """ def __init__( @@ -148,11 +148,6 @@ def __init__( overlapping: bool = False, random_state: Optional[Union[int, RandomState]] = None, ) -> None: - if length is None and n_blocks is None: - raise ValueError( - "At least one argument in ['length', 'n_blocks]" - "has to be not None." - ) self.n_resamplings = n_resamplings self.length = length self.n_blocks = n_blocks @@ -181,6 +176,12 @@ def split( ValueError If ``length`` is greater than the train set size. """ + if (self.length is None) and (self.n_blocks is None): + raise ValueError( + "At least one argument in ['length', 'n_blocks]" + "has to be not None." + ) + length = ( self.length if self.length is not None else len(X) // self.n_blocks ) @@ -190,9 +191,9 @@ def split( else (len(X) // length) + 1 ) indices = np.arange(len(X)) - if length > len(indices): + if (length <= 0) or (length > len(indices)): raise ValueError( - "The length of blocks is greater than the lenght" + "The length of blocks is <= 0 or greater than the lenght" "of training set." ) @@ -202,7 +203,6 @@ def split( indices = indices[len(indices) % length:] blocks_number = len(indices) // length blocks = np.array_split(indices, indices_or_sections=blocks_number) - random_state = check_random_state(self.random_state) for k in range(self.n_resamplings): diff --git a/mapie/tests/test_subsample.py b/mapie/tests/test_subsample.py index 39dd2ab74..533a50b7f 100644 --- a/mapie/tests/test_subsample.py +++ b/mapie/tests/test_subsample.py @@ -1,11 +1,12 @@ from __future__ import annotations import numpy as np +import pytest -from mapie.subsample import Subsample +from mapie.subsample import BlockBootstrap, Subsample -def test_default_parameters() -> None: +def test_default_parameters_SubSample() -> None: """Test default values of Subsample.""" cv = Subsample() assert cv.n_resamplings == 30 @@ -13,13 +14,13 @@ def test_default_parameters() -> None: assert cv.random_state is None -def test_get_n_splits() -> None: +def test_get_n_splits_SubSample() -> None: """Test get_n_splits method of Subsample.""" cv = Subsample(n_resamplings=3) assert cv.get_n_splits() == 3 -def test_split() -> None: +def test_split_SubSample() -> None: """Test outputs of subsamplings.""" X = np.array([0, 1, 2, 3]) cv = Subsample(n_resamplings=2, random_state=1) @@ -29,3 +30,57 @@ def test_split() -> None: tests_expected = np.array([2, 0, 2]) np.testing.assert_equal(trains, trains_expected) np.testing.assert_equal(tests, tests_expected) + + +def test_default_parameters_BlockBootstrap() -> None: + """Test default values of Subsample.""" + cv = BlockBootstrap() + assert cv.n_resamplings == 30 + assert cv.length is None + assert cv.n_blocks is None + assert not cv.overlapping + assert cv.random_state is None + + +def test_get_n_splits_BlockBootstrap() -> None: + """Test get_n_splits method of Subsample.""" + cv = BlockBootstrap(n_resamplings=3) + assert cv.get_n_splits() == 3 + + +def test_split_BlockBootstrap() -> None: + """Test outputs of subsamplings.""" + X = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + cv = BlockBootstrap( + n_resamplings=1, length=2, overlapping=False, random_state=1 + ) + trains = np.concatenate([x[0] for x in cv.split(X)]) + tests = np.concatenate([x[1] for x in cv.split(X)]) + trains_expected = np.array([7, 8, 9, 10, 1, 2, 3, 4, 7, 8, 1, 2]) + tests_expected = np.array([5, 6]) + np.testing.assert_equal(trains, trains_expected) + np.testing.assert_equal(tests, tests_expected) + + X = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + cv = BlockBootstrap( + n_resamplings=1, length=2, overlapping=True, random_state=1 + ) + trains = np.concatenate([x[0] for x in cv.split(X)]) + tests = np.concatenate([x[1] for x in cv.split(X)]) + trains_expected = np.array([5, 6, 8, 9, 9, 10, 5, 6, 0, 1, 0, 1]) + tests_expected = np.array([2, 3, 4, 7]) + np.testing.assert_equal(trains, trains_expected) + np.testing.assert_equal(tests, tests_expected) + + +def test_split_BlockBootstrap_error() -> None: + """Test outputs of subsamplings.""" + X = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + cv = BlockBootstrap() + print(cv.length) + print(cv.n_blocks) + with pytest.raises(ValueError, match=r".*At least one argument*"): + next(cv.split(X)) + cv = BlockBootstrap(length=20) + with pytest.raises(ValueError, match=r".*The length of blocks is <= 0 *"): + next(cv.split(X)) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 65cd59bcb..8a6b17763 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -16,8 +16,8 @@ from mapie.time_series_regression import MapieTimeSeriesRegressor from mapie.subsample import BlockBootstrap -X_toy = np.array(range(50)).reshape(-1, 1) -y_toy = (5.0 + 2.0*X_toy).flatten() +X_toy = np.array(range(5)).reshape(-1, 1) +y_toy = (5.0 + 2.0 * X_toy ** 1.1).flatten() X, y = make_regression(n_samples=500, n_features=10, noise=1.0, random_state=1) k = np.ones(shape=(5, X.shape[1])) METHODS = ["naive", "base", "plus", "minmax"] @@ -79,7 +79,7 @@ "cv": 3.76, "cv_plus": 3.76, "cv_minmax": 3.95, - "prefit": 4.81, + "prefit": 3.89, "cv_plus_median": 3.90, "jackknife_plus_ab": 3.76, "jackknife_minmax_ab": 3.96, @@ -93,12 +93,12 @@ "jackknife_minmax": 0.952, "cv": 0.958, "cv_plus": 0.956, - "cv_minmax": 0.966, - "prefit": 0.980, + "cv_minmax": 0.956, + "prefit": 0.90, "cv_plus_median": 0.954, "jackknife_plus_ab": 0.952, - "jackknife_minmax_ab": 0.970, - "jackknife_plus_median_ab": 0.960, + "jackknife_minmax_ab": 0.960, + "jackknife_plus_median_ab": 0.946, } @@ -365,8 +365,8 @@ def test_aggregate_with_mask_with_prefit() -> None: def test_pred_loof_isnan() -> None: """Test that if validation set is empty then prediction is empty.""" mapie_ts_reg = MapieTimeSeriesRegressor() - _, y_pred, _, _ = mapie_ts_reg._fit_and_predict_oof_model( - estimator=mapie_ts_reg(), + _, y_pred, _ = mapie_ts_reg._fit_and_predict_oof_model( + estimator=mapie_ts_reg, X=X_toy, y=y_toy, train_index=[0, 1, 2, 3, 4], @@ -376,25 +376,32 @@ def test_pred_loof_isnan() -> None: assert len(y_pred) == 0 +def test_MapieTimeSeriesRegressor_alpha_is_None() -> None: + """Test ``predict`` when ``alpha`` is None.""" + mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) + pred = mapie_ts_reg.predict(X_toy, alpha=None) + assert pred.shape == (len(pred), ) + + def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: """Test ``partial_update`` when ``ensemble`` is True.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) - assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) + assert round(mapie_ts_reg.residuals_[-1], 2) == round( + np.abs(14.189 - 14.049), 2 + ) mapie_ts_reg = mapie_ts_reg.partial_fit( X=np.array([[6]]), y=np.array([17.5]), ensemble=True ) - assert round(mapie_ts_reg.residuals_[-1], 2) == round( - np.abs(17.5 - 16.56), 2 - ) + assert round(mapie_ts_reg.residuals_[-1], 2) == round(17.5 - 18.665, 2) def test_MapieTimeSeriesRegressor_partial_fit_ensemble_F() -> None: """Test ``partial_update`` when ``ensemble`` is False.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) - assert round(mapie_ts_reg.residuals_[-1], 2) == round(np.abs(15 - 14.4), 2) + assert round(mapie_ts_reg.residuals_[-1], 2) == round( + np.abs(14.189 - 14.049), 2 + ) mapie_ts_reg = mapie_ts_reg.partial_fit( X=np.array([[6]]), y=np.array([17.5]), ensemble=False ) - assert round(mapie_ts_reg.residuals_[-1], 2) == round( - np.abs(17.5 - 16.6), 2 - ) + assert round(mapie_ts_reg.residuals_[-1], 2) == round(17.5 - 18.66504, 2) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index e6b42ab5a..351a1282e 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -1,5 +1,4 @@ from __future__ import annotations -from configparser import Interpolation from typing import Iterable, Optional, Tuple, Union, cast @@ -42,14 +41,6 @@ class MapieTimeSeriesRegressor(MapieRegressor): cv_need_agg_function = [BlockBootstrap, Subsample] valid_methods_ = ["naive", "base", "plus", "minmax"] valid_agg_functions_ = [None, "median", "mean"] - fit_attributes = [ - "single_estimator_", - "estimators_", - "k_", - "residuals_", - "n_features_in_", - "n_samples_val_", - ] def __init__( self, @@ -75,7 +66,7 @@ def fit( The model itself. """ self = super().fit(X=X, y=y, sample_weight=sample_weight) - y_pred = super().predict(X=X) + y_pred = super().predict(X) self.residuals_ = y - y_pred return self @@ -143,7 +134,7 @@ def predict( betas_0 = np.full_like(alpha_, np.nan, dtype=float) for ind, _alpha in enumerate(alpha_): - betas = np.linspace(0.0, _alpha, num=len(self.residuals_)+2) + betas = np.linspace(0.0, _alpha, num=len(self.residuals_) + 2) one_alpha_beta = np.quantile( self.residuals_, From ff1246055c0967b07f6c4b1d2bda22c8630a4b41 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Tue, 22 Mar 2022 11:00:21 +0100 Subject: [PATCH 09/32] after tyoe checking --- examples/regression/plot_timeseries_enbpi.py | 19 ++++++------- mapie/subsample.py | 29 ++++++++++---------- mapie/time_series_regression.py | 7 ++--- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 1b9879c89..96acc2c51 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -40,7 +40,6 @@ "../data/demand_temperature.csv", parse_dates=True, index_col=0 ) -print(demand_df.shape) demand_df["Date"] = pd.to_datetime(demand_df.index) demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") @@ -52,7 +51,9 @@ num_test_steps = 24 * 7 demand_train = demand_df.iloc[:-num_test_steps, :].copy() demand_test = demand_df.iloc[-num_test_steps:, :].copy() -features = ["Weekofyear", "Weekday", "Hour", "Temperature"] +features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ + f"Lag_{hour}" for hour in range(1, 2) +] X_train = demand_train.loc[ ~np.any(demand_train[features].isnull(), axis=1), features @@ -62,17 +63,13 @@ y_test = demand_test["Demand"] # Model: Random Forest previously optimized with a cross-validation -model = RandomForestRegressor(max_depth=10, n_estimators=50, random_state=59) +model = RandomForestRegressor(max_depth=17, n_estimators=150, random_state=59) # Estimate prediction intervals on test set with best estimator alpha = 0.1 -cv_MapieTimeSeries = BlockBootstrap(200, length=24, random_state=59) +cv_MapieTimeSeries = BlockBootstrap(100, length=48, random_state=59) mapie = MapieTimeSeriesRegressor( - model, - method="plus", - cv=cv_MapieTimeSeries, - agg_function="mean", - n_jobs=-1 + model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 ) mapie.fit(X_train, y_train) @@ -81,7 +78,7 @@ coverage = regression_coverage_score(y_test, y_pis[:, 0, 0], y_pis[:, 1, 0]) width = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() -# With partial_fit every 2 hours +# With partial_fit every hour gap = 1 y_pred_steps, y_pis_steps = mapie.predict(X_test.iloc[:gap, :], alpha=alpha) @@ -91,7 +88,7 @@ X_test.iloc[(step - gap):step, :], y_test.iloc[(step - gap):step] ) y_pred_gap_step, y_pis_gap_step = mapie.predict( - X_test.iloc[step:(step+gap), :], + X_test.iloc[step:(step + gap), :], alpha=alpha, ) y_pred_steps = np.concatenate((y_pred_steps, y_pred_gap_step), axis=0) diff --git a/mapie/subsample.py b/mapie/subsample.py index e52778769..f02827cd2 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -174,22 +174,23 @@ def split( Raises ------ ValueError - If ``length`` is greater than the train set size. + If ``length`` is not positive or greater than the train set size. """ - if (self.length is None) and (self.n_blocks is None): + if self.n_blocks is not None: + length = ( + self.length + if self.length is not None + else len(X) // self.n_blocks + ) + n_blocks = self.n_blocks + elif self.length is not None: + length = self.length + n_blocks = (len(X) // self.length) + 1 + else: raise ValueError( - "At least one argument in ['length', 'n_blocks]" - "has to be not None." + "At least one argument between ``length`` and " + "``n_blocks`` has to be not None" ) - - length = ( - self.length if self.length is not None else len(X) // self.n_blocks - ) - n_blocks = ( - self.n_blocks - if self.n_blocks is not None - else (len(X) // length) + 1 - ) indices = np.arange(len(X)) if (length <= 0) or (length > len(indices)): raise ValueError( @@ -200,7 +201,7 @@ def split( if self.overlapping: blocks = sliding_window_view(indices, window_shape=length) else: - indices = indices[len(indices) % length:] + indices = indices[(len(indices) % length):] blocks_number = len(indices) // length blocks = np.array_split(indices, indices_or_sections=blocks_number) random_state = check_random_state(self.random_state) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 351a1282e..369adc081 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -10,7 +10,7 @@ from .aggregation_functions import aggregate_all from .regression import MapieRegressor -from .subsample import Subsample, BlockBootstrap +from .subsample import BlockBootstrap from ._typing import ArrayLike from .utils import ( check_alpha, @@ -38,10 +38,6 @@ class MapieTimeSeriesRegressor(MapieRegressor): "Conformal prediction for dynamic time-series." """ - cv_need_agg_function = [BlockBootstrap, Subsample] - valid_methods_ = ["naive", "base", "plus", "minmax"] - valid_agg_functions_ = [None, "median", "mean"] - def __init__( self, estimator: Optional[RegressorMixin] = None, @@ -52,6 +48,7 @@ def __init__( verbose: int = 0, ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) + self.cv_need_agg_function.append(BlockBootstrap) def fit( self, From 85c936f8fc5a97b04c353d8764e173e3c933e133 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Tue, 22 Mar 2022 11:12:42 +0100 Subject: [PATCH 10/32] correct typing --- mapie/regression.py | 5 ++--- mapie/time_series_regression.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mapie/regression.py b/mapie/regression.py index 661c858f6..871fe1de9 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -19,7 +19,6 @@ from ._typing import ArrayLike from .aggregation_functions import aggregate_all, phi2D -from .subsample import Subsample from .utils import ( check_cv, check_alpha, @@ -188,7 +187,7 @@ class MapieRegressor(BaseEstimator, RegressorMixin): # type: ignore [ 5.28571429 7.17142857 9.05714286 10.94285714 12.82857143 14.71428571] """ - cv_need_agg_function = [Subsample] + cv_need_agg_function = ["Subsample"] valid_methods_ = ["naive", "base", "plus", "minmax"] valid_agg_functions_ = [None, "median", "mean"] fit_attributes = [ @@ -264,7 +263,7 @@ def _check_agg_function( ) if (agg_function is None) and ( - type(self.cv) in self.cv_need_agg_function + type(self.cv).__name__ in self.cv_need_agg_function ): raise ValueError( "You need to specify an aggregation function when " diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 369adc081..d9c5becb7 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -10,7 +10,6 @@ from .aggregation_functions import aggregate_all from .regression import MapieRegressor -from .subsample import BlockBootstrap from ._typing import ArrayLike from .utils import ( check_alpha, @@ -48,7 +47,7 @@ def __init__( verbose: int = 0, ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) - self.cv_need_agg_function.append(BlockBootstrap) + self.cv_need_agg_function.append("BlockBootstrap") def fit( self, From 07578cfaadd95ce1ce1a4e3b6c63ead56226b00a Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 28 Mar 2022 18:45:49 +0200 Subject: [PATCH 11/32] ALL TESTS OK look at docstrinf --- examples/regression/plot_timeseries_enbpi.py | 6 +- mapie/subsample.py | 26 +++--- mapie/tests/test_regression.py | 8 +- mapie/tests/test_time_series_regression.py | 44 ++++------ mapie/time_series_regression.py | 87 ++++++++++---------- mapie/utils.py | 9 +- 6 files changed, 85 insertions(+), 95 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 96acc2c51..f0e51b8be 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -67,7 +67,7 @@ # Estimate prediction intervals on test set with best estimator alpha = 0.1 -cv_MapieTimeSeries = BlockBootstrap(100, length=48, random_state=59) +cv_MapieTimeSeries = BlockBootstrap(50, length=48, random_state=59) mapie = MapieTimeSeriesRegressor( model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 ) @@ -85,10 +85,10 @@ for step in range(gap, len(X_test), gap): mapie.partial_fit( - X_test.iloc[(step - gap):step, :], y_test.iloc[(step - gap):step] + X_test.iloc[(step - gap) : step, :], y_test.iloc[(step - gap) : step] ) y_pred_gap_step, y_pis_gap_step = mapie.predict( - X_test.iloc[step:(step + gap), :], + X_test.iloc[step : (step + gap), :], alpha=alpha, ) y_pred_steps = np.concatenate((y_pred_steps, y_pred_gap_step), axis=0) diff --git a/mapie/subsample.py b/mapie/subsample.py index b53047403..52d5354d8 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -9,7 +9,7 @@ from sklearn.utils import check_random_state, resample from sklearn.utils.validation import _num_samples -from ._typing import ArrayLike, NDArray +from ._typing import NDArray class Subsample(BaseCrossValidator): @@ -56,15 +56,14 @@ def __init__( self.random_state = random_state def split( - self, - X: ArrayLike + self, X: NDArray ) -> Generator[Tuple[NDArray, NDArray], None, None]: """ Generate indices to split data into training and test sets. Parameters ---------- - X : ArrayLike of shape (n_samples, n_features) + X : NDArray of shape (n_samples, n_features) Training data. Yields @@ -157,21 +156,21 @@ def __init__( self.random_state = random_state def split( - self, X: ArrayLike - ) -> Generator[Tuple[Any, ArrayLike], None, None]: + self, X: NDArray + ) -> Generator[Tuple[NDArray, NDArray], None, None]: """ Generate indices to split data into training and test sets. Parameters ---------- - X : ArrayLike of shape (n_samples, n_features) + X : NDArray of shape (n_samples, n_features) Training data. Yields ------ - train : ArrayLike of shape (n_indices_training,) + train : NDArray of shape (n_indices_training,) The training set indices for that split. - test : ArrayLike of shape (n_indices_test,) + test : NDArray of shape (n_indices_test,) The testing set indices for that split. Raises ------ @@ -190,7 +189,7 @@ def split( n_blocks = (len(X) // self.length) + 1 else: raise ValueError( - "At least one argument between ``length`` and " + "At least one argument between ``length`` or " "``n_blocks`` has to be not None" ) indices = np.arange(len(X)) @@ -203,9 +202,12 @@ def split( if self.overlapping: blocks = sliding_window_view(indices, window_shape=length) else: - indices = indices[(len(indices) % length):] + indices = indices[(len(indices) % length) :] blocks_number = len(indices) // length - blocks = np.array_split(indices, indices_or_sections=blocks_number) + blocks = np.asarray( + np.array_split(indices, indices_or_sections=blocks_number) + ) + random_state = check_random_state(self.random_state) for k in range(self.n_resamplings): diff --git a/mapie/tests/test_regression.py b/mapie/tests/test_regression.py index b68c5cf43..e96198223 100644 --- a/mapie/tests/test_regression.py +++ b/mapie/tests/test_regression.py @@ -450,7 +450,7 @@ def test_pipeline_compatibility() -> None: { "x_cat": ["A", "A", "B", "A", "A", "B"], "x_num": [0, 1, 1, 4, np.nan, 5], - "y": [5, 7, 3, 9, 10, 8] + "y": [5, 7, 3, 9, 10, 8], } ) y = pd.Series([5, 7, 3, 9, 10, 8]) @@ -460,14 +460,12 @@ def test_pipeline_compatibility() -> None: ] ) categorical_preprocessor = Pipeline( - steps=[ - ("encoding", OneHotEncoder(handle_unknown="ignore")) - ] + steps=[("encoding", OneHotEncoder(handle_unknown="ignore"))] ) preprocessor = ColumnTransformer( [ ("cat", categorical_preprocessor, ["x_cat"]), - ("num", numeric_preprocessor, ["x_num"]) + ("num", numeric_preprocessor, ["x_num"]), ] ) pipe = make_pipeline(preprocessor, LinearRegression()) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 8a6b17763..c8c5c79b4 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -5,12 +5,13 @@ import numpy as np import pytest +from sklearn import ensemble from sklearn.datasets import make_regression from sklearn.linear_model import LinearRegression from sklearn.model_selection import KFold, LeaveOneOut, train_test_split from typing_extensions import TypedDict -from mapie._typing import ArrayLike +from mapie._typing import NDArray from mapie.aggregation_functions import aggregate_all from mapie.metrics import regression_coverage_score from mapie.time_series_regression import MapieTimeSeriesRegressor @@ -109,14 +110,13 @@ def test_invalid_agg_function(agg_function: Any) -> None: mapie_ts_reg = MapieTimeSeriesRegressor(agg_function=None) with pytest.raises(ValueError, match=r".*If ensemble is True*"): mapie_ts_reg.fit(X_toy, y_toy) - mapie_ts_reg.predict(X_toy, ensemble=True) @pytest.mark.parametrize("strategy", [*STRATEGIES]) @pytest.mark.parametrize("dataset", [(X, y), (X_toy, y_toy)]) @pytest.mark.parametrize("alpha", [0.2, [0.2, 0.4], (0.2, 0.4)]) def test_predict_output_shape( - strategy: str, alpha: Any, dataset: Tuple[ArrayLike, ArrayLike] + strategy: str, alpha: Any, dataset: Tuple[NDArray, NDArray] ) -> None: """Test predict output shape.""" mapie_ts_reg = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) @@ -169,8 +169,10 @@ def test_results_for_ordered_alpha(strategy: str) -> None: mapie = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) mapie.fit(X, y) y_pred, y_pis = mapie.predict(X, alpha=[0.05, 0.1]) - assert (y_pis[:, 0, 0] <= y_pis[:, 0, 1]).all() - assert (y_pis[:, 1, 0] >= y_pis[:, 1, 1]).all() + assert np.all( + np.abs(y_pis[:, 1, 0] - y_pis[:, 0, 0]) + >= np.abs(y_pis[:, 1, 1] - y_pis[:, 0, 1]) + ) @pytest.mark.parametrize("strategy", [*STRATEGIES]) @@ -264,7 +266,7 @@ def test_linear_regression_results(strategy: str) -> None: def test_results_prefit_ignore_method() -> None: """Test that method is ignored when ``cv="prefit"``.""" estimator = LinearRegression().fit(X, y) - all_y_pis: List[ArrayLike] = [] + all_y_pis: List[NDArray] = [] for method in METHODS: mapie_ts_reg = MapieTimeSeriesRegressor( estimator=estimator, cv="prefit", method=method @@ -371,7 +373,6 @@ def test_pred_loof_isnan() -> None: y=y_toy, train_index=[0, 1, 2, 3, 4], val_index=[], - k=0, ) assert len(y_pred) == 0 @@ -379,29 +380,20 @@ def test_pred_loof_isnan() -> None: def test_MapieTimeSeriesRegressor_alpha_is_None() -> None: """Test ``predict`` when ``alpha`` is None.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) - pred = mapie_ts_reg.predict(X_toy, alpha=None) - assert pred.shape == (len(pred), ) - -def test_MapieTimeSeriesRegressor_partial_fit_ensemble_T() -> None: - """Test ``partial_update`` when ``ensemble`` is True.""" - mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) - assert round(mapie_ts_reg.residuals_[-1], 2) == round( - np.abs(14.189 - 14.049), 2 - ) - mapie_ts_reg = mapie_ts_reg.partial_fit( - X=np.array([[6]]), y=np.array([17.5]), ensemble=True - ) - assert round(mapie_ts_reg.residuals_[-1], 2) == round(17.5 - 18.665, 2) + with pytest.raises(ValueError, match=r".*too many values to unpackt*"): + y_pred, y_pis = mapie_ts_reg.predict(X_toy, alpha=None) -def test_MapieTimeSeriesRegressor_partial_fit_ensemble_F() -> None: - """Test ``partial_update`` when ``ensemble`` is False.""" +def test_MapieTimeSeriesRegressor_partial_fit_ensemble() -> None: + """Test ``partial_fit``.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) - assert round(mapie_ts_reg.residuals_[-1], 2) == round( - np.abs(14.189 - 14.049), 2 + assert round(mapie_ts_reg.conformity_scores_[-1], 2) == round( + np.abs(14.189 - 14.038), 2 ) mapie_ts_reg = mapie_ts_reg.partial_fit( - X=np.array([[6]]), y=np.array([17.5]), ensemble=False + X=np.array([[6]]), y=np.array([17.5]) + ) + assert round(mapie_ts_reg.conformity_scores_[-1], 2) == round( + 17.5 - 18.665, 2 ) - assert round(mapie_ts_reg.residuals_[-1], 2) == round(17.5 - 18.66504, 2) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index d9c5becb7..f4c482546 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -3,6 +3,7 @@ from typing import Iterable, Optional, Tuple, Union, cast import numpy as np +import numpy.ma as ma from sklearn.base import RegressorMixin from sklearn.model_selection import BaseCrossValidator from sklearn.utils import check_array @@ -10,7 +11,7 @@ from .aggregation_functions import aggregate_all from .regression import MapieRegressor -from ._typing import ArrayLike +from ._typing import ArrayLike, NDArray from .utils import ( check_alpha, check_alpha_and_n_samples, @@ -62,16 +63,18 @@ def fit( The model itself. """ self = super().fit(X=X, y=y, sample_weight=sample_weight) - y_pred = super().predict(X) - self.residuals_ = y - y_pred + y_pred, _ = super().predict(X, alpha=0.5, ensemble=True) + self.conformity_scores_ = np.asarray(y) - y_pred return self def partial_fit( - self, X: ArrayLike, y: ArrayLike, ensemble: bool = True + self, + X: ArrayLike, + y: ArrayLike, ) -> MapieTimeSeriesRegressor: """ - Update the ``residuals_`` attribute when data with known labels are - available. + Update the ``conformity_scores_`` attribute when data with known labels + are available. Parameters ---------- @@ -81,28 +84,24 @@ def partial_fit( y : ArrayLike of shape (n_samples,) Input labels. - ensemble : bool - Boolean corresponding to the ``ensemble`` argument of ``predict`` - method, determining whether the predictions computed to determine - the new ``residuals_`` are ensembled or not. - If False, predictions are those of the model trained on the whole - training set. - Returns ------- MapieTimeSeriesRegressor The model itself. """ - y_pred, _ = self.predict(X, alpha=0.5, ensemble=ensemble) - new_residuals = y - y_pred + y_pred, _ = self.predict(X, alpha=0.5, ensemble=True) + new_conformity_scores_ = np.asarray(y) - np.asarray(y_pred) + new_conformity_scores_ = new_conformity_scores_[ + ~np.isnan(new_conformity_scores_) + ] cut_index = min( - len(new_residuals[~np.isnan(new_residuals)]), len(self.residuals_) + len(new_conformity_scores_), len(self.conformity_scores_) ) - self.residuals_ = np.concatenate( + self.conformity_scores_ = np.concatenate( [ - self.residuals_[cut_index:], - new_residuals[~np.isnan(new_residuals)], + self.conformity_scores_[cut_index:], + new_conformity_scores_, ], axis=0, ) @@ -113,63 +112,64 @@ def predict( X: ArrayLike, ensemble: bool = False, alpha: Optional[Union[float, Iterable[float]]] = None, - ) -> Union[ArrayLike, Tuple[ArrayLike, ArrayLike]]: + ) -> Union[NDArray, Tuple[NDArray, NDArray]]: # Checks check_is_fitted(self, self.fit_attributes) self._check_ensemble(ensemble) - alpha_ = check_alpha(alpha) + alpha = cast(Optional[NDArray], check_alpha(alpha)) X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) y_pred = self.single_estimator_.predict(X) + n = len(self.conformity_scores_) if alpha is None: return np.array(y_pred) else: - alpha_ = cast(ArrayLike, alpha_) - check_alpha_and_n_samples(alpha_, self.residuals_.shape[0]) - betas_0 = np.full_like(alpha_, np.nan, dtype=float) - - for ind, _alpha in enumerate(alpha_): - betas = np.linspace(0.0, _alpha, num=len(self.residuals_) + 2) + alpha_np = cast(NDArray, alpha) + check_alpha_and_n_samples(alpha_np, n) + betas_0 = np.full_like(alpha_np, np.nan, dtype=float) + for ind, _alpha in enumerate(alpha_np): + betas = np.linspace(0.0, _alpha, num=n + 1) one_alpha_beta = np.quantile( - self.residuals_, + ma.masked_invalid(self.conformity_scores_), 1 - _alpha + betas, axis=0, interpolation="higher", - ) + ) # type: ignore beta = np.quantile( - self.residuals_, + ma.masked_invalid(self.conformity_scores_), betas, axis=0, interpolation="lower", - ) + ) # type: ignore betas_0[ind] = betas[np.argmin(one_alpha_beta - beta, axis=0)] + lower_quantiles = np.quantile( - self.residuals_, + ma.masked_invalid(self.conformity_scores_), betas_0, axis=0, interpolation="lower", - ) + ) # type: ignore higher_quantiles = np.quantile( - self.residuals_, - 1 - alpha_ + betas_0, + ma.masked_invalid(self.conformity_scores_), + 1 - alpha_np + betas_0, axis=0, interpolation="higher", - ) + ) # type: ignore if self.method in ["naive", "base"] or self.cv == "prefit": y_pred_low = np.column_stack( [ y_pred[:, np.newaxis] + lower_quantiles[k] - for k in range(len(alpha_)) + for k in range(len(alpha_np)) ] ) y_pred_up = np.column_stack( [ y_pred[:, np.newaxis] + higher_quantiles[k] - for k in range(len(alpha_)) + for k in range(len(alpha_np)) ] ) else: @@ -187,12 +187,15 @@ def predict( if self.method == "plus": pred = aggregate_all(self.agg_function, y_pred_multi) y_pred_low = np.column_stack( - [pred + lower_quantiles[k] for k in range(len(alpha_))] + [ + pred + lower_quantiles[k] + for k in range(len(alpha_np)) + ] ) y_pred_up = np.column_stack( [ pred + higher_quantiles[k] - for k in range(len(alpha_)) + for k in range(len(alpha_np)) ] ) @@ -202,13 +205,13 @@ def predict( y_pred_low = np.column_stack( [ lower_bounds + lower_quantiles[k] - for k in range(len(alpha_)) + for k in range(len(alpha_np)) ] ) y_pred_up = np.column_stack( [ upper_bounds + higher_quantiles[k] - for k in range(len(alpha_)) + for k in range(len(alpha_np)) ] ) if ensemble: diff --git a/mapie/utils.py b/mapie/utils.py index 0f83f09e5..44fdc7e62 100644 --- a/mapie/utils.py +++ b/mapie/utils.py @@ -5,19 +5,14 @@ import numpy as np from sklearn.base import ClassifierMixin, RegressorMixin from sklearn.model_selection import BaseCrossValidator, KFold, LeaveOneOut -from sklearn.utils.validation import ( - _check_sample_weight, - _num_features -) +from sklearn.utils.validation import _check_sample_weight, _num_features from sklearn.utils import _safe_indexing from ._typing import ArrayLike, NDArray def check_null_weight( - sample_weight: Optional[ArrayLike], - X: ArrayLike, - y: ArrayLike + sample_weight: Optional[ArrayLike], X: ArrayLike, y: ArrayLike ) -> Tuple[Optional[NDArray], ArrayLike, ArrayLike]: """ Check sample weights and remove samples with null sample weights. From b5b46d6f2afeb5dcbfe693e0f74e612a9d2c0a23 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 28 Mar 2022 18:48:59 +0200 Subject: [PATCH 12/32] make lint PASS --- examples/regression/plot_timeseries_enbpi.py | 4 ++-- mapie/subsample.py | 2 +- mapie/tests/test_time_series_regression.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index f0e51b8be..391d697e9 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -85,10 +85,10 @@ for step in range(gap, len(X_test), gap): mapie.partial_fit( - X_test.iloc[(step - gap) : step, :], y_test.iloc[(step - gap) : step] + X_test.iloc[(step - gap):step, :], y_test.iloc[(step - gap):step] ) y_pred_gap_step, y_pis_gap_step = mapie.predict( - X_test.iloc[step : (step + gap), :], + X_test.iloc[step:(step + gap), :], alpha=alpha, ) y_pred_steps = np.concatenate((y_pred_steps, y_pred_gap_step), axis=0) diff --git a/mapie/subsample.py b/mapie/subsample.py index 52d5354d8..049ac50f6 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -202,7 +202,7 @@ def split( if self.overlapping: blocks = sliding_window_view(indices, window_shape=length) else: - indices = indices[(len(indices) % length) :] + indices = indices[(len(indices) % length):] blocks_number = len(indices) // length blocks = np.asarray( np.array_split(indices, indices_or_sections=blocks_number) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index c8c5c79b4..80dcad92a 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -5,7 +5,6 @@ import numpy as np import pytest -from sklearn import ensemble from sklearn.datasets import make_regression from sklearn.linear_model import LinearRegression from sklearn.model_selection import KFold, LeaveOneOut, train_test_split From 8a807e9e9dcba84db5e436f5e79f8920f88daaca Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 30 Mar 2022 20:12:30 +0200 Subject: [PATCH 13/32] maked_quantile not perfect, yet --- README.rst | 5 + examples/regression/plot_timeseries_enbpi.py | 227 ++++++++++++---- .../regression/plot_timeseries_enbpi_train.py | 83 ++++++ mapie/regression.py | 10 +- mapie/time_series_regression.py | 245 ++++++++++++------ mapie/utils.py | 103 ++++++++ 6 files changed, 536 insertions(+), 137 deletions(-) create mode 100644 examples/regression/plot_timeseries_enbpi_train.py diff --git a/README.rst b/README.rst index cb21db27f..b40c0cadb 100644 --- a/README.rst +++ b/README.rst @@ -244,6 +244,11 @@ MAPIE methods belong to the field of conformal inference. "Uncertainty Sets for Image Classifiers using Conformal Prediction." International Conference on Learning Representations 2021. +[6] Chen Xu, Yao Xie. +"Conformal prediction for dynamic time-series" +https://arxiv.org/abs/2010.09107 + + 📝 License ========== diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 391d697e9..5dbb47f42 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -4,24 +4,24 @@ ================================================================== This example uses :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate -prediction intervals associated with time series forecast. The implementation -is still at its first step, based on Jackknife+-after-bootsrtap, to estimate -residuals and associated prediction intervals. +prediction intervals associated with time series forecast. It follows [6] and +an alternative expermimental implemetation inspired from [2] We use here the Victoria electricity demand dataset used in the book "Forecasting: Principles and Practice" by R. J. Hyndman and G. Athanasopoulos. The electricity demand features daily and weekly seasonalities and is impacted by the temperature, considered here as a exogeneous variable. -A Random Forest model is fitted on data. The hyper-parameters are optimized -with a :class:`sklearn.model_selection.RandomizedSearchCV` using a sequential -:class:`sklearn.model_selection.TimeSeriesSplit` cross validation, in which the -training set is prior to the validation set. +A Random Forest model is aloready fitted on data. The hyper-parameters are +optimized with a :class:`sklearn.model_selection.RandomizedSearchCV` using a +sequential :class:`sklearn.model_selection.TimeSeriesSplit` cross validation, +in which the training set is prior to the validation set. The best model is then feeded into :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the associated prediction intervals. We compare two approaches: one with no `partial_fit` call and one with `partial_fit` every step. """ +import copy import warnings import numpy as np @@ -63,87 +63,202 @@ y_test = demand_test["Demand"] # Model: Random Forest previously optimized with a cross-validation -model = RandomForestRegressor(max_depth=17, n_estimators=150, random_state=59) +model = RandomForestRegressor(max_depth=15, n_estimators=1, random_state=59) # Estimate prediction intervals on test set with best estimator alpha = 0.1 -cv_MapieTimeSeries = BlockBootstrap(50, length=48, random_state=59) -mapie = MapieTimeSeriesRegressor( +cv_MapieTimeSeries = BlockBootstrap(20, length=48, random_state=59) + +mapie_model = MapieTimeSeriesRegressor( model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 ) -mapie.fit(X_train, y_train) +mapie_model = mapie_model.fit(X_train, y_train) +mapie_no_pfit = mapie_model.fit(X_train, y_train) +mapie_pfit_JAB_F = mapie_model.fit(X_train, y_train) +mapie_pfit_JAB_T = mapie_model.fit(X_train, y_train) + +gap_pfit = 1 + +# With no partial_fit, JAB_like is False +y_pred_npfit_JAB_F, y_pis_npfit_JAB_F = mapie_no_pfit.predict( + X_test, alpha=alpha, ensemble=True +) +coverage_npfit_JAB_F = regression_coverage_score( + y_test, y_pis_npfit_JAB_F[:, 0, 0], y_pis_npfit_JAB_F[:, 1, 0] +) +width_npfit_JAB_F = ( + y_pis_npfit_JAB_F[:, 1, 0] - y_pis_npfit_JAB_F[:, 0, 0] +).mean() + +# With partial_fit every hour, JAB_like is False + +y_pred_pfit_JAB_F, y_pis_pfit_JAB_F = mapie_pfit_JAB_F.predict( + X_test.iloc[:gap_pfit, :], alpha=alpha, ensemble=True +) + +for step in range(gap_pfit, len(X_test), gap_pfit): + mapie_pfit_JAB_F.partial_fit( + X_test.iloc[(step - gap_pfit) : step, :], + y_test.iloc[(step - gap_pfit) : step], + ) + y_pred_gap_step, y_pis_gap_step = mapie_pfit_JAB_F.predict( + X_test.iloc[step : (step + gap_pfit), :], alpha=alpha, ensemble=True + ) + y_pred_pfit_JAB_F = np.concatenate( + (y_pred_pfit_JAB_F, y_pred_gap_step), axis=0 + ) + y_pis_pfit_JAB_F = np.concatenate( + (y_pis_pfit_JAB_F, y_pis_gap_step), axis=0 + ) -# With no partial_fit -y_pred, y_pis = mapie.predict(X_test, alpha=alpha) -coverage = regression_coverage_score(y_test, y_pis[:, 0, 0], y_pis[:, 1, 0]) -width = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() +coverage_pfit_JAB_F = regression_coverage_score( + y_test, y_pis_pfit_JAB_F[:, 0, 0], y_pis_pfit_JAB_F[:, 1, 0] +) +width_pfit_JAB_F = ( + y_pis_pfit_JAB_F[:, 1, 0] - y_pis_pfit_JAB_F[:, 0, 0] +).mean() -# With partial_fit every hour -gap = 1 -y_pred_steps, y_pis_steps = mapie.predict(X_test.iloc[:gap, :], alpha=alpha) +# With no partial_fit, JAB_like is True +y_pred_npfit_JAB_T, y_pis_npfit_JAB_T = mapie_no_pfit.predict( + X_test, alpha=alpha, JAB_Like=True +) +coverage_npfit_JAB_T = regression_coverage_score( + y_test, y_pis_npfit_JAB_T[:, 0, 0], y_pis_npfit_JAB_T[:, 1, 0] +) +width_npfit_JAB_T = ( + y_pis_npfit_JAB_T[:, 1, 0] - y_pis_npfit_JAB_T[:, 0, 0] +).mean() -for step in range(gap, len(X_test), gap): - mapie.partial_fit( - X_test.iloc[(step - gap):step, :], y_test.iloc[(step - gap):step] +# With partial_fit every hour, JAB_like is True +y_pred_pfit_JAB_T, y_pis_pfit_JAB_T = mapie_no_pfit.predict( + X_test.iloc[:gap_pfit, :], alpha=alpha, JAB_Like=True +) +for step in range(gap_pfit, len(X_test), gap_pfit): + mapie_pfit_JAB_T.partial_fit( + X_test.iloc[(step - gap_pfit) : step, :], + y_test.iloc[(step - gap_pfit) : step], ) - y_pred_gap_step, y_pis_gap_step = mapie.predict( - X_test.iloc[step:(step + gap), :], + y_pred_gap_step, y_pis_gap_step = mapie_pfit_JAB_T.predict( + X_test.iloc[step : (step + gap_pfit), :], alpha=alpha, + JAB_Like=True, + ensemble=True, + ) + y_pred_pfit_JAB_T = np.concatenate( + (y_pred_pfit_JAB_T, y_pred_gap_step), axis=0 + ) + y_pis_pfit_JAB_T = np.concatenate( + (y_pis_pfit_JAB_T, y_pis_gap_step), axis=0 ) - y_pred_steps = np.concatenate((y_pred_steps, y_pred_gap_step), axis=0) - y_pis_steps = np.concatenate((y_pis_steps, y_pis_gap_step), axis=0) -coverage_steps = regression_coverage_score( - y_test, y_pis_steps[:, 0, 0], y_pis_steps[:, 1, 0] +coverage_pfit_JAB_T = regression_coverage_score( + y_test, y_pis_pfit_JAB_T[:, 0, 0], y_pis_pfit_JAB_T[:, 1, 0] ) -width_steps = (y_pis_steps[:, 1, 0] - y_pis_steps[:, 0, 0]).mean() +width_pfit_JAB_T = ( + y_pis_pfit_JAB_T[:, 1, 0] - y_pis_pfit_JAB_T[:, 0, 0] +).mean() # Print results print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nWithout any partial_fit:" - f"{coverage:.3f}, {width:.3f}" + "\nWithout any partial_fit. JAB_like is False:" + f"{coverage_npfit_JAB_F:.3f}, {width_npfit_JAB_F:.3f}" +) +print( + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " + "\nWithout any partial_fit. JAB_like is True:" + f"{coverage_npfit_JAB_T:.3f}, {width_npfit_JAB_T:.3f}" +) +print( + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " + "\nWith partial_fit. JAB_like is False:" + f"{coverage_pfit_JAB_F:.3f}, {width_pfit_JAB_F:.3f}" +) +print( + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " + "\nWith partial_fit. JAB_like is True:" + f"{coverage_pfit_JAB_T:.3f}, {width_pfit_JAB_T:.3f}" ) # Plot estimated prediction intervals on test set -fig = plt.figure(figsize=(15, 5)) -ax = fig.add_subplot(1, 1, 1) -ax.set_ylabel("Hourly demand (GW)") -ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") -ax.plot(demand_test.index, y_pred, lw=2, c="C2", label="Predictions") -ax.fill_between( +fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots( + nrows=2, ncols=2, figsize=(30, 10), sharey="row", sharex="col" +) + +ax1.set_ylabel("Hourly demand (GW)") +ax1.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +ax1.plot( + demand_test.index, y_pred_npfit_JAB_F, lw=2, c="C2", label="Predictions" +) +ax1.fill_between( demand_test.index, - y_pis[:, 0, 0], - y_pis[:, 1, 0], + y_pis_npfit_JAB_F[:, 0, 0], + y_pis_npfit_JAB_F[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax.legend() -plt.title("Without partial_fit") -plt.show() +ax1.legend() +ax1.set_title( + "Without partial_fit, JAB False." + f"Coverage:{coverage_npfit_JAB_F:.3f} Width:{width_npfit_JAB_F:.3f}" +) -print( - "Coverage / prediction interval width mean for MapieTimeSeriesRegressor " - "\nWith partial_fit every step: " - f"{coverage_steps:.3f}, {width_steps:.3f}" +ax2.set_ylabel("Hourly demand (GW)") +ax2.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +ax2.plot( + demand_test.index, y_pred_npfit_JAB_T, lw=2, c="C2", label="Predictions" +) +ax2.fill_between( + demand_test.index, + y_pis_npfit_JAB_T[:, 0, 0], + y_pis_npfit_JAB_T[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", +) +ax2.legend() +ax2.set_title( + "Without partial_fit, JAB True." + f"Coverage:{coverage_npfit_JAB_T:.3f} Width:{width_npfit_JAB_T:.3f}" ) -# Plot estimated prediction intervals on test set -fig = plt.figure(figsize=(15, 5)) -ax = fig.add_subplot(1, 1, 1) -ax.set_ylabel("Hourly demand (GW)") -ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") -ax.plot(demand_test.index, y_pred_steps, lw=2, c="C2", label="Predictions") -ax.fill_between( +ax3.set_ylabel("Hourly demand (GW)") +ax3.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +ax3.plot( + demand_test.index, y_pred_npfit_JAB_F, lw=2, c="C2", label="Predictions" +) +ax3.fill_between( + demand_test.index, + y_pis_npfit_JAB_F[:, 0, 0], + y_pis_npfit_JAB_F[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", +) +ax3.legend() +ax3.set_title( + "With partial_fit, JAB False." + f"Coverage:{coverage_npfit_JAB_F:.3f} Width:{width_npfit_JAB_F:.3f}" +) + +ax4.set_ylabel("Hourly demand (GW)") +ax4.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +ax4.plot( + demand_test.index, y_pred_pfit_JAB_T, lw=2, c="C2", label="Predictions" +) +ax4.fill_between( demand_test.index, - y_pis_steps[:, 0, 0], - y_pis_steps[:, 1, 0], + y_pis_pfit_JAB_T[:, 0, 0], + y_pis_pfit_JAB_T[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax.legend() -plt.title("With partial_fit") +ax4.legend() +ax4.set_title( + "With partial_fit, JAB True." + f"Coverage:{coverage_npfit_JAB_T:.3f} Width:{width_npfit_JAB_T:.3f}" +) plt.show() diff --git a/examples/regression/plot_timeseries_enbpi_train.py b/examples/regression/plot_timeseries_enbpi_train.py new file mode 100644 index 000000000..ec71f4472 --- /dev/null +++ b/examples/regression/plot_timeseries_enbpi_train.py @@ -0,0 +1,83 @@ +""" +================================================================== +Estimating prediction intervals of time series forecast with EnbPI +================================================================== +This example uses +:class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate +prediction intervals associated with time series forecast. It follows [6] and +an alternative expermimental implemetation inspired from [2] + +We use here the Victoria electricity demand dataset used in the book +"Forecasting: Principles and Practice" by R. J. Hyndman and G. Athanasopoulos. +The electricity demand features daily and weekly seasonalities and is impacted +by the temperature, considered here as a exogeneous variable. + +A Random Forest model is aloready fitted on data. The hyper-parameters are +optimized with a :class:`sklearn.model_selection.RandomizedSearchCV` using a +sequential :class:`sklearn.model_selection.TimeSeriesSplit` cross validation, +in which the training set is prior to the validation set. +The best model is then feeded into +:class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the +associated prediction intervals. We compare two approaches: one with no +`partial_fit` call and one with `partial_fit` every step. +""" +import warnings + +import numpy as np +import pandas as pd +from scipy.stats import randint +from sklearn.ensemble import RandomForestRegressor +from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit + + +from mapie.metrics import regression_coverage_score +from mapie.time_series_regression import MapieTimeSeriesRegressor + +warnings.simplefilter("ignore") + +# Load input data and feature engineering +demand_df = pd.read_csv( + "../data/demand_temperature.csv", parse_dates=True, index_col=0 +) + +demand_df["Date"] = pd.to_datetime(demand_df.index) +demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") +demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") +demand_df["Hour"] = demand_df.index.hour +for hour in range(1, 3): + demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) + +# Train/validation/test split +num_test_steps = 24 * 7 +demand_train = demand_df.iloc[:-num_test_steps, :].copy() +demand_test = demand_df.iloc[-num_test_steps:, :].copy() +features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ + f"Lag_{hour}" for hour in range(1, 2) +] + +X_train = demand_train.loc[ + ~np.any(demand_train[features].isnull(), axis=1), features +] +y_train = demand_train.loc[X_train.index, "Demand"] +X_test = demand_test.loc[:, features] +y_test = demand_test["Demand"] + +# CV parameter search +n_iter = 100 +n_splits = 5 +tscv = TimeSeriesSplit(n_splits=n_splits) +random_state = 59 +rf_model = RandomForestRegressor(random_state=random_state) +rf_params = {"max_depth": randint(2, 30), "n_estimators": randint(10, 100)} +cv_obj = RandomizedSearchCV( + rf_model, + param_distributions=rf_params, + n_iter=n_iter, + cv=tscv, + scoring="neg_root_mean_squared_error", + random_state=random_state, + verbose=0, + n_jobs=-1, +) +cv_obj.fit(X_train, y_train) +print(cv_obj.best_estimator_) \ No newline at end of file diff --git a/mapie/regression.py b/mapie/regression.py index 8117fd494..611225886 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -1,4 +1,5 @@ from __future__ import annotations +from configparser import Interpolation from typing import Iterable, List, Optional, Tuple, Union, cast @@ -29,6 +30,7 @@ check_null_weight, check_verbose, fit_estimator, + masked_quantile, ) @@ -192,7 +194,7 @@ class MapieRegressor(BaseEstimator, RegressorMixin): "estimators_", "k_", "conformity_scores_", - "n_features_in_" + "n_features_in_", ] def __init__( @@ -609,7 +611,7 @@ def predict( alpha_np = cast(NDArray, alpha) check_alpha_and_n_samples(alpha_np, n) if self.method in ["naive", "base"] or self.cv == "prefit": - quantile = np.quantile( + quantile = masked_quantile( self.conformity_scores_, 1 - alpha_np, method="higher" ) y_pred_low = y_pred[:, np.newaxis] - quantile @@ -644,7 +646,7 @@ def predict( y_pred_low = np.column_stack( [ - np.quantile( + masked_quantile( ma.masked_invalid(lower_bounds), _alpha, axis=1, @@ -656,7 +658,7 @@ def predict( y_pred_up = np.column_stack( [ - np.quantile( + masked_quantile( ma.masked_invalid(upper_bounds), 1 - _alpha, axis=1, diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index f4c482546..ab2d5159a 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -15,27 +15,25 @@ from .utils import ( check_alpha, check_alpha_and_n_samples, + masked_quantile, ) class MapieTimeSeriesRegressor(MapieRegressor): """ - Prediction interval with out-of-fold residuals for time series. + Prediction interval with out-of-fold residuals for time series. This class implements the EnbPI strategy and some variations for estimating prediction intervals on single-output time series. - It is ``MapieRegressor`` with one more method ``partial_fit``. - Actually, EnbPI only corresponds to MapieRegressor if the ``cv`` argument - if of type ``Subsample`` (Jackknife+-after-Bootstrap method). Moreover, for - the moment we consider the absolute values of the residuals of the model, - and consequently the prediction intervals are symmetryc. Moreover we did - not implement the PI's optimization to the oracle interval yet. It is still - a first step before implementing the actual EnbPI. + + Actually, EnbPI only corresponds to ``MapieTimeSeriesRegressor`` if the + ``cv`` argument if of type ``BlockBootstrap``. References ---------- Chen Xu, and Yao Xie. - "Conformal prediction for dynamic time-series." + [6] "Conformal prediction for dynamic time-series." + https://arxiv.org/abs/2010.09107 """ def __init__( @@ -112,6 +110,7 @@ def predict( X: ArrayLike, ensemble: bool = False, alpha: Optional[Union[float, Iterable[float]]] = None, + JAB_Like=False, ) -> Union[NDArray, Tuple[NDArray, NDArray]]: # Checks @@ -127,93 +126,185 @@ def predict( else: alpha_np = cast(NDArray, alpha) check_alpha_and_n_samples(alpha_np, n) - betas_0 = np.full_like(alpha_np, np.nan, dtype=float) - for ind, _alpha in enumerate(alpha_np): - betas = np.linspace(0.0, _alpha, num=n + 1) - one_alpha_beta = np.quantile( + if ( + (not JAB_Like) + or (self.method in ["naive", "base"]) + or (self.cv == "prefit") + ): + # This version of predict is the implementation of the paper + # [6]. Its PIs are closed to the oracle's ones. + betas_0 = np.full_like(alpha_np, np.nan, dtype=float) + for ind, _alpha in enumerate(alpha_np): + betas = np.linspace( + _alpha / (n + 1), _alpha, num=n + 1, endpoint=False + ) + + one_alpha_beta = masked_quantile( + ma.masked_invalid(self.conformity_scores_), + 1 - _alpha + betas, + axis=0, + method="higher", + ) # type: ignore + + beta = masked_quantile( + ma.masked_invalid(self.conformity_scores_), + betas, + axis=0, + method="lower", + ) # type: ignore + betas_0[ind] = betas[ + np.argmin(one_alpha_beta - beta, axis=0) + ] + + lower_quantiles = masked_quantile( ma.masked_invalid(self.conformity_scores_), - 1 - _alpha + betas, + betas_0, axis=0, - interpolation="higher", + method="lower", ) # type: ignore - - beta = np.quantile( + higher_quantiles = masked_quantile( ma.masked_invalid(self.conformity_scores_), - betas, + 1 - alpha_np + betas_0, axis=0, - interpolation="lower", + method="higher", ) # type: ignore - betas_0[ind] = betas[np.argmin(one_alpha_beta - beta, axis=0)] - - lower_quantiles = np.quantile( - ma.masked_invalid(self.conformity_scores_), - betas_0, - axis=0, - interpolation="lower", - ) # type: ignore - higher_quantiles = np.quantile( - ma.masked_invalid(self.conformity_scores_), - 1 - alpha_np + betas_0, - axis=0, - interpolation="higher", - ) # type: ignore - - if self.method in ["naive", "base"] or self.cv == "prefit": - y_pred_low = np.column_stack( - [ - y_pred[:, np.newaxis] + lower_quantiles[k] - for k in range(len(alpha_np)) - ] - ) - y_pred_up = np.column_stack( - [ - y_pred[:, np.newaxis] + higher_quantiles[k] - for k in range(len(alpha_np)) - ] - ) - else: - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size thanks to - # the shape of k_. - - y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) - - if self.method == "plus": - pred = aggregate_all(self.agg_function, y_pred_multi) + if (self.method in ["naive", "base"]) or (self.cv == "prefit"): y_pred_low = np.column_stack( [ - pred + lower_quantiles[k] + y_pred[:, np.newaxis] + lower_quantiles[k] for k in range(len(alpha_np)) ] ) y_pred_up = np.column_stack( [ - pred + higher_quantiles[k] + y_pred[:, np.newaxis] + higher_quantiles[k] for k in range(len(alpha_np)) ] ) + else: + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) - if self.method == "minmax": - lower_bounds = np.min(y_pred_multi, axis=1, keepdims=True) - upper_bounds = np.max(y_pred_multi, axis=1, keepdims=True) - y_pred_low = np.column_stack( - [ - lower_bounds + lower_quantiles[k] - for k in range(len(alpha_np)) - ] + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size thanks + # to the shape of k_. + + y_pred_multi = self.aggregate_with_mask( + y_pred_multi, self.k_ ) - y_pred_up = np.column_stack( - [ - upper_bounds + higher_quantiles[k] - for k in range(len(alpha_np)) - ] + + if self.method == "plus": + pred = aggregate_all(self.agg_function, y_pred_multi) + y_pred_low = np.column_stack( + [ + pred + lower_quantiles[k] + for k in range(len(alpha_np)) + ] + ) + y_pred_up = np.column_stack( + [ + pred + higher_quantiles[k] + for k in range(len(alpha_np)) + ] + ) + + if self.method == "minmax": + lower_bounds = np.min( + y_pred_multi, axis=1, keepdims=True + ) + upper_bounds = np.max( + y_pred_multi, axis=1, keepdims=True + ) + y_pred_low = np.column_stack( + [ + lower_bounds + lower_quantiles[k] + for k in range(len(alpha_np)) + ] + ) + y_pred_up = np.column_stack( + [ + upper_bounds + higher_quantiles[k] + for k in range(len(alpha_np)) + ] + ) + else: + # This version of predict is the implementation of the paper + # [2]. Its PIs are wider. It does not coorespond to [6]. It is + # a try. It is a bit slower because the betas + # (width optimization parameters of the PIs) are optimized for + # every points. + y_pred_low = np.empty((len(y_pred), len(alpha)), dtype=float) + y_pred_up = np.empty((len(y_pred), len(alpha)), dtype=float) + + for ind_alpha, _alpha in enumerate(alpha_np): + betas = np.linspace( + _alpha / (n + 1), _alpha, num=n + 1, endpoint=False + ) + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size + # thanks to the shape of k_. + + y_pred_multi = self.aggregate_with_mask( + y_pred_multi, self.k_ ) - if ensemble: - y_pred = aggregate_all(self.agg_function, y_pred_multi) + if self.method == "plus": + lower_bounds = y_pred_multi + self.conformity_scores_ + upper_bounds = y_pred_multi + self.conformity_scores_ + + if self.method == "minmax": + lower_bounds = np.min( + y_pred_multi, axis=1, keepdims=True + ) + upper_bounds = np.max( + y_pred_multi, axis=1, keepdims=True + ) + lower_bounds = lower_bounds + self.conformity_scores_ + upper_bounds = upper_bounds + self.conformity_scores_ + + one_alpha_beta = masked_quantile( + ma.masked_invalid(upper_bounds), + 1 - _alpha + betas, + axis=1, + method="higher", + ) # type: ignore + + beta = masked_quantile( + ma.masked_invalid(lower_bounds), + betas, + axis=1, + method="lower", + ) # type: ignore + + betas_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] + + lower_quantiles = np.empty((len(betas_0),)) + upper_quantiles = np.empty((len(betas_0),)) + + for ind, beta_0 in enumerate(betas_0): + lower_quantiles[ind] = masked_quantile( + ma.masked_invalid(lower_bounds[ind, :]), + beta_0, + axis=0, + method="lower", + ) # type: ignore + + upper_quantiles[ind] = masked_quantile( + ma.masked_invalid(upper_bounds[ind, :]), + 1 - _alpha + beta_0, + axis=0, + method="higher", + ) # type: ignore + y_pred_low[:, ind_alpha] = lower_quantiles + y_pred_up[:, ind_alpha] = upper_quantiles + + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) diff --git a/mapie/utils.py b/mapie/utils.py index 44fdc7e62..25ef72e3a 100644 --- a/mapie/utils.py +++ b/mapie/utils.py @@ -423,3 +423,106 @@ def check_input_is_image(X: ArrayLike) -> None: "When X is an image, the number of dimensions" "must be equal to 3 or 4." ) + + +def masked_quantile( + a: NDArray, + q: Union[float, NDArray], + axis: Optional[int] = None, + method: str = "linear", +) -> NDArray: + """ + Compute quantile for masked arrays + + Parameters + ---------- + a: NDArray + Array of data. + + q: Union[float, NDArray] + Quantiles. + + axis:int + ``axis`` is ``0`` or ``1`` + + method: str + "linear", "higher" or "lower" + """ + return_float = False + flatten = False + if isinstance(q, float): + flatten = True + + if (len(a.shape)) == 1 or (len(a) == 1): + return_float = True + + q = cast(NDArray, check_alpha(q)) + if (len(a.shape) == 1) or (a.shape[1] == 1): + if axis == 1: + raise ValueError( + "axis 1 is out of bounds for array of dimension 1" + ) + a = a.reshape(-1, 1) + flatten = True + + if axis is None: + a = a.flatten() + axis = 0 + + if axis == 0: + if hasattr(a, "mask"): + a_np = np.where(a.mask, np.inf, a.data) + masked_sum = np.sum(a.mask, axis=0).astype(int) + else: + a_np = a.copy() + masked_sum = np.zeros((a.shape[1],)) + a_np = np.sort(a_np, axis=0) + grid = np.indices(a_np.shape)[0] + a_tile = np.tile(a_np, (len(q), 1, 1)) + grid_tile = np.tile(grid, (len(q), 1, 1)) + + nb_valid_values = (len(a_np) - masked_sum).astype(int) + q_out = np.outer(nb_valid_values - 1, q) + q_out_inf = np.floor(q_out).astype(int) + q_out_sup = np.ceil(q_out) + q_out_sup = np.minimum( + q_out_sup, np.outer(nb_valid_values - 1, np.ones(len(q))) + ).astype(int) + + inf_bool = np.equal( + grid_tile, + np.tile(q_out_inf.T, (1, 1, a_np.shape[0])) + .reshape(len(q), *a_np.shape) + .astype(int), + ) + sup_bool = np.equal( + grid_tile, + np.tile(q_out_sup.T, (1, 1, a_np.shape[0])).reshape( + (len(q), *a_np.shape) + ), + ) + + lower_values = a_tile[inf_bool].reshape(len(q), -1) + upper_values = a_tile[sup_bool].reshape(len(q), -1) + + if method == "lower": + values = lower_values + elif method == "higher": + values = upper_values + elif method == "linear": + + interpolated_values = lower_values + (q_out - q_out_inf).T * ( + upper_values - lower_values + ) + values = interpolated_values + if flatten: + if len(q) == 1: + values = values.flatten() + + if return_float: + values = values[0] + return values + elif axis == 1: + return masked_quantile(a.T, q, axis=0, method=method) + else: + raise ValueError("axis should be None, 0 or 1") From 98faf3d28d7ea07d209f7b2042025d8456f9998e Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Tue, 5 Apr 2022 13:27:24 +0200 Subject: [PATCH 14/32] PR EnbPI completed --- examples/regression/plot_timeseries_enbpi.py | 19 ++--- .../regression/plot_timeseries_enbpi_train.py | 6 +- mapie/regression.py | 1 - mapie/tests/test_time_series_regression.py | 81 ++++++++++++++++--- mapie/tests/test_utils.py | 24 +++++- mapie/time_series_regression.py | 61 ++++++++------ mapie/utils.py | 30 ++++--- 7 files changed, 156 insertions(+), 66 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 5dbb47f42..96a725b1d 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -18,10 +18,11 @@ in which the training set is prior to the validation set. The best model is then feeded into :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the -associated prediction intervals. We compare two approaches: one with no -`partial_fit` call and one with `partial_fit` every step. +associated prediction intervals. We compare four approaches: with or without +``partial_fit`` called at every step, and following [6] or a approach inspired +from [2]. It appears that the approach inspired from [2] and ``partial_fit`` +offer higher coverage, but with higher width of PIs and are much slower. """ -import copy import warnings import numpy as np @@ -98,11 +99,11 @@ for step in range(gap_pfit, len(X_test), gap_pfit): mapie_pfit_JAB_F.partial_fit( - X_test.iloc[(step - gap_pfit) : step, :], - y_test.iloc[(step - gap_pfit) : step], + X_test.iloc[(step - gap_pfit):step, :], + y_test.iloc[(step - gap_pfit):step], ) y_pred_gap_step, y_pis_gap_step = mapie_pfit_JAB_F.predict( - X_test.iloc[step : (step + gap_pfit), :], alpha=alpha, ensemble=True + X_test.iloc[step:(step + gap_pfit), :], alpha=alpha, ensemble=True ) y_pred_pfit_JAB_F = np.concatenate( (y_pred_pfit_JAB_F, y_pred_gap_step), axis=0 @@ -136,11 +137,11 @@ ) for step in range(gap_pfit, len(X_test), gap_pfit): mapie_pfit_JAB_T.partial_fit( - X_test.iloc[(step - gap_pfit) : step, :], - y_test.iloc[(step - gap_pfit) : step], + X_test.iloc[(step - gap_pfit):step, :], + y_test.iloc[(step - gap_pfit):step], ) y_pred_gap_step, y_pis_gap_step = mapie_pfit_JAB_T.predict( - X_test.iloc[step : (step + gap_pfit), :], + X_test.iloc[step:(step + gap_pfit), :], alpha=alpha, JAB_Like=True, ensemble=True, diff --git a/examples/regression/plot_timeseries_enbpi_train.py b/examples/regression/plot_timeseries_enbpi_train.py index ec71f4472..e9ee8d450 100644 --- a/examples/regression/plot_timeseries_enbpi_train.py +++ b/examples/regression/plot_timeseries_enbpi_train.py @@ -29,10 +29,6 @@ from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit - -from mapie.metrics import regression_coverage_score -from mapie.time_series_regression import MapieTimeSeriesRegressor - warnings.simplefilter("ignore") # Load input data and feature engineering @@ -80,4 +76,4 @@ n_jobs=-1, ) cv_obj.fit(X_train, y_train) -print(cv_obj.best_estimator_) \ No newline at end of file +print(cv_obj.best_estimator_) diff --git a/mapie/regression.py b/mapie/regression.py index 611225886..1bceac984 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -1,5 +1,4 @@ from __future__ import annotations -from configparser import Interpolation from typing import Iterable, List, Optional, Tuple, Union, cast diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 80dcad92a..bf1f8df37 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -69,6 +69,25 @@ random_state=1, ), ), + "jackknife_plus_ab_JAB": Params( + method="plus", + agg_function="mean", + cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), + ), + "jackknife_minmax_ab_JAB": Params( + method="minmax", + agg_function="mean", + cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), + ), + "jackknife_plus_median_ab_JAB": Params( + method="plus", + agg_function="median", + cv=BlockBootstrap( + n_resamplings=30, + n_blocks=5, + random_state=1, + ), + ), } WIDTHS = { @@ -84,6 +103,9 @@ "jackknife_plus_ab": 3.76, "jackknife_minmax_ab": 3.96, "jackknife_plus_median_ab": 3.76, + "jackknife_plus_ab_JAB": 3.76, + "jackknife_minmax_ab_JAB": 3.96, + "jackknife_plus_median_ab_JAB": 3.76, } COVERAGES = { @@ -99,6 +121,9 @@ "jackknife_plus_ab": 0.952, "jackknife_minmax_ab": 0.960, "jackknife_plus_median_ab": 0.946, + "jackknife_plus_ab_JAB": 0.952, + "jackknife_minmax_ab_JAB": 0.960, + "jackknife_plus_median_ab_JAB": 0.965, } @@ -189,6 +214,15 @@ def test_results_single_and_multi_jobs(strategy: str) -> None: np.testing.assert_allclose(y_pred_single, y_pred_multi) np.testing.assert_allclose(y_pis_single, y_pis_multi) + y_pred_single_JAB, y_pis_single_JAB = mapie_single.predict( + X_toy, alpha=0.2, JAB_Like=True + ) + y_pred_multi_JAB, y_pis_multi_JAB = mapie_multi.predict( + X_toy, alpha=0.2, JAB_Like=True + ) + np.testing.assert_allclose(y_pred_single_JAB, y_pred_multi_JAB) + np.testing.assert_allclose(y_pis_single_JAB, y_pis_multi_JAB) + @pytest.mark.parametrize("strategy", [*STRATEGIES]) def test_results_with_constant_sample_weights(strategy: str) -> None: @@ -211,15 +245,19 @@ def test_results_with_constant_sample_weights(strategy: str) -> None: np.testing.assert_allclose(y_pis0, y_pis1) np.testing.assert_allclose(y_pis1, y_pis2) - -@pytest.mark.parametrize("strategy", [*STRATEGIES]) -def test_prediction_between_low_up(strategy: str) -> None: - """Test that prediction lies between low and up prediction intervals.""" - mapie = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) - mapie.fit(X, y) - y_pred, y_pis = mapie.predict(X, alpha=0.1) - assert (y_pred >= y_pis[:, 0, 0]).all() - assert (y_pred <= y_pis[:, 1, 0]).all() + y_pred0_JAB, y_pis0_JAB = mapie0.predict( + X[:200, :], alpha=0.05, JAB_Like=True + ) + y_pred1_JAB, y_pis1_JAB = mapie1.predict( + X[:200, :], alpha=0.05, JAB_Like=True + ) + y_pred2_JAB, y_pis2_JAB = mapie2.predict( + X[:200, :], alpha=0.05, JAB_Like=True + ) + np.testing.assert_allclose(y_pred0_JAB, y_pred1_JAB) + np.testing.assert_allclose(y_pred1_JAB, y_pred2_JAB) + np.testing.assert_allclose(y_pis0_JAB, y_pis1_JAB) + np.testing.assert_allclose(y_pis1_JAB, y_pis2_JAB) @pytest.mark.parametrize("method", ["plus", "minmax"]) @@ -237,13 +275,24 @@ def test_prediction_agg_function( method=method, cv=cv, agg_function=agg_function ) mapie.fit(X, y) - y_pred_1, y_pis_1 = mapie.predict(X, ensemble=True, alpha=alpha) - y_pred_2, y_pis_2 = mapie.predict(X, ensemble=False, alpha=alpha) + y_pred_1, y_pis_1 = mapie.predict(X[:200, :], ensemble=True, alpha=alpha) + y_pred_2, y_pis_2 = mapie.predict(X[:200, :], ensemble=False, alpha=alpha) np.testing.assert_allclose(y_pis_1[:, 0, 0], y_pis_2[:, 0, 0]) np.testing.assert_allclose(y_pis_1[:, 1, 0], y_pis_2[:, 1, 0]) with pytest.raises(AssertionError): np.testing.assert_allclose(y_pred_1, y_pred_2) + y_pred_1_JAB, y_pis_1_JAB = mapie.predict( + X[:200, :], ensemble=True, alpha=alpha, JAB_Like=True + ) + y_pred_2_JAB, y_pis_2_JAB = mapie.predict( + X[:200, :], ensemble=False, alpha=alpha, JAB_Like=True + ) + np.testing.assert_allclose(y_pis_1_JAB[:, 0, 0], y_pis_2_JAB[:, 0, 0]) + np.testing.assert_allclose(y_pis_1_JAB[:, 1, 0], y_pis_2_JAB[:, 1, 0]) + with pytest.raises(AssertionError): + np.testing.assert_allclose(y_pred_1_JAB, y_pred_2_JAB) + @pytest.mark.parametrize("strategy", [*STRATEGIES]) def test_linear_regression_results(strategy: str) -> None: @@ -254,10 +303,16 @@ def test_linear_regression_results(strategy: str) -> None: """ mapie_ts = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) mapie_ts.fit(X, y) - _, y_pis = mapie_ts.predict(X, alpha=0.05) + if "JAB" in strategy: + _, y_pis = mapie_ts.predict(X[:200, :], alpha=0.05, JAB_Like=True) + else: + _, y_pis = mapie_ts.predict(X, alpha=0.05, JAB_Like=False) y_pred_low, y_pred_up = y_pis[:, 0, 0], y_pis[:, 1, 0] width_mean = (y_pred_up - y_pred_low).mean() - coverage = regression_coverage_score(y, y_pred_low, y_pred_up) + if "JAB" in strategy: + coverage = regression_coverage_score(y[:200], y_pred_low, y_pred_up) + else: + coverage = regression_coverage_score(y, y_pred_low, y_pred_up) np.testing.assert_allclose(width_mean, WIDTHS[strategy], rtol=1e-2) np.testing.assert_allclose(coverage, COVERAGES[strategy], rtol=1e-2) diff --git a/mapie/tests/test_utils.py b/mapie/tests/test_utils.py index 2d1539e2a..ca31261ab 100644 --- a/mapie/tests/test_utils.py +++ b/mapie/tests/test_utils.py @@ -16,6 +16,7 @@ check_null_weight, check_verbose, fit_estimator, + masked_quantile, ) from mapie._typing import ArrayLike @@ -63,12 +64,10 @@ def test_check_null_weight_with_zeros() -> None: sw_out, X_out, y_out = check_null_weight(sample_weight, X_toy, y_toy) np.testing.assert_almost_equal(np.array(sw_out), np.array([1, 1, 1, 1, 1])) np.testing.assert_almost_equal( - np.array(X_out), - np.array([[1], [2], [3], [4], [5]]) + np.array(X_out), np.array([[1], [2], [3], [4], [5]]) ) np.testing.assert_almost_equal( - np.array(y_out), - np.array([7, 9, 11, 13, 15]) + np.array(y_out), np.array([7, 9, 11, 13, 15]) ) @@ -194,3 +193,20 @@ def test_invalid_verbose(verbose: Any) -> None: def test_valid_verbose(verbose: Any) -> None: """Test that valid verboses raise no errors.""" check_verbose(verbose) + + +def test_masked_quantile_invalid_minus_one(): + with pytest.raises(ValueError, match=r".*axis should be None, 0 or 1.*"): + masked_quantile(a=X_toy, q=0.1, axis=-1) + + +def test_masked_quantile_invalid_one(): + with pytest.raises(ValueError, match=r".*axis 1 is out of bounds.*"): + masked_quantile(a=X_toy.flatten(), q=0.1, axis=1) + + +def test_masked_quantile_linear_interpolation(): + quantiles = masked_quantile( + a=X_toy, q=[0.1, 0.2, 0.5], axis=0, method="linear" + ) + np.testing.assert_allclose(quantiles, np.array([[0.5, 1.0, 2.5]])) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index ab2d5159a..cee380170 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -112,6 +112,19 @@ def predict( alpha: Optional[Union[float, Iterable[float]]] = None, JAB_Like=False, ) -> Union[NDArray, Tuple[NDArray, NDArray]]: + """ + ``predict`` method correspond to the ``MapieRegressor``'s one with the + argument ``JAB_Like`` in more. In case ``JAB_Like`` is ``False``, + predictions correspond to [6]. In case it is True, is is another + implementation inspired of [2] with wider PIs. The second method is + much slower because of PIwise optimization. + + Parameters + ---------- + JAB_Like : boolean, default False + Whether to use the implementation of [6] or an implementation + closer to [2]. + """ # Checks check_is_fitted(self, self.fit_attributes) @@ -132,8 +145,8 @@ def predict( or (self.method in ["naive", "base"]) or (self.cv == "prefit") ): - # This version of predict is the implementation of the paper - # [6]. Its PIs are closed to the oracle's ones. + # This version of predict corresponds to [6]. + # Its PIs are closed to the oracle's ones. betas_0 = np.full_like(alpha_np, np.nan, dtype=float) for ind, _alpha in enumerate(alpha_np): betas = np.linspace( @@ -153,8 +166,9 @@ def predict( axis=0, method="lower", ) # type: ignore + betas_0[ind] = betas[ - np.argmin(one_alpha_beta - beta, axis=0) + np.argmin(one_alpha_beta - beta, axis=1) ] lower_quantiles = masked_quantile( @@ -162,13 +176,13 @@ def predict( betas_0, axis=0, method="lower", - ) # type: ignore + ).T # type: ignore higher_quantiles = masked_quantile( ma.masked_invalid(self.conformity_scores_), 1 - alpha_np + betas_0, axis=0, method="higher", - ) # type: ignore + ).T # type: ignore if (self.method in ["naive", "base"]) or (self.cv == "prefit"): y_pred_low = np.column_stack( @@ -232,11 +246,22 @@ def predict( ] ) else: - # This version of predict is the implementation of the paper - # [2]. Its PIs are wider. It does not coorespond to [6]. It is - # a try. It is a bit slower because the betas - # (width optimization parameters of the PIs) are optimized for + # This version of predict corresponds to [2]. + # Its PIs are wider. It does not coorespond to [6]. It is + # a try. It is slower because the betas + # (width optimization parameters of the PIs) are optimized at # every points. + + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size + # thanks to the shape of k_. + + y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + y_pred_low = np.empty((len(y_pred), len(alpha)), dtype=float) y_pred_up = np.empty((len(y_pred), len(alpha)), dtype=float) @@ -244,17 +269,7 @@ def predict( betas = np.linspace( _alpha / (n + 1), _alpha, num=n + 1, endpoint=False ) - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size - # thanks to the shape of k_. - y_pred_multi = self.aggregate_with_mask( - y_pred_multi, self.k_ - ) if self.method == "plus": lower_bounds = y_pred_multi + self.conformity_scores_ upper_bounds = y_pred_multi + self.conformity_scores_ @@ -274,14 +289,14 @@ def predict( 1 - _alpha + betas, axis=1, method="higher", - ) # type: ignore + ).T # type: ignore beta = masked_quantile( ma.masked_invalid(lower_bounds), betas, axis=1, method="lower", - ) # type: ignore + ).T # type: ignore betas_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] @@ -294,14 +309,14 @@ def predict( beta_0, axis=0, method="lower", - ) # type: ignore + ).T # type: ignore upper_quantiles[ind] = masked_quantile( ma.masked_invalid(upper_bounds[ind, :]), 1 - _alpha + beta_0, axis=0, method="higher", - ) # type: ignore + ).T # type: ignore y_pred_low[:, ind_alpha] = lower_quantiles y_pred_up[:, ind_alpha] = upper_quantiles diff --git a/mapie/utils.py b/mapie/utils.py index 25ef72e3a..3d2cfdce6 100644 --- a/mapie/utils.py +++ b/mapie/utils.py @@ -3,6 +3,7 @@ from typing import Any, Iterable, Optional, Tuple, Union, cast import numpy as np +import numpy.ma as ma from sklearn.base import ClassifierMixin, RegressorMixin from sklearn.model_selection import BaseCrossValidator, KFold, LeaveOneOut from sklearn.utils.validation import _check_sample_weight, _num_features @@ -432,7 +433,8 @@ def masked_quantile( method: str = "linear", ) -> NDArray: """ - Compute quantile for masked arrays + Compute quantile for masked arrays. It avoids using ``np.nanquantile`` that + is quite slow because of an ``apply_along_axis`` loop. Parameters ---------- @@ -442,17 +444,18 @@ def masked_quantile( q: Union[float, NDArray] Quantiles. - axis:int - ``axis`` is ``0`` or ``1`` + axis:Optional[int] + ``axis`` is ``None``, ``0`` or ``1``, default ``None``. + If ``axis`` is ``None``, compute the quantiles of the flatten array. method: str "linear", "higher" or "lower" """ return_float = False flatten = False + if isinstance(q, float): flatten = True - if (len(a.shape)) == 1 or (len(a) == 1): return_float = True @@ -462,19 +465,22 @@ def masked_quantile( raise ValueError( "axis 1 is out of bounds for array of dimension 1" ) - a = a.reshape(-1, 1) - flatten = True + if len(q) == 1: + flatten = True if axis is None: a = a.flatten() axis = 0 if axis == 0: + if (len(a.shape) == 1) or (a.shape[1] == 1): + a = a.reshape(-1, 1) if hasattr(a, "mask"): - a_np = np.where(a.mask, np.inf, a.data) - masked_sum = np.sum(a.mask, axis=0).astype(int) + a_m = cast(ma.MaskedArray, a) + a_np = np.where(a_m.mask, np.inf, a_m.data) + masked_sum = np.sum(a_m.mask, axis=0).astype(int) else: - a_np = a.copy() + a_np = a masked_sum = np.zeros((a.shape[1],)) a_np = np.sort(a_np, axis=0) grid = np.indices(a_np.shape)[0] @@ -515,9 +521,11 @@ def masked_quantile( upper_values - lower_values ) values = interpolated_values + if flatten: - if len(q) == 1: - values = values.flatten() + values = values.flatten() + else: + values = values.T if return_float: values = values[0] From ed39ccaf3cd314165a011d1a16c4b49ec85719d1 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 6 Apr 2022 11:44:31 +0200 Subject: [PATCH 15/32] EnbPI ready!! --- mapie/tests/test_time_series_regression.py | 26 ++++++++++++---------- mapie/tests/test_utils.py | 12 ++++++++++ mapie/utils.py | 7 +++--- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index bf1f8df37..e66fe174b 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -19,6 +19,7 @@ X_toy = np.array(range(5)).reshape(-1, 1) y_toy = (5.0 + 2.0 * X_toy ** 1.1).flatten() X, y = make_regression(n_samples=500, n_features=10, noise=1.0, random_state=1) +X_short, y_short = X[:50, :], y[:50] k = np.ones(shape=(5, X.shape[1])) METHODS = ["naive", "base", "plus", "minmax"] @@ -121,9 +122,9 @@ "jackknife_plus_ab": 0.952, "jackknife_minmax_ab": 0.960, "jackknife_plus_median_ab": 0.946, - "jackknife_plus_ab_JAB": 0.952, - "jackknife_minmax_ab_JAB": 0.960, - "jackknife_plus_median_ab_JAB": 0.965, + "jackknife_plus_ab_JAB": 0.92, + "jackknife_minmax_ab_JAB": 0.940, + "jackknife_plus_median_ab_JAB": 0.94, } @@ -246,14 +247,15 @@ def test_results_with_constant_sample_weights(strategy: str) -> None: np.testing.assert_allclose(y_pis1, y_pis2) y_pred0_JAB, y_pis0_JAB = mapie0.predict( - X[:200, :], alpha=0.05, JAB_Like=True + X_short, alpha=0.05, JAB_Like=True ) y_pred1_JAB, y_pis1_JAB = mapie1.predict( - X[:200, :], alpha=0.05, JAB_Like=True + X_short, alpha=0.05, JAB_Like=True ) y_pred2_JAB, y_pis2_JAB = mapie2.predict( - X[:200, :], alpha=0.05, JAB_Like=True + X_short, alpha=0.05, JAB_Like=True ) + np.testing.assert_allclose(y_pred0_JAB, y_pred1_JAB) np.testing.assert_allclose(y_pred1_JAB, y_pred2_JAB) np.testing.assert_allclose(y_pis0_JAB, y_pis1_JAB) @@ -275,18 +277,18 @@ def test_prediction_agg_function( method=method, cv=cv, agg_function=agg_function ) mapie.fit(X, y) - y_pred_1, y_pis_1 = mapie.predict(X[:200, :], ensemble=True, alpha=alpha) - y_pred_2, y_pis_2 = mapie.predict(X[:200, :], ensemble=False, alpha=alpha) + y_pred_1, y_pis_1 = mapie.predict(X_short, ensemble=True, alpha=alpha) + y_pred_2, y_pis_2 = mapie.predict(X_short, ensemble=False, alpha=alpha) np.testing.assert_allclose(y_pis_1[:, 0, 0], y_pis_2[:, 0, 0]) np.testing.assert_allclose(y_pis_1[:, 1, 0], y_pis_2[:, 1, 0]) with pytest.raises(AssertionError): np.testing.assert_allclose(y_pred_1, y_pred_2) y_pred_1_JAB, y_pis_1_JAB = mapie.predict( - X[:200, :], ensemble=True, alpha=alpha, JAB_Like=True + X_short, ensemble=True, alpha=alpha, JAB_Like=True ) y_pred_2_JAB, y_pis_2_JAB = mapie.predict( - X[:200, :], ensemble=False, alpha=alpha, JAB_Like=True + X_short, ensemble=False, alpha=alpha, JAB_Like=True ) np.testing.assert_allclose(y_pis_1_JAB[:, 0, 0], y_pis_2_JAB[:, 0, 0]) np.testing.assert_allclose(y_pis_1_JAB[:, 1, 0], y_pis_2_JAB[:, 1, 0]) @@ -304,13 +306,13 @@ def test_linear_regression_results(strategy: str) -> None: mapie_ts = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) mapie_ts.fit(X, y) if "JAB" in strategy: - _, y_pis = mapie_ts.predict(X[:200, :], alpha=0.05, JAB_Like=True) + _, y_pis = mapie_ts.predict(X_short, alpha=0.05, JAB_Like=True) else: _, y_pis = mapie_ts.predict(X, alpha=0.05, JAB_Like=False) y_pred_low, y_pred_up = y_pis[:, 0, 0], y_pis[:, 1, 0] width_mean = (y_pred_up - y_pred_low).mean() if "JAB" in strategy: - coverage = regression_coverage_score(y[:200], y_pred_low, y_pred_up) + coverage = regression_coverage_score(y_short, y_pred_low, y_pred_up) else: coverage = regression_coverage_score(y, y_pred_low, y_pred_up) np.testing.assert_allclose(width_mean, WIDTHS[strategy], rtol=1e-2) diff --git a/mapie/tests/test_utils.py b/mapie/tests/test_utils.py index ca31261ab..6be7abbed 100644 --- a/mapie/tests/test_utils.py +++ b/mapie/tests/test_utils.py @@ -210,3 +210,15 @@ def test_masked_quantile_linear_interpolation(): a=X_toy, q=[0.1, 0.2, 0.5], axis=0, method="linear" ) np.testing.assert_allclose(quantiles, np.array([[0.5, 1.0, 2.5]])) + + +def test_masked_quantile_linear_interpolation_scalar(): + quantile = masked_quantile( + a=X_toy.flatten(), q=0.1, axis=0, method="linear" + ) + assert quantile == 0.5 + + +def test_masked_quantile_invalid_method(): + with pytest.raises(ValueError, match=r".*'method' has to be 'higher'.*"): + masked_quantile(a=X_toy.flatten(), q=0.1, axis=0, method="lineaar") diff --git a/mapie/utils.py b/mapie/utils.py index 3d2cfdce6..23158a9ac 100644 --- a/mapie/utils.py +++ b/mapie/utils.py @@ -516,12 +516,11 @@ def masked_quantile( elif method == "higher": values = upper_values elif method == "linear": - - interpolated_values = lower_values + (q_out - q_out_inf).T * ( + values = lower_values + (q_out - q_out_inf).T * ( upper_values - lower_values ) - values = interpolated_values - + else: + raise ValueError("'method' has to be 'higher','lower', or'linear'") if flatten: values = values.flatten() else: From 59b75053f9ebf0c39db5d9bebbd3cedc2fddd07a Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 13 Apr 2022 16:01:21 +0200 Subject: [PATCH 16/32] 3 methods for predict --- .../plot_MapieRegressor_benchmark.py | 164 +++++++ examples/regression/plot_timeseries_enbpi.py | 346 +++++++++----- .../regression/plot_timeseries_enbpi_train.py | 6 +- mapie/quantile_timeit.ipynb | 230 ++++++++++ mapie/regression.py | 15 +- mapie/tests/test_time_series_regression.py | 67 ++- mapie/tests/test_utils.py | 30 -- mapie/time_series_regression.py | 429 ++++++++++-------- mapie/utils.py | 109 ----- 9 files changed, 940 insertions(+), 456 deletions(-) create mode 100644 examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py create mode 100644 mapie/quantile_timeit.ipynb diff --git a/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py b/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py new file mode 100644 index 000000000..f899a9ea7 --- /dev/null +++ b/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py @@ -0,0 +1,164 @@ +""" +================================================ +Estimating aleatoric and epistemic uncertainties +================================================ +This example uses :class:`mapie.regression.MapieRegressor` to estimate +prediction intervals capturing both aleatoric and epistemic uncertainties +on a one-dimensional dataset with homoscedastic noise and normal sampling. +""" +from ast import Sub +from typing import Any, Callable, Tuple, TypeVar, Union + +from typing_extensions import TypedDict +import numpy as np +from sklearn.linear_model import LinearRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import PolynomialFeatures +import matplotlib.pyplot as plt + +from mapie.regression import MapieRegressor +from mapie.subsample import Subsample +from mapie._typing import NDArray + +F = TypeVar("F", bound=Callable[..., Any]) + + +# Functions for generating our dataset +def x_sinx(x: NDArray) -> NDArray: + """One-dimensional x*sin(x) function.""" + return x * np.sin(x) + + +def get_1d_data_with_normal_distrib( + funct: F, mu: float, sigma: float, n_samples: int, noise: float +) -> Tuple[NDArray, NDArray, NDArray, NDArray, NDArray]: + """ + Generate noisy 1D data with normal distribution from given function + and noise standard deviation. + + Parameters + ---------- + funct : F + Base function used to generate the dataset. + mu : float + Mean of normal training distribution. + sigma : float + Standard deviation of normal training distribution. + n_samples : int + Number of training samples. + noise : float + Standard deviation of noise. + + Returns + ------- + Tuple[NDArray, AnNDArrayy, NDArray, NDArray, NDArray] + Generated training and test data. + [0]: X_train + [1]: y_train + [2]: X_test + [3]: y_test + [4]: y_mesh + """ + np.random.seed(42) + X_train = np.random.normal(mu, sigma, n_samples) + X_test = np.arange(mu - 4 * sigma, mu + 4 * sigma, sigma / 20.0) + y_train, y_mesh, y_test = funct(X_train), funct(X_test), funct(X_test) + y_train += np.random.normal(0, noise, y_train.shape[0]) + y_test += np.random.normal(0, noise, y_test.shape[0]) + return ( + X_train.reshape(-1, 1), + y_train, + X_test.reshape(-1, 1), + y_test, + y_mesh, + ) + + +# Data generation +mu, sigma, n_samples, noise = 0, 2.5, 300, 0.5 +X_train, y_train, X_test, y_test, y_mesh = get_1d_data_with_normal_distrib( + x_sinx, mu, sigma, n_samples, noise +) + +# Definition of our base model +degree_polyn = 10 +polyn_model = Pipeline( + [ + ("poly", PolynomialFeatures(degree=degree_polyn)), + ("linear", LinearRegression()), + ] +) + +# Estimating prediction intervals +Params = TypedDict("Params", {"method": str, "cv": Union[int, Subsample]}) +STRATEGIES = { + "jackknife_plus": Params(method="plus", cv=-1), + "jackknife_minmax": Params(method="minmax", cv=-1), + "cv_plus": Params(method="plus", cv=10), + "cv_minmax": Params(method="minmax", cv=10), + "jackknide-plus-after-bootstrap": Params(method="plus", cv=10), + "jackknide-minmax-after-bootstrap": Params(method="minmax", cv=10), +} +y_pred, y_pis = {}, {} +for strategy, params in STRATEGIES.items(): + mapie = MapieRegressor(polyn_model, **params) + mapie.fit(X_train, y_train) + y_pred[strategy], y_pis[strategy] = mapie.predict(X_test, alpha=0.05) + + +# Visualization +def plot_1d_data( + X_train: NDArray, + y_train: NDArray, + X_test: NDArray, + y_test: NDArray, + y_sigma: float, + y_pred: NDArray, + y_pred_low: NDArray, + y_pred_up: NDArray, + ax: plt.Axes, + title: str, +) -> None: + ax.set_xlabel("x") + ax.set_ylabel("y") + ax.set_xlim([-10, 10]) + ax.set_ylim([np.min(y_test) * 1.3, np.max(y_test) * 1.3]) + ax.fill_between(X_test, y_pred_low, y_pred_up, alpha=0.3) + ax.scatter(X_train, y_train, color="red", alpha=0.3, label="Training data") + ax.plot(X_test, y_test, color="gray", label="True confidence intervals") + ax.plot(X_test, y_test - y_sigma, color="gray", ls="--") + ax.plot(X_test, y_test + y_sigma, color="gray", ls="--") + ax.plot(X_test, y_pred, color="b", alpha=0.5, label="Prediction intervals") + if title is not None: + ax.set_title(title) + ax.legend() + + +n_figs = len(STRATEGIES) +fig, axs = plt.subplots(2, 2, figsize=(13, 12)) +coords = [axs[0, 0], axs[0, 1], axs[1, 0], axs[1, 1]] +for strategy, coord in zip(STRATEGIES, coords): + plot_1d_data( + X_train.ravel(), + y_train.ravel(), + X_test.ravel(), + y_mesh.ravel(), + 1.96 * noise, + y_pred[strategy].ravel(), + y_pis[strategy][:, 0, 0].ravel(), + y_pis[strategy][:, 1, 0].ravel(), + ax=coord, + title=strategy, + ) + + +fig, ax = plt.subplots(1, 1, figsize=(7, 5)) +ax.set_xlim([-8, 8]) +ax.set_ylim([0, 4]) +for strategy in STRATEGIES: + ax.plot(X_test, y_pis[strategy][:, 1, 0] - y_pis[strategy][:, 0, 0]) +ax.axhline(1.96 * 2 * noise, ls="--", color="k") +ax.set_xlabel("x") +ax.set_ylabel("Prediction Interval Width") +ax.legend(list(STRATEGIES.keys()) + ["True width"], fontsize=8) +plt.show() diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 96a725b1d..fa167cad9 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -45,7 +45,8 @@ demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") demand_df["Hour"] = demand_df.index.hour -for hour in range(1, 3): +n_lags = 5 +for hour in range(1, n_lags): demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) # Train/validation/test split @@ -53,7 +54,7 @@ demand_train = demand_df.iloc[:-num_test_steps, :].copy() demand_test = demand_df.iloc[-num_test_steps:, :].copy() features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ - f"Lag_{hour}" for hour in range(1, 2) + f"Lag_{hour}" for hour in range(1, n_lags) ] X_train = demand_train.loc[ @@ -64,202 +65,337 @@ y_test = demand_test["Demand"] # Model: Random Forest previously optimized with a cross-validation -model = RandomForestRegressor(max_depth=15, n_estimators=1, random_state=59) +model = RandomForestRegressor(max_depth=10, n_estimators=50, random_state=59) # Estimate prediction intervals on test set with best estimator -alpha = 0.1 -cv_MapieTimeSeries = BlockBootstrap(20, length=48, random_state=59) +alpha = 0.05 +cv_MapieTimeSeries = BlockBootstrap( + n_resamplings=100, length=48, overlapping=True, random_state=59 +) -mapie_model = MapieTimeSeriesRegressor( +mapie_plus = MapieTimeSeriesRegressor( + model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 +) +mapie_enpbi = MapieTimeSeriesRegressor( model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 ) -mapie_model = mapie_model.fit(X_train, y_train) -mapie_no_pfit = mapie_model.fit(X_train, y_train) -mapie_pfit_JAB_F = mapie_model.fit(X_train, y_train) -mapie_pfit_JAB_T = mapie_model.fit(X_train, y_train) -gap_pfit = 1 +gap = 1 -# With no partial_fit, JAB_like is False -y_pred_npfit_JAB_F, y_pis_npfit_JAB_F = mapie_no_pfit.predict( - X_test, alpha=alpha, ensemble=True +print("EnbPI, with no partial_fit, width optimization") +mapie_enpbi = mapie_enpbi.fit(X_train, y_train) +y_pred_npfit_enbpi, y_pis_npfit_enbpi = mapie_enpbi.predict( + X_test, alpha=alpha, ensemble=True, beta_optimize=True ) -coverage_npfit_JAB_F = regression_coverage_score( - y_test, y_pis_npfit_JAB_F[:, 0, 0], y_pis_npfit_JAB_F[:, 1, 0] +coverage_npfit_enbpi = regression_coverage_score( + y_test, y_pis_npfit_enbpi[:, 0, 0], y_pis_npfit_enbpi[:, 1, 0] ) -width_npfit_JAB_F = ( - y_pis_npfit_JAB_F[:, 1, 0] - y_pis_npfit_JAB_F[:, 0, 0] +width_npfit_enbpi = ( + y_pis_npfit_enbpi[:, 1, 0] - y_pis_npfit_enbpi[:, 0, 0] ).mean() -# With partial_fit every hour, JAB_like is False +print("EnbPI with partial_fit, width optimization") +mapie_enpbi = mapie_enpbi.fit(X_train, y_train) +y_pred_pfit_enbpi = np.zeros(y_pred_npfit_enbpi.shape) +y_pis_pfit_enbpi = np.zeros(y_pis_npfit_enbpi.shape) -y_pred_pfit_JAB_F, y_pis_pfit_JAB_F = mapie_pfit_JAB_F.predict( - X_test.iloc[:gap_pfit, :], alpha=alpha, ensemble=True +y_pred_pfit_enbpi[:gap], y_pis_pfit_enbpi[:gap, :, :] = mapie_enpbi.predict( + X_test.iloc[:gap, :], alpha=alpha, ensemble=True, beta_optimize=True ) -for step in range(gap_pfit, len(X_test), gap_pfit): - mapie_pfit_JAB_F.partial_fit( - X_test.iloc[(step - gap_pfit):step, :], - y_test.iloc[(step - gap_pfit):step], +for step in range(gap, len(X_test), gap): + mapie_enpbi.partial_fit( + X_test.iloc[(step - gap) : step, :], + y_test.iloc[(step - gap) : step], ) - y_pred_gap_step, y_pis_gap_step = mapie_pfit_JAB_F.predict( - X_test.iloc[step:(step + gap_pfit), :], alpha=alpha, ensemble=True + ( + y_pred_pfit_enbpi[step : step + gap], + y_pis_pfit_enbpi[step : step + gap, :, :], + ) = mapie_enpbi.predict( + X_test.iloc[step : (step + gap), :], + alpha=alpha, + ensemble=True, + beta_optimize=True, ) - y_pred_pfit_JAB_F = np.concatenate( - (y_pred_pfit_JAB_F, y_pred_gap_step), axis=0 +coverage_pfit_enbpi = regression_coverage_score( + y_test, y_pis_pfit_enbpi[:, 0, 0], y_pis_pfit_enbpi[:, 1, 0] +) +width_pfit_enbpi = ( + y_pis_pfit_enbpi[:, 1, 0] - y_pis_pfit_enbpi[:, 0, 0] +).mean() + +print("EnbPI with partial_fit, NO width optimization") +mapie_enpbi = mapie_enpbi.fit(X_train, y_train) +y_pred_pfit_enbpi_no_opt = np.zeros(y_pred_npfit_enbpi.shape) +y_pis_pfit_enbpi_no_opt = np.zeros(y_pis_npfit_enbpi.shape) +( + y_pred_pfit_enbpi_no_opt[:gap], + y_pis_pfit_enbpi_no_opt[:gap, :, :], +) = mapie_enpbi.predict( + X_test.iloc[:gap, :], alpha=alpha, ensemble=True, beta_optimize=False +) + +for step in range(gap, len(X_test), gap): + mapie_enpbi.partial_fit( + X_test.iloc[(step - gap) : step, :], + y_test.iloc[(step - gap) : step], ) - y_pis_pfit_JAB_F = np.concatenate( - (y_pis_pfit_JAB_F, y_pis_gap_step), axis=0 + ( + y_pred_pfit_enbpi_no_opt[step : step + gap], + y_pis_pfit_enbpi_no_opt[step : step + gap, :, :], + ) = mapie_enpbi.predict( + X_test.iloc[step : (step + gap), :], + alpha=alpha, + ensemble=True, + beta_optimize=False, ) - -coverage_pfit_JAB_F = regression_coverage_score( - y_test, y_pis_pfit_JAB_F[:, 0, 0], y_pis_pfit_JAB_F[:, 1, 0] +coverage_pfit_enbpi_no_opt = regression_coverage_score( + y_test, y_pis_pfit_enbpi_no_opt[:, 0, 0], y_pis_pfit_enbpi_no_opt[:, 1, 0] ) -width_pfit_JAB_F = ( - y_pis_pfit_JAB_F[:, 1, 0] - y_pis_pfit_JAB_F[:, 0, 0] +width_pfit_enbpi_no_opt = ( + y_pis_pfit_enbpi_no_opt[:, 1, 0] - y_pis_pfit_enbpi_no_opt[:, 0, 0] ).mean() -# With no partial_fit, JAB_like is True -y_pred_npfit_JAB_T, y_pis_npfit_JAB_T = mapie_no_pfit.predict( - X_test, alpha=alpha, JAB_Like=True +print("Plus, with partial_fit, width optimization") +mapie_plus = mapie_plus.fit(X_train, y_train) +y_pred_pfit_plus = np.zeros(y_pred_npfit_enbpi.shape) +y_pis_pfit_plus = np.zeros(y_pis_npfit_enbpi.shape) +(y_pred_pfit_plus[:gap], y_pis_pfit_plus[:gap, :, :],) = mapie_plus.predict( + X_test.iloc[:gap, :], + alpha=alpha, + beta_optimize=True, ) -coverage_npfit_JAB_T = regression_coverage_score( - y_test, y_pis_npfit_JAB_T[:, 0, 0], y_pis_npfit_JAB_T[:, 1, 0] +for step in range(gap, len(X_test), gap): + mapie_plus.partial_fit( + X_test.iloc[(step - gap) : step, :], + y_test.iloc[(step - gap) : step], + ) + ( + y_pred_pfit_plus[step : step + gap], + y_pis_pfit_plus[step : step + gap, :, :], + ) = mapie_plus.predict( + X_test.iloc[step : (step + gap), :], + alpha=alpha, + ensemble=True, + beta_optimize=True, + ) + +coverage_pfit_plus = regression_coverage_score( + y_test, y_pis_pfit_plus[:, 0, 0], y_pis_pfit_plus[:, 1, 0] ) -width_npfit_JAB_T = ( - y_pis_npfit_JAB_T[:, 1, 0] - y_pis_npfit_JAB_T[:, 0, 0] -).mean() +width_pfit_plus = (y_pis_pfit_plus[:, 1, 0] - y_pis_pfit_plus[:, 0, 0]).mean() -# With partial_fit every hour, JAB_like is True -y_pred_pfit_JAB_T, y_pis_pfit_JAB_T = mapie_no_pfit.predict( - X_test.iloc[:gap_pfit, :], alpha=alpha, JAB_Like=True +print("Plus, with partial_fit, NO width optimization") +mapie_plus = mapie_plus.fit(X_train, y_train) +y_pred_pfit_plus_no_opt = np.zeros(y_pred_npfit_enbpi.shape) +y_pis_pfit_plus_no_opt = np.zeros(y_pis_npfit_enbpi.shape) +( + y_pred_pfit_plus_no_opt[:gap], + y_pis_pfit_plus_no_opt[:gap, :, :], +) = mapie_plus.predict( + X_test.iloc[:gap, :], + alpha=alpha, + beta_optimize=False, ) -for step in range(gap_pfit, len(X_test), gap_pfit): - mapie_pfit_JAB_T.partial_fit( - X_test.iloc[(step - gap_pfit):step, :], - y_test.iloc[(step - gap_pfit):step], +for step in range(gap, len(X_test), gap): + mapie_plus.partial_fit( + X_test.iloc[(step - gap) : step, :], + y_test.iloc[(step - gap) : step], ) - y_pred_gap_step, y_pis_gap_step = mapie_pfit_JAB_T.predict( - X_test.iloc[step:(step + gap_pfit), :], + ( + y_pred_pfit_plus_no_opt[step : step + gap], + y_pis_pfit_plus_no_opt[step : step + gap, :, :], + ) = mapie_plus.predict( + X_test.iloc[step : (step + gap), :], alpha=alpha, - JAB_Like=True, ensemble=True, + beta_optimize=False, ) - y_pred_pfit_JAB_T = np.concatenate( - (y_pred_pfit_JAB_T, y_pred_gap_step), axis=0 + +coverage_pfit_plus_no_opt = regression_coverage_score( + y_test, y_pis_pfit_plus_no_opt[:, 0, 0], y_pis_pfit_plus_no_opt[:, 1, 0] +) +width_pfit_plus_no_opt = ( + y_pis_pfit_plus_no_opt[:, 1, 0] - y_pis_pfit_plus_no_opt[:, 0, 0] +).mean() + +print("Plus, with partial_fit, MapieRegressor_Like") +mapie_plus = mapie_plus.fit(X_train, y_train) +y_pred_pfit_MR = np.zeros(y_pred_npfit_enbpi.shape) +y_pis_pfit_MR = np.zeros(y_pis_npfit_enbpi.shape) +y_pred_pfit_MR[:gap], y_pis_pfit_MR[:gap, :, :] = mapie_plus.root_predict( + X_test.iloc[:gap, :], alpha=alpha +) +for step in range(gap, len(X_test), gap): + mapie_plus.partial_fit( + X_test.iloc[(step - gap) : step, :], + y_test.iloc[(step - gap) : step], ) - y_pis_pfit_JAB_T = np.concatenate( - (y_pis_pfit_JAB_T, y_pis_gap_step), axis=0 + ( + y_pred_pfit_MR[step : step + gap], + y_pis_pfit_MR[step : step + gap, :, :], + ) = mapie_plus.root_predict( + X_test.iloc[step : (step + gap), :], + alpha=alpha, + ensemble=True, ) -coverage_pfit_JAB_T = regression_coverage_score( - y_test, y_pis_pfit_JAB_T[:, 0, 0], y_pis_pfit_JAB_T[:, 1, 0] +coverage_pfit_MR = regression_coverage_score( + y_test, y_pis_pfit_MR[:, 0, 0], y_pis_pfit_MR[:, 1, 0] ) -width_pfit_JAB_T = ( - y_pis_pfit_JAB_T[:, 1, 0] - y_pis_pfit_JAB_T[:, 0, 0] -).mean() +width_pfit_MR = (y_pis_pfit_MR[:, 1, 0] - y_pis_pfit_MR[:, 0, 0]).mean() # Print results print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nWithout any partial_fit. JAB_like is False:" - f"{coverage_npfit_JAB_F:.3f}, {width_npfit_JAB_F:.3f}" + "\nEnbPI without any partial_fit:" + f"{coverage_npfit_enbpi :.3f}, {width_npfit_enbpi:.3f}" +) +print( + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " + "\nEnbPI with partial_fit:" + f"{coverage_pfit_enbpi:.3f}, {width_pfit_enbpi:.3f}" ) print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nWithout any partial_fit. JAB_like is True:" - f"{coverage_npfit_JAB_T:.3f}, {width_npfit_JAB_T:.3f}" + "\nEnbPI with partial_fit, no with optimization:" + f"{coverage_pfit_enbpi_no_opt:.3f}, {width_pfit_enbpi_no_opt:.3f}" ) print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nWith partial_fit. JAB_like is False:" - f"{coverage_pfit_JAB_F:.3f}, {width_pfit_JAB_F:.3f}" + "\nPlus, with partial_fit:" + f"{coverage_pfit_plus:.3f}, {width_pfit_plus:.3f}" ) print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nWith partial_fit. JAB_like is True:" - f"{coverage_pfit_JAB_T:.3f}, {width_pfit_JAB_T:.3f}" + "\nPlus, with partial_fit. no width optimization:" + f"{coverage_pfit_plus_no_opt:.3f}, {width_pfit_plus_no_opt:.3f}" +) +print( + "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " + "\nMR_Like, with partial_fit:" + f"{coverage_pfit_MR:.3f}, {width_pfit_MR:.3f}" ) # Plot estimated prediction intervals on test set -fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots( - nrows=2, ncols=2, figsize=(30, 10), sharey="row", sharex="col" +fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots( + nrows=2, ncols=3, figsize=(30, 25), sharey="row", sharex="col" ) -ax1.set_ylabel("Hourly demand (GW)") -ax1.plot(demand_test.Demand, lw=2, label="Test data", c="C1") +for ax in [ax1, ax2, ax3, ax4, ax5, ax6]: + ax.set_ylabel("Hourly demand (GW)") + ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") + + ax1.plot( - demand_test.index, y_pred_npfit_JAB_F, lw=2, c="C2", label="Predictions" + demand_test.index, y_pred_npfit_enbpi, lw=2, c="C2", label="Predictions" ) ax1.fill_between( demand_test.index, - y_pis_npfit_JAB_F[:, 0, 0], - y_pis_npfit_JAB_F[:, 1, 0], + y_pis_npfit_enbpi[:, 0, 0], + y_pis_npfit_enbpi[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax1.legend() ax1.set_title( - "Without partial_fit, JAB False." - f"Coverage:{coverage_npfit_JAB_F:.3f} Width:{width_npfit_JAB_F:.3f}" + "EnbPI, without partial_fit.\n" + f"Coverage:{coverage_npfit_enbpi:.3f} Width:{width_npfit_enbpi:.3f}" ) -ax2.set_ylabel("Hourly demand (GW)") -ax2.plot(demand_test.Demand, lw=2, label="Test data", c="C1") ax2.plot( - demand_test.index, y_pred_npfit_JAB_T, lw=2, c="C2", label="Predictions" + demand_test.index, y_pred_pfit_enbpi, lw=2, c="C2", label="Predictions" ) ax2.fill_between( demand_test.index, - y_pis_npfit_JAB_T[:, 0, 0], - y_pis_npfit_JAB_T[:, 1, 0], + y_pis_pfit_enbpi[:, 0, 0], + y_pis_pfit_enbpi[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax2.legend() ax2.set_title( - "Without partial_fit, JAB True." - f"Coverage:{coverage_npfit_JAB_T:.3f} Width:{width_npfit_JAB_T:.3f}" + "EnbPI with partial_fit.\n" + f"Coverage:{coverage_pfit_enbpi:.3f} Width:{width_pfit_enbpi:.3f}" ) -ax3.set_ylabel("Hourly demand (GW)") -ax3.plot(demand_test.Demand, lw=2, label="Test data", c="C1") ax3.plot( - demand_test.index, y_pred_npfit_JAB_F, lw=2, c="C2", label="Predictions" + demand_test.index, + y_pred_pfit_enbpi_no_opt, + lw=2, + c="C2", + label="Predictions", ) ax3.fill_between( demand_test.index, - y_pis_npfit_JAB_F[:, 0, 0], - y_pis_npfit_JAB_F[:, 1, 0], + y_pis_pfit_enbpi_no_opt[:, 0, 0], + y_pis_pfit_enbpi_no_opt[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax3.legend() ax3.set_title( - "With partial_fit, JAB False." - f"Coverage:{coverage_npfit_JAB_F:.3f} Width:{width_npfit_JAB_F:.3f}" + "EnbPI with partial_fit. No width optimization\n" + f"Coverage:{coverage_pfit_enbpi_no_opt:.3f}" + f"Width:{width_pfit_enbpi_no_opt:.3f}" ) -ax4.set_ylabel("Hourly demand (GW)") -ax4.plot(demand_test.Demand, lw=2, label="Test data", c="C1") ax4.plot( - demand_test.index, y_pred_pfit_JAB_T, lw=2, c="C2", label="Predictions" + demand_test.index, + y_pred_pfit_plus, + lw=2, + c="C2", + label="Predictions", ) ax4.fill_between( demand_test.index, - y_pis_pfit_JAB_T[:, 0, 0], - y_pis_pfit_JAB_T[:, 1, 0], + y_pis_pfit_plus[:, 0, 0], + y_pis_pfit_plus[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax4.legend() ax4.set_title( - "With partial_fit, JAB True." - f"Coverage:{coverage_npfit_JAB_T:.3f} Width:{width_npfit_JAB_T:.3f}" + "Plus, with partial_fit.\n" + f"Coverage:{coverage_pfit_plus:.3f}" + f"Width:{width_pfit_plus:.3f}" +) + +ax5.plot( + demand_test.index, + y_pred_pfit_plus_no_opt, + lw=2, + c="C2", + label="Predictions", +) +ax5.fill_between( + demand_test.index, + y_pis_pfit_plus_no_opt[:, 0, 0], + y_pis_pfit_plus_no_opt[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", +) +ax5.set_title( + "Plus, with partial_fit no width optimization\n" + f"Coverage:{coverage_pfit_plus_no_opt:.3f}" + f"Width:{width_pfit_plus_no_opt:.3f}" +) + + +ax6.plot(demand_test.index, y_pred_pfit_MR, lw=2, c="C2", label="Predictions") +ax6.fill_between( + demand_test.index, + y_pis_pfit_MR[:, 0, 0], + y_pis_pfit_MR[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", +) +ax6.set_title( + "MapieRegressor Like, with partial_fit\n" + f"Coverage:{coverage_pfit_MR:.3f} Width:{width_pfit_MR:.3f}" ) +ax1.legend() plt.show() diff --git a/examples/regression/plot_timeseries_enbpi_train.py b/examples/regression/plot_timeseries_enbpi_train.py index e9ee8d450..2200d0635 100644 --- a/examples/regression/plot_timeseries_enbpi_train.py +++ b/examples/regression/plot_timeseries_enbpi_train.py @@ -40,7 +40,9 @@ demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") demand_df["Hour"] = demand_df.index.hour -for hour in range(1, 3): + +n_lags = 5 +for hour in range(1, n_lags): demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) # Train/validation/test split @@ -48,7 +50,7 @@ demand_train = demand_df.iloc[:-num_test_steps, :].copy() demand_test = demand_df.iloc[-num_test_steps:, :].copy() features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ - f"Lag_{hour}" for hour in range(1, 2) + f"Lag_{hour}" for hour in range(1, n_lags) ] X_train = demand_train.loc[ diff --git a/mapie/quantile_timeit.ipynb b/mapie/quantile_timeit.ipynb new file mode 100644 index 000000000..d9770966d --- /dev/null +++ b/mapie/quantile_timeit.ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "d4595769-7300-44d1-8850-c269ebf72fde", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.append(\"/Users/tmorzadec/Missions/MAPIE\")\n", + "import numpy as np\n", + "from mapie.utils import masked_quantile\n", + "from mapie.regression import MapieRegressor\n", + "from sklearn.datasets import make_regression\n", + "import numpy.ma as ma\n", + "from pycallgraph2 import PyCallGraph, Config\n", + "from pycallgraph2.output import GraphvizOutput\n", + "from functools import lru_cache\n", + "import callgraph.decorator as callgraph\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4e824af9-8e84-4b89-b6de-e983a168c02f", + "metadata": {}, + "outputs": [], + "source": [ + "X = np.random.uniform(low=-100, high=100, size = int(1e8)).reshape(int(1e4), -1)\n", + "\n", + "# indices1 = np.random.choice(X.shape[0], size=1, replace=True)\n", + "# indices2 = np.random.choice(X.shape[1], size=1, replace=True)\n", + "# indices = zip(indices1, indices2)\n", + "\n", + "# for (x, y) in indices:\n", + "# X[x,y] = np.nan\n", + "# " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ea9ba9ba-3dcf-43f0-9425-91437fd6f000", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1000, 10000)\n" + ] + } + ], + "source": [ + "q = list(np.linspace(0.1, 0.9, 1000))\n", + "print(X.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f90f220e-cc20-40cc-a33c-566470b61856", + "metadata": {}, + "outputs": [], + "source": [ + "@callgraph()\n", + "@lru_cache()\n", + "def mask():\n", + " masked_quantile(ma.masked_invalid(X), q, axis=0, method=\"higher\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29592f5d", + "metadata": {}, + "outputs": [], + "source": [ + "%timeit -n 1 -r 1 masked_quantile(ma.masked_invalid(X), q, axis=0, method=\"higher\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "843ab182-fd23-40f9-a569-647b1a2ad165", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.48 s ± 401 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit np.nanquantile(X, q, axis=0, interpolation=\"higher\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "260ba3a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.35 s ± 502 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit np.quantile(X, q, axis=0, interpolation=\"higher\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5349359a", + "metadata": {}, + "outputs": [], + "source": [ + "config = Config(max_depth=10)\n", + "with PyCallGraph(output=GraphvizOutput(), config=config):\n", + " masked_quantile(ma.masked_invalid(X), q, axis=0, method=\"higher\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2acbef74-3f03-457a-b31e-a5dad75d70c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "500\n", + "(1, 500)\n", + "2\n", + "500\n", + "(1, 500)\n", + "2\n", + "500\n", + "(1, 500)\n", + "2\n", + "500\n", + "(1, 500)\n", + "[-118.90258408 209.02916237 -22.18296168 234.40885886 -159.61745268\n", + " -184.84111207 26.85546511 -19.12965127 -162.97950999 -53.36413493]\n", + "(1, 2, 1000)\n" + ] + } + ], + "source": [ + "mapie_reg = MapieRegressor(method=\"minmax\", agg_function=\"mean\", cv=-1)\n", + "alpha = [0.2, 0.8]\n", + "mapie_reg.fit(X, y)\n", + "#y_pred_float1, y_pis_float1 = mapie_reg.predict(X, alpha=alpha[0])\n", + "#y_pred_float2, y_pis_float2 = mapie_reg.predict(X, alpha=alpha[1])\n", + "y_pred_array, y_pis_array = mapie_reg.predict(X, alpha=alpha)\n", + "#print(y_pis_float1[0,1,:10])\n", + "#print(y_pis_float2[0,1,:10])\n", + "print(y_pis_array[0,1,:10])\n", + "print(y_pis_array.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f82a3d9-cacc-42c6-9350-0709811a0af9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.44901419, -0.31701145, 0.03759213, -1.03072401, 0.32107287,\n", + " 0.73329445, -0.16373546, -0.58167561, 0.24257418, -0.40065236])" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "X[0,:].flatten()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d03bf675-6fd1-4407-ae2d-0937c2e46df9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "e1d6b69c58a8ab3fab9d4bd10bf376ef86c3438c956e9d4e062e4cc32a9f8bce" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/mapie/regression.py b/mapie/regression.py index 1bceac984..3465fe9a7 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -421,13 +421,14 @@ def aggregate_with_mask(self, x: NDArray, k: NDArray) -> NDArray: ArrayLike of shape (n_samples_test,) Array of aggregated predictions for each testing sample. """ - if self.agg_function == "median": - return phi2D(A=x, B=k, fun=lambda x: np.nanmedian(x, axis=1)) if self.cv == "prefit": raise ValueError( "There should not be aggregation of predictions if cv is " "'prefit'" ) + if self.agg_function == "median": + return phi2D(A=x, B=k, fun=lambda x: np.nanmedian(x, axis=1)) + # To aggregate with mean() the aggregation coud be done # with phi2D(A=x, B=k, fun=lambda x: np.nanmean(x, axis=1). # However, phi2D contains a np.apply_along_axis loop which @@ -610,7 +611,7 @@ def predict( alpha_np = cast(NDArray, alpha) check_alpha_and_n_samples(alpha_np, n) if self.method in ["naive", "base"] or self.cv == "prefit": - quantile = masked_quantile( + quantile = np.nanquantile( self.conformity_scores_, 1 - alpha_np, method="higher" ) y_pred_low = y_pred[:, np.newaxis] - quantile @@ -645,8 +646,8 @@ def predict( y_pred_low = np.column_stack( [ - masked_quantile( - ma.masked_invalid(lower_bounds), + np.nanquantile( + lower_bounds, _alpha, axis=1, method="lower", @@ -657,8 +658,8 @@ def predict( y_pred_up = np.column_stack( [ - masked_quantile( - ma.masked_invalid(upper_bounds), + np.nanquantile( + upper_bounds, 1 - _alpha, axis=1, method="higher", diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index e66fe174b..bcf566afc 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -51,18 +51,37 @@ agg_function="mean", cv=KFold(n_splits=3, shuffle=True, random_state=1), ), - "jackknife_plus_ab": Params( - method="plus", + "jackknife_plus_ab_enbpi": Params( + method="enbpi", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_minmax_ab": Params( + "jackknife_minmax_ab_enbpi": Params( method="minmax", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_plus_median_ab": Params( - method="plus", + "jackknife_plus_median_ab_enbpi": Params( + method="enbpi", + agg_function="median", + cv=BlockBootstrap( + n_resamplings=30, + n_blocks=5, + random_state=1, + ), + ), + "jackknife_plus_ab_enbpi_no_opt": Params( + method="enbpi", + agg_function="mean", + cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), + ), + "jackknife_minmax_ab_enbpi_no_opt": Params( + method="minmax", + agg_function="mean", + cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), + ), + "jackknife_plus_median_ab_enbpi_no_opt": Params( + method="enbpi", agg_function="median", cv=BlockBootstrap( n_resamplings=30, @@ -70,7 +89,7 @@ random_state=1, ), ), - "jackknife_plus_ab_JAB": Params( + "jackknife_plus_ab_plus": Params( method="plus", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), @@ -101,12 +120,18 @@ "cv_minmax": 3.95, "prefit": 3.89, "cv_plus_median": 3.90, - "jackknife_plus_ab": 3.76, - "jackknife_minmax_ab": 3.96, - "jackknife_plus_median_ab": 3.76, - "jackknife_plus_ab_JAB": 3.76, - "jackknife_minmax_ab_JAB": 3.96, - "jackknife_plus_median_ab_JAB": 3.76, + "jackknife_plus_ab_enbpi": 3.76, + "jackknife_minmax_ab_enbpi": 3.96, + "jackknife_plus_median_ab_enbpi": 3.76, + "jackknife_plus_ab_enbpi_no_opt": 3.76, + "jackknife_minmax_ab_enbpi_no_opt": 3.96, + "jackknife_plus_median_ab_enbpi_no_opt": 3.76, + "jackknife_plus_ab_plus": 3.76, + "jackknife_minmax_ab_plus": 3.96, + "jackknife_plus_median_ab_plus": 3.76, + "jackknife_plus_ab_MR": 3.76, + "jackknife_minmax_ab_MR": 3.96, + "jackknife_plus_median_MR": 3.76, } COVERAGES = { @@ -119,12 +144,18 @@ "cv_minmax": 0.956, "prefit": 0.90, "cv_plus_median": 0.954, - "jackknife_plus_ab": 0.952, - "jackknife_minmax_ab": 0.960, - "jackknife_plus_median_ab": 0.946, - "jackknife_plus_ab_JAB": 0.92, - "jackknife_minmax_ab_JAB": 0.940, - "jackknife_plus_median_ab_JAB": 0.94, + "jackknife_plus_ab_enbpi": 0.952, + "jackknife_minmax_ab_enbpi": 0.960, + "jackknife_plus_median_ab_enbpi": 0.946, + "jackknife_plus_ab_enbpi_no_opt": 0.952, + "jackknife_minmax_ab_enbpi_no_opt": 0.960, + "jackknife_plus_median_ab_enbpi_no_opt": 0.946, + "jackknife_plus_ab_plus": 0.92, + "jackknife_minmax_ab_plus": 0.940, + "jackknife_plus_median_ab_plus": 0.94, + "jackknife_plus_ab_MR": 0.92, + "jackknife_minmax_ab_MR": 0.940, + "jackknife_plus_median_MR": 0.94, } diff --git a/mapie/tests/test_utils.py b/mapie/tests/test_utils.py index 6be7abbed..5382e51f5 100644 --- a/mapie/tests/test_utils.py +++ b/mapie/tests/test_utils.py @@ -16,7 +16,6 @@ check_null_weight, check_verbose, fit_estimator, - masked_quantile, ) from mapie._typing import ArrayLike @@ -193,32 +192,3 @@ def test_invalid_verbose(verbose: Any) -> None: def test_valid_verbose(verbose: Any) -> None: """Test that valid verboses raise no errors.""" check_verbose(verbose) - - -def test_masked_quantile_invalid_minus_one(): - with pytest.raises(ValueError, match=r".*axis should be None, 0 or 1.*"): - masked_quantile(a=X_toy, q=0.1, axis=-1) - - -def test_masked_quantile_invalid_one(): - with pytest.raises(ValueError, match=r".*axis 1 is out of bounds.*"): - masked_quantile(a=X_toy.flatten(), q=0.1, axis=1) - - -def test_masked_quantile_linear_interpolation(): - quantiles = masked_quantile( - a=X_toy, q=[0.1, 0.2, 0.5], axis=0, method="linear" - ) - np.testing.assert_allclose(quantiles, np.array([[0.5, 1.0, 2.5]])) - - -def test_masked_quantile_linear_interpolation_scalar(): - quantile = masked_quantile( - a=X_toy.flatten(), q=0.1, axis=0, method="linear" - ) - assert quantile == 0.5 - - -def test_masked_quantile_invalid_method(): - with pytest.raises(ValueError, match=r".*'method' has to be 'higher'.*"): - masked_quantile(a=X_toy.flatten(), q=0.1, axis=0, method="lineaar") diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index cee380170..2cab837c5 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -3,7 +3,6 @@ from typing import Iterable, Optional, Tuple, Union, cast import numpy as np -import numpy.ma as ma from sklearn.base import RegressorMixin from sklearn.model_selection import BaseCrossValidator from sklearn.utils import check_array @@ -15,31 +14,30 @@ from .utils import ( check_alpha, check_alpha_and_n_samples, - masked_quantile, ) class MapieTimeSeriesRegressor(MapieRegressor): """ - Prediction interval with out-of-fold residuals for time series. + Prediction intervals with out-of-fold residuals for time series. - This class implements the EnbPI strategy and some variations - for estimating prediction intervals on single-output time series. + This class implements the EnbPI strategy and some variants for estimating + prediction intervals on single-output time series. Actually, EnbPI only corresponds to ``MapieTimeSeriesRegressor`` if the - ``cv`` argument if of type ``BlockBootstrap``. + ``cv`` argument is of type ``BlockBootstrap`` and ``method`` is "enbpi". References ---------- Chen Xu, and Yao Xie. - [6] "Conformal prediction for dynamic time-series." + "Conformal prediction for dynamic time-series." https://arxiv.org/abs/2010.09107 """ def __init__( self, estimator: Optional[RegressorMixin] = None, - method: str = "plus", + method: str = "enbpi", cv: Optional[Union[int, str, BaseCrossValidator]] = None, n_jobs: Optional[int] = None, agg_function: Optional[str] = "mean", @@ -47,6 +45,30 @@ def __init__( ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) self.cv_need_agg_function.append("BlockBootstrap") + self.valid_methods_.append("enbpi") + + def _relative_conformity_scores( + self, + X: ArrayLike, + y: ArrayLike, + ) -> ArrayLike: + """ + Compute the conformity scores on a data set. + + Parameters + ---------- + X : ArrayLike of shape (n_samples, n_features) + Input data. + + y : ArrayLike of shape (n_samples,) + Input labels. + + Returns + ------- + The conformity scores corresponding to the input data set. + """ + y_pred, _ = super().predict(X, alpha=0.5, ensemble=True) + return np.asarray(y) - np.asarray(y_pred) def fit( self, @@ -55,14 +77,17 @@ def fit( sample_weight: Optional[ArrayLike] = None, ) -> MapieTimeSeriesRegressor: """ + Compare to the method ``fit`` of ``MapieRegressor``, the ``fit`` method + of ``MapieTimeSeriesRegressor`` computes the ``conformity_scores_`` + with relative values. + Returns ------- MapieTimeSeriesRegressor The model itself. """ self = super().fit(X=X, y=y, sample_weight=sample_weight) - y_pred, _ = super().predict(X, alpha=0.5, ensemble=True) - self.conformity_scores_ = np.asarray(y) - y_pred + self.conformity_scores_ = self._relative_conformity_scores(X, y) return self def partial_fit( @@ -71,15 +96,16 @@ def partial_fit( y: ArrayLike, ) -> MapieTimeSeriesRegressor: """ - Update the ``conformity_scores_`` attribute when data with known labels - are available. + Update the ``conformity_scores_`` and ``k_`` attributes when new data + with known labels are available. + Note: Don't use ``partial_fit`` with samples of the training set. Parameters ---------- - X : ArrayLike of shape (n_samples, n_features) + X : ArrayLike of shape (n_samples_test, n_features) Input data. - y : ArrayLike of shape (n_samples,) + y : ArrayLike of shape (n_samples_test,) Input labels. Returns @@ -87,43 +113,135 @@ def partial_fit( MapieTimeSeriesRegressor The model itself. """ - y_pred, _ = self.predict(X, alpha=0.5, ensemble=True) - new_conformity_scores_ = np.asarray(y) - np.asarray(y_pred) - new_conformity_scores_ = new_conformity_scores_[ - ~np.isnan(new_conformity_scores_) - ] - - cut_index = min( - len(new_conformity_scores_), len(self.conformity_scores_) - ) + if len(X) > len(self.conformity_scores_): + raise ValueError("You try to update more residuals than tere are!") + new_conformity_scores_ = self._relative_conformity_scores(X, y) self.conformity_scores_ = np.concatenate( [ - self.conformity_scores_[cut_index:], + self.conformity_scores_[-len(new_conformity_scores_) :], new_conformity_scores_, - ], - axis=0, + ] ) + self.k_[:, -len(new_conformity_scores_)] = 1.0 return self + def _beta_optimize( + self, + alpha: NDArray, + upper_bounds: NDArray, + lower_bounds: NDArray, + beta_optimize: bool = True, + ) -> NDArray: + """ + ``_beta_optimize`` offers to minimize the width of the PIs, for a given + difference of quantiles. + + Parameters + ---------- + alpha: Optional[NDArray] + The quantiles to compute. + upper_bounds: NDArray + The array of upper values. + lower_bounds: NDArray + The array of lower values. + optimize: bool + Whether to optimize or not. If ``False``, betas are the half of + alphas. + + Returns + ------- + NDArray + Array of betas minimizing the differences + ``(1-alpa+beta)-quantile - beta-quantile``. + """ + if lower_bounds.shape != upper_bounds.shape: + raise ValueError( + "Lower and upper bounds arrays should have the same shape." + ) + betas_0 = np.full( + shape=(len(alpha), len(lower_bounds)), + fill_value=np.nan, + dtype=float, + ) + if not beta_optimize: + for ind_alpha, _alpha in enumerate(alpha): + betas_0[ind_alpha, :] = _alpha / 2.0 + return betas_0 + + for ind_alpha, _alpha in enumerate(alpha): + betas = np.linspace( + _alpha / (len(lower_bounds) + 1), + _alpha, + num=len(lower_bounds), + endpoint=False, + ) + one_alpha_beta = np.nanquantile( + upper_bounds, + 1 - _alpha + betas, + axis=1, + method="higher", + ) # type: ignore + beta = np.nanquantile( + lower_bounds, + betas, + axis=1, + method="lower", + ) # type: ignore + if len(betas_0.shape) == 2: + betas_0[ind_alpha, :] = betas[ + np.argmin(one_alpha_beta - beta, axis=0) + ] + else: + betas_0[ind_alpha] = betas[ + np.argmin(one_alpha_beta - beta, axis=0)[0] + ] + return betas_0 + + def _pred_multi(self, X: NDArray) -> NDArray: + """ + Return a prediction per train sample for each test sample, by + aggregation with matrix ``k_``. + + Parameters + ---------- + X: NDArray of shape (n_samples_test, n_features) + Input data + + Returns + ------- + NDArray of shape (n_samples_test, n_samples_train) + """ + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size + # thanks to the shape of k_. + + y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + return y_pred_multi + def predict( self, X: ArrayLike, ensemble: bool = False, alpha: Optional[Union[float, Iterable[float]]] = None, - JAB_Like=False, + beta_optimize: bool = True, ) -> Union[NDArray, Tuple[NDArray, NDArray]]: """ - ``predict`` method correspond to the ``MapieRegressor``'s one with the - argument ``JAB_Like`` in more. In case ``JAB_Like`` is ``False``, - predictions correspond to [6]. In case it is True, is is another - implementation inspired of [2] with wider PIs. The second method is - much slower because of PIwise optimization. + Correspond to the ``MapieRegressor``'s one with the + method ``'plus'``. In case ``method`` is ``'enbpi'``, predictions + correspond to 'Conformal prediction for dynamic time-series'. The + method ``'plus'`` is slower because of PI-wise optimization. However, + you can choose not to optimize the width of the PIs by setting + ``beta_optimize`` to ``False``. Parameters ---------- - JAB_Like : boolean, default False - Whether to use the implementation of [6] or an implementation - closer to [2]. + beta_optimize: bool + Whether to optimize the PIs' width or not. + """ # Checks @@ -132,194 +250,135 @@ def predict( alpha = cast(Optional[NDArray], check_alpha(alpha)) X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) y_pred = self.single_estimator_.predict(X) - n = len(self.conformity_scores_) if alpha is None: return np.array(y_pred) else: alpha_np = cast(NDArray, alpha) - check_alpha_and_n_samples(alpha_np, n) + check_alpha_and_n_samples(alpha_np, len(self.conformity_scores_)) - if ( - (not JAB_Like) - or (self.method in ["naive", "base"]) - or (self.cv == "prefit") + if (self.method in ["enbpi", "naive", "base"]) or ( + self.cv == "prefit" ): - # This version of predict corresponds to [6]. - # Its PIs are closed to the oracle's ones. - betas_0 = np.full_like(alpha_np, np.nan, dtype=float) - for ind, _alpha in enumerate(alpha_np): - betas = np.linspace( - _alpha / (n + 1), _alpha, num=n + 1, endpoint=False - ) - - one_alpha_beta = masked_quantile( - ma.masked_invalid(self.conformity_scores_), - 1 - _alpha + betas, - axis=0, - method="higher", - ) # type: ignore - - beta = masked_quantile( - ma.masked_invalid(self.conformity_scores_), - betas, - axis=0, - method="lower", - ) # type: ignore - - betas_0[ind] = betas[ - np.argmin(one_alpha_beta - beta, axis=1) - ] - - lower_quantiles = masked_quantile( - ma.masked_invalid(self.conformity_scores_), - betas_0, + betas_0 = self._beta_optimize( + alpha=alpha_np, + lower_bounds=self.conformity_scores_, + upper_bounds=self.conformity_scores_, + beta_optimize=beta_optimize, + ) + lower_quantiles = np.nanquantile( + self.conformity_scores_, + betas_0[:, 0], axis=0, method="lower", ).T # type: ignore - higher_quantiles = masked_quantile( - ma.masked_invalid(self.conformity_scores_), - 1 - alpha_np + betas_0, + higher_quantiles = np.nanquantile( + self.conformity_scores_, + 1 - alpha_np + betas_0[:, 0], axis=0, method="higher", ).T # type: ignore if (self.method in ["naive", "base"]) or (self.cv == "prefit"): + y_pred_low = y_pred[:, np.newaxis] + lower_quantiles + y_pred_up = y_pred[:, np.newaxis] + higher_quantiles + else: # method == "enbpi" + # Correspond to "Conformal prediction for dynamic time + # series". + # Its PIs are closed to the oracle's ones if beta_optimized + # is True. + y_pred_multi = self._pred_multi(X) + pred = aggregate_all(self.agg_function, y_pred_multi) y_pred_low = np.column_stack( [ - y_pred[:, np.newaxis] + lower_quantiles[k] - for k in range(len(alpha_np)) + pred + lower_quantiles[k] + for k, _ in enumerate(alpha_np) ] ) y_pred_up = np.column_stack( [ - y_pred[:, np.newaxis] + higher_quantiles[k] - for k in range(len(alpha_np)) + pred + higher_quantiles[k] + for k, _ in enumerate(alpha_np) ] ) - else: - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size thanks - # to the shape of k_. - y_pred_multi = self.aggregate_with_mask( - y_pred_multi, self.k_ + if self.method == "minmax": + lower_bounds = np.min(y_pred_multi, axis=1, keepdims=True) + upper_bounds = np.max(y_pred_multi, axis=1, keepdims=True) + y_pred_low = np.column_stack( + [ + lower_bounds + lower_quantiles[k] + for k, _ in enumerate(alpha_np) + ] ) - - if self.method == "plus": - pred = aggregate_all(self.agg_function, y_pred_multi) - y_pred_low = np.column_stack( - [ - pred + lower_quantiles[k] - for k in range(len(alpha_np)) - ] - ) - y_pred_up = np.column_stack( - [ - pred + higher_quantiles[k] - for k in range(len(alpha_np)) - ] - ) - - if self.method == "minmax": - lower_bounds = np.min( - y_pred_multi, axis=1, keepdims=True - ) - upper_bounds = np.max( - y_pred_multi, axis=1, keepdims=True - ) - y_pred_low = np.column_stack( - [ - lower_bounds + lower_quantiles[k] - for k in range(len(alpha_np)) - ] - ) - y_pred_up = np.column_stack( - [ - upper_bounds + higher_quantiles[k] - for k in range(len(alpha_np)) - ] - ) - else: - # This version of predict corresponds to [2]. - # Its PIs are wider. It does not coorespond to [6]. It is - # a try. It is slower because the betas - # (width optimization parameters of the PIs) are optimized at - # every points. - - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size - # thanks to the shape of k_. - - y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) - + y_pred_up = np.column_stack( + [ + upper_bounds + higher_quantiles[k] + for k, _ in enumerate(alpha_np) + ] + ) + elif self.method == "plus": + # This version of predict corresponds to "Predictive Inference + # Is Free with the Jackknife+-after-Bootstrap.". + # Its PIs are wider. It does not coorespond to "Conformal + # prediction for dynamic time series". It is a try. It is + # slower because the betas (width optimization parameters of + # the PIs) are optimized for every point. + + y_pred_multi = self._pred_multi(X) y_pred_low = np.empty((len(y_pred), len(alpha)), dtype=float) y_pred_up = np.empty((len(y_pred), len(alpha)), dtype=float) + lower_bounds = y_pred_multi + self.conformity_scores_ + upper_bounds = y_pred_multi + self.conformity_scores_ + + betas_0 = self._beta_optimize( + alpha=alpha_np, + lower_bounds=lower_bounds, + upper_bounds=upper_bounds, + beta_optimize=beta_optimize, + ) + for ind_alpha, _alpha in enumerate(alpha_np): - betas = np.linspace( - _alpha / (n + 1), _alpha, num=n + 1, endpoint=False - ) + lower_quantiles = np.empty((betas_0.shape[1],)) + upper_quantiles = np.empty((betas_0.shape[1],)) - if self.method == "plus": - lower_bounds = y_pred_multi + self.conformity_scores_ - upper_bounds = y_pred_multi + self.conformity_scores_ - - if self.method == "minmax": - lower_bounds = np.min( - y_pred_multi, axis=1, keepdims=True - ) - upper_bounds = np.max( - y_pred_multi, axis=1, keepdims=True - ) - lower_bounds = lower_bounds + self.conformity_scores_ - upper_bounds = upper_bounds + self.conformity_scores_ - - one_alpha_beta = masked_quantile( - ma.masked_invalid(upper_bounds), - 1 - _alpha + betas, - axis=1, - method="higher", - ).T # type: ignore - - beta = masked_quantile( - ma.masked_invalid(lower_bounds), - betas, - axis=1, - method="lower", - ).T # type: ignore - - betas_0 = betas[np.argmin(one_alpha_beta - beta, axis=0)] - - lower_quantiles = np.empty((len(betas_0),)) - upper_quantiles = np.empty((len(betas_0),)) - - for ind, beta_0 in enumerate(betas_0): - lower_quantiles[ind] = masked_quantile( - ma.masked_invalid(lower_bounds[ind, :]), + for ind_beta_0, beta_0 in enumerate(betas_0[ind_alpha, :]): + lower_quantiles[ind_beta_0] = np.nanquantile( + lower_bounds[ind_beta_0, :], beta_0, axis=0, method="lower", - ).T # type: ignore + ) # type: ignore - upper_quantiles[ind] = masked_quantile( - ma.masked_invalid(upper_bounds[ind, :]), + upper_quantiles[ind_beta_0] = np.nanquantile( + upper_bounds[ind_beta_0, :], 1 - _alpha + beta_0, axis=0, method="higher", - ).T # type: ignore - y_pred_low[:, ind_alpha] = lower_quantiles - y_pred_up[:, ind_alpha] = upper_quantiles + ) # type: ignore + y_pred_low[:, ind_alpha] = lower_quantiles + y_pred_up[:, ind_alpha] = upper_quantiles if ensemble: y_pred = aggregate_all(self.agg_function, y_pred_multi) return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) + + def root_predict( + self, + X: ArrayLike, + ensemble: bool = False, + alpha: Optional[Union[float, Iterable[float]]] = None, + ) -> Union[NDArray, Tuple[NDArray, NDArray]]: + """ + ``root_predict`` method correspond to the one of ``MapieRegressor``'s. + """ + conformity_scores_save = self.conformity_scores_.copy() + self.conformity_scores_ = np.abs(self.conformity_scores_) + if alpha is None: + y_pred = super().predict(X=X, ensemble=ensemble, alpha=alpha) + self.conformity_scores_ = conformity_scores_save + return y_pred + y_pred, y_pis = super().predict(X=X, ensemble=ensemble, alpha=alpha) + self.conformity_scores_ = conformity_scores_save + return y_pred, y_pis diff --git a/mapie/utils.py b/mapie/utils.py index 23158a9ac..e7be5d6c8 100644 --- a/mapie/utils.py +++ b/mapie/utils.py @@ -424,112 +424,3 @@ def check_input_is_image(X: ArrayLike) -> None: "When X is an image, the number of dimensions" "must be equal to 3 or 4." ) - - -def masked_quantile( - a: NDArray, - q: Union[float, NDArray], - axis: Optional[int] = None, - method: str = "linear", -) -> NDArray: - """ - Compute quantile for masked arrays. It avoids using ``np.nanquantile`` that - is quite slow because of an ``apply_along_axis`` loop. - - Parameters - ---------- - a: NDArray - Array of data. - - q: Union[float, NDArray] - Quantiles. - - axis:Optional[int] - ``axis`` is ``None``, ``0`` or ``1``, default ``None``. - If ``axis`` is ``None``, compute the quantiles of the flatten array. - - method: str - "linear", "higher" or "lower" - """ - return_float = False - flatten = False - - if isinstance(q, float): - flatten = True - if (len(a.shape)) == 1 or (len(a) == 1): - return_float = True - - q = cast(NDArray, check_alpha(q)) - if (len(a.shape) == 1) or (a.shape[1] == 1): - if axis == 1: - raise ValueError( - "axis 1 is out of bounds for array of dimension 1" - ) - if len(q) == 1: - flatten = True - - if axis is None: - a = a.flatten() - axis = 0 - - if axis == 0: - if (len(a.shape) == 1) or (a.shape[1] == 1): - a = a.reshape(-1, 1) - if hasattr(a, "mask"): - a_m = cast(ma.MaskedArray, a) - a_np = np.where(a_m.mask, np.inf, a_m.data) - masked_sum = np.sum(a_m.mask, axis=0).astype(int) - else: - a_np = a - masked_sum = np.zeros((a.shape[1],)) - a_np = np.sort(a_np, axis=0) - grid = np.indices(a_np.shape)[0] - a_tile = np.tile(a_np, (len(q), 1, 1)) - grid_tile = np.tile(grid, (len(q), 1, 1)) - - nb_valid_values = (len(a_np) - masked_sum).astype(int) - q_out = np.outer(nb_valid_values - 1, q) - q_out_inf = np.floor(q_out).astype(int) - q_out_sup = np.ceil(q_out) - q_out_sup = np.minimum( - q_out_sup, np.outer(nb_valid_values - 1, np.ones(len(q))) - ).astype(int) - - inf_bool = np.equal( - grid_tile, - np.tile(q_out_inf.T, (1, 1, a_np.shape[0])) - .reshape(len(q), *a_np.shape) - .astype(int), - ) - sup_bool = np.equal( - grid_tile, - np.tile(q_out_sup.T, (1, 1, a_np.shape[0])).reshape( - (len(q), *a_np.shape) - ), - ) - - lower_values = a_tile[inf_bool].reshape(len(q), -1) - upper_values = a_tile[sup_bool].reshape(len(q), -1) - - if method == "lower": - values = lower_values - elif method == "higher": - values = upper_values - elif method == "linear": - values = lower_values + (q_out - q_out_inf).T * ( - upper_values - lower_values - ) - else: - raise ValueError("'method' has to be 'higher','lower', or'linear'") - if flatten: - values = values.flatten() - else: - values = values.T - - if return_float: - values = values[0] - return values - elif axis == 1: - return masked_quantile(a.T, q, axis=0, method=method) - else: - raise ValueError("axis should be None, 0 or 1") From 81c46793e712a53360f129152a813cdb8c5fa7ee Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Thu, 14 Apr 2022 11:53:42 +0200 Subject: [PATCH 17/32] [REFACTO] new version of enbpi refactorized --- .../plot_MapieRegressor_benchmark.py | 1 - examples/regression/plot_timeseries_enbpi.py | 50 +++--- mapie/regression.py | 6 +- mapie/tests/test_time_series_regression.py | 156 ++++++++---------- mapie/time_series_regression.py | 100 ++++++----- mapie/utils.py | 1 - 6 files changed, 143 insertions(+), 171 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py b/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py index f899a9ea7..35bd02e2a 100644 --- a/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py +++ b/examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py @@ -6,7 +6,6 @@ prediction intervals capturing both aleatoric and epistemic uncertainties on a one-dimensional dataset with homoscedastic noise and normal sampling. """ -from ast import Sub from typing import Any, Callable, Tuple, TypeVar, Union from typing_extensions import TypedDict diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index fa167cad9..05ca7350b 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -105,14 +105,14 @@ for step in range(gap, len(X_test), gap): mapie_enpbi.partial_fit( - X_test.iloc[(step - gap) : step, :], - y_test.iloc[(step - gap) : step], + X_test.iloc[(step - gap):step, :], + y_test.iloc[(step - gap):step], ) ( - y_pred_pfit_enbpi[step : step + gap], - y_pis_pfit_enbpi[step : step + gap, :, :], + y_pred_pfit_enbpi[step:step + gap], + y_pis_pfit_enbpi[step:step + gap, :, :], ) = mapie_enpbi.predict( - X_test.iloc[step : (step + gap), :], + X_test.iloc[step:(step + gap), :], alpha=alpha, ensemble=True, beta_optimize=True, @@ -137,14 +137,14 @@ for step in range(gap, len(X_test), gap): mapie_enpbi.partial_fit( - X_test.iloc[(step - gap) : step, :], - y_test.iloc[(step - gap) : step], + X_test.iloc[(step - gap):step, :], + y_test.iloc[(step - gap):step], ) ( - y_pred_pfit_enbpi_no_opt[step : step + gap], - y_pis_pfit_enbpi_no_opt[step : step + gap, :, :], + y_pred_pfit_enbpi_no_opt[step:step + gap], + y_pis_pfit_enbpi_no_opt[step:step + gap, :, :], ) = mapie_enpbi.predict( - X_test.iloc[step : (step + gap), :], + X_test.iloc[step:step + gap, :], alpha=alpha, ensemble=True, beta_optimize=False, @@ -168,14 +168,14 @@ ) for step in range(gap, len(X_test), gap): mapie_plus.partial_fit( - X_test.iloc[(step - gap) : step, :], - y_test.iloc[(step - gap) : step], + X_test.iloc[step - gap:step, :], + y_test.iloc[step - gap:step], ) ( - y_pred_pfit_plus[step : step + gap], - y_pis_pfit_plus[step : step + gap, :, :], + y_pred_pfit_plus[step:step + gap], + y_pis_pfit_plus[step:step + gap, :, :], ) = mapie_plus.predict( - X_test.iloc[step : (step + gap), :], + X_test.iloc[step:step + gap, :], alpha=alpha, ensemble=True, beta_optimize=True, @@ -200,14 +200,14 @@ ) for step in range(gap, len(X_test), gap): mapie_plus.partial_fit( - X_test.iloc[(step - gap) : step, :], - y_test.iloc[(step - gap) : step], + X_test.iloc[step - gap:step, :], + y_test.iloc[step - gap: step], ) ( - y_pred_pfit_plus_no_opt[step : step + gap], - y_pis_pfit_plus_no_opt[step : step + gap, :, :], + y_pred_pfit_plus_no_opt[step:step + gap], + y_pis_pfit_plus_no_opt[step:step + gap, :, :], ) = mapie_plus.predict( - X_test.iloc[step : (step + gap), :], + X_test.iloc[step:step + gap, :], alpha=alpha, ensemble=True, beta_optimize=False, @@ -229,14 +229,14 @@ ) for step in range(gap, len(X_test), gap): mapie_plus.partial_fit( - X_test.iloc[(step - gap) : step, :], - y_test.iloc[(step - gap) : step], + X_test.iloc[step - gap:step, :], + y_test.iloc[step - gap:step], ) ( - y_pred_pfit_MR[step : step + gap], - y_pis_pfit_MR[step : step + gap, :, :], + y_pred_pfit_MR[step:step + gap], + y_pis_pfit_MR[step:step + gap, :, :], ) = mapie_plus.root_predict( - X_test.iloc[step : (step + gap), :], + X_test.iloc[step:step + gap, :], alpha=alpha, ensemble=True, ) diff --git a/mapie/regression.py b/mapie/regression.py index bf8b02109..ae7a48adc 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -4,7 +4,6 @@ from joblib import Parallel, delayed import numpy as np -import numpy.ma as ma from sklearn.base import BaseEstimator, RegressorMixin, clone from sklearn.linear_model import LinearRegression from sklearn.model_selection import BaseCrossValidator @@ -29,9 +28,7 @@ check_null_weight, check_verbose, fit_estimator, - masked_quantile, ) -from ._compatibility import np_quantile class MapieRegressor(BaseEstimator, RegressorMixin): @@ -188,6 +185,7 @@ class MapieRegressor(BaseEstimator, RegressorMixin): cv_need_agg_function = ["Subsample"] valid_methods_ = ["naive", "base", "plus", "minmax"] + plus_like_method = ["plus"] valid_agg_functions_ = [None, "median", "mean"] fit_attributes = [ "single_estimator_", @@ -635,7 +633,7 @@ def predict( y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) - if self.method == "plus": + if self.method in self.plus_like_method: lower_bounds = y_pred_multi - self.conformity_scores_ upper_bounds = y_pred_multi + self.conformity_scores_ diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index bcf566afc..1016f8d3f 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -19,9 +19,8 @@ X_toy = np.array(range(5)).reshape(-1, 1) y_toy = (5.0 + 2.0 * X_toy ** 1.1).flatten() X, y = make_regression(n_samples=500, n_features=10, noise=1.0, random_state=1) -X_short, y_short = X[:50, :], y[:50] k = np.ones(shape=(5, X.shape[1])) -METHODS = ["naive", "base", "plus", "minmax"] +METHODS = ["base", "enbpi", "minmax", "naive", "plus"] Params = TypedDict( "Params", @@ -51,17 +50,17 @@ agg_function="mean", cv=KFold(n_splits=3, shuffle=True, random_state=1), ), - "jackknife_plus_ab_enbpi": Params( + "jackknife_enbpi_ab_wopt": Params( method="enbpi", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_minmax_ab_enbpi": Params( + "jackknife_minmax_ab": Params( method="minmax", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_plus_median_ab_enbpi": Params( + "jackknife_enbpi_median_ab_wopt": Params( method="enbpi", agg_function="median", cv=BlockBootstrap( @@ -70,17 +69,12 @@ random_state=1, ), ), - "jackknife_plus_ab_enbpi_no_opt": Params( + "jackknife_enbpi_ab": Params( method="enbpi", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_minmax_ab_enbpi_no_opt": Params( - method="minmax", - agg_function="mean", - cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), - ), - "jackknife_plus_median_ab_enbpi_no_opt": Params( + "jackknife_enbpi_median_ab": Params( method="enbpi", agg_function="median", cv=BlockBootstrap( @@ -89,17 +83,17 @@ random_state=1, ), ), - "jackknife_plus_ab_plus": Params( + "jackknife_plus_ab_MR": Params( method="plus", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_minmax_ab_JAB": Params( + "jackknife_minmax_ab_MR": Params( method="minmax", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_plus_median_ab_JAB": Params( + "jackknife_plus_ab_median_MR": Params( method="plus", agg_function="median", cv=BlockBootstrap( @@ -111,51 +105,50 @@ } WIDTHS = { - "naive": 3.76, - "jackknife": 3.76, - "jackknife_plus": 3.76, - "jackknife_minmax": 3.82, - "cv": 3.76, - "cv_plus": 3.76, - "cv_minmax": 3.95, - "prefit": 3.89, + "naive": 3.83, + "jackknife": 3.83, + "jackknife_plus": 3.82, + "jackknife_minmax": 3.90, + "cv": 3.83, + "cv_plus": 3.87, + "cv_minmax": 4.03, + "prefit": 4.79, "cv_plus_median": 3.90, - "jackknife_plus_ab_enbpi": 3.76, - "jackknife_minmax_ab_enbpi": 3.96, - "jackknife_plus_median_ab_enbpi": 3.76, - "jackknife_plus_ab_enbpi_no_opt": 3.76, - "jackknife_minmax_ab_enbpi_no_opt": 3.96, - "jackknife_plus_median_ab_enbpi_no_opt": 3.76, - "jackknife_plus_ab_plus": 3.76, - "jackknife_minmax_ab_plus": 3.96, - "jackknife_plus_median_ab_plus": 3.76, - "jackknife_plus_ab_MR": 3.76, + "jackknife_enbpi_ab_wopt": 3.76, + "jackknife_minmax_ab": 3.96, + "jackknife_enbpi_median_ab_wopt": 3.76, + "jackknife_enbpi_ab": 3.76, + "jackknife_minmax_ab": 3.96, + "jackknife_enbpi_median_ab": 3.76, + "jackknife_plus_ab": 3.76, + "jackknife_plus_median_ab": 3.83, + "jackknife_plus_ab_MR": 3.82, "jackknife_minmax_ab_MR": 3.96, - "jackknife_plus_median_MR": 3.76, + "jackknife_plus_ab_median_MR": 3.83, } COVERAGES = { "naive": 0.952, "jackknife": 0.952, - "jackknife_plus": 0.952, + "jackknife_plus": 0.94, "jackknife_minmax": 0.952, "cv": 0.958, "cv_plus": 0.956, - "cv_minmax": 0.956, - "prefit": 0.90, + "cv_minmax": 0.966, + "prefit": 0.98, "cv_plus_median": 0.954, - "jackknife_plus_ab_enbpi": 0.952, - "jackknife_minmax_ab_enbpi": 0.960, - "jackknife_plus_median_ab_enbpi": 0.946, - "jackknife_plus_ab_enbpi_no_opt": 0.952, - "jackknife_minmax_ab_enbpi_no_opt": 0.960, - "jackknife_plus_median_ab_enbpi_no_opt": 0.946, + "jackknife_enbpi_ab_wopt": 0.952, + "jackknife_minmax_ab": 0.960, + "jackknife_enbpi_median_ab_wopt": 0.946, + "jackknife_enbpi_ab": 0.952, + "jackknife_minmax_ab": 0.960, + "jackknife_enbpi_median_ab": 0.946, "jackknife_plus_ab_plus": 0.92, "jackknife_minmax_ab_plus": 0.940, "jackknife_plus_median_ab_plus": 0.94, - "jackknife_plus_ab_MR": 0.92, - "jackknife_minmax_ab_MR": 0.940, - "jackknife_plus_median_MR": 0.94, + "jackknife_plus_ab_MR": 0.954, + "jackknife_minmax_ab_MR": 0.958, + "jackknife_plus_ab_median_MR": 0.954, } @@ -246,15 +239,6 @@ def test_results_single_and_multi_jobs(strategy: str) -> None: np.testing.assert_allclose(y_pred_single, y_pred_multi) np.testing.assert_allclose(y_pis_single, y_pis_multi) - y_pred_single_JAB, y_pis_single_JAB = mapie_single.predict( - X_toy, alpha=0.2, JAB_Like=True - ) - y_pred_multi_JAB, y_pis_multi_JAB = mapie_multi.predict( - X_toy, alpha=0.2, JAB_Like=True - ) - np.testing.assert_allclose(y_pred_single_JAB, y_pred_multi_JAB) - np.testing.assert_allclose(y_pis_single_JAB, y_pis_multi_JAB) - @pytest.mark.parametrize("strategy", [*STRATEGIES]) def test_results_with_constant_sample_weights(strategy: str) -> None: @@ -277,23 +261,8 @@ def test_results_with_constant_sample_weights(strategy: str) -> None: np.testing.assert_allclose(y_pis0, y_pis1) np.testing.assert_allclose(y_pis1, y_pis2) - y_pred0_JAB, y_pis0_JAB = mapie0.predict( - X_short, alpha=0.05, JAB_Like=True - ) - y_pred1_JAB, y_pis1_JAB = mapie1.predict( - X_short, alpha=0.05, JAB_Like=True - ) - y_pred2_JAB, y_pis2_JAB = mapie2.predict( - X_short, alpha=0.05, JAB_Like=True - ) - np.testing.assert_allclose(y_pred0_JAB, y_pred1_JAB) - np.testing.assert_allclose(y_pred1_JAB, y_pred2_JAB) - np.testing.assert_allclose(y_pis0_JAB, y_pis1_JAB) - np.testing.assert_allclose(y_pis1_JAB, y_pis2_JAB) - - -@pytest.mark.parametrize("method", ["plus", "minmax"]) +@pytest.mark.parametrize("method", ["plus", "minmax", "enbpi"]) @pytest.mark.parametrize("cv", [-1, 2, 3, 5]) @pytest.mark.parametrize("agg_function", ["mean", "median"]) @pytest.mark.parametrize("alpha", [0.05, 0.1, 0.2]) @@ -308,24 +277,13 @@ def test_prediction_agg_function( method=method, cv=cv, agg_function=agg_function ) mapie.fit(X, y) - y_pred_1, y_pis_1 = mapie.predict(X_short, ensemble=True, alpha=alpha) - y_pred_2, y_pis_2 = mapie.predict(X_short, ensemble=False, alpha=alpha) + y_pred_1, y_pis_1 = mapie.predict(X, ensemble=True, alpha=alpha) + y_pred_2, y_pis_2 = mapie.predict(X, ensemble=False, alpha=alpha) np.testing.assert_allclose(y_pis_1[:, 0, 0], y_pis_2[:, 0, 0]) np.testing.assert_allclose(y_pis_1[:, 1, 0], y_pis_2[:, 1, 0]) with pytest.raises(AssertionError): np.testing.assert_allclose(y_pred_1, y_pred_2) - y_pred_1_JAB, y_pis_1_JAB = mapie.predict( - X_short, ensemble=True, alpha=alpha, JAB_Like=True - ) - y_pred_2_JAB, y_pis_2_JAB = mapie.predict( - X_short, ensemble=False, alpha=alpha, JAB_Like=True - ) - np.testing.assert_allclose(y_pis_1_JAB[:, 0, 0], y_pis_2_JAB[:, 0, 0]) - np.testing.assert_allclose(y_pis_1_JAB[:, 1, 0], y_pis_2_JAB[:, 1, 0]) - with pytest.raises(AssertionError): - np.testing.assert_allclose(y_pred_1_JAB, y_pred_2_JAB) - @pytest.mark.parametrize("strategy", [*STRATEGIES]) def test_linear_regression_results(strategy: str) -> None: @@ -336,14 +294,16 @@ def test_linear_regression_results(strategy: str) -> None: """ mapie_ts = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) mapie_ts.fit(X, y) - if "JAB" in strategy: - _, y_pis = mapie_ts.predict(X_short, alpha=0.05, JAB_Like=True) + if "opt" in strategy: + beta_optimize = True else: - _, y_pis = mapie_ts.predict(X, alpha=0.05, JAB_Like=False) + beta_optimize = False + _, y_pis = mapie_ts.predict(X, alpha=0.05, beta_optimize=beta_optimize) y_pred_low, y_pred_up = y_pis[:, 0, 0], y_pis[:, 1, 0] width_mean = (y_pred_up - y_pred_low).mean() - if "JAB" in strategy: - coverage = regression_coverage_score(y_short, y_pred_low, y_pred_up) + + if mapie_ts.method == "plus": + coverage = regression_coverage_score(y, y_pred_low, y_pred_up) else: coverage = regression_coverage_score(y, y_pred_low, y_pred_up) np.testing.assert_allclose(width_mean, WIDTHS[strategy], rtol=1e-2) @@ -470,6 +430,8 @@ def test_MapieTimeSeriesRegressor_alpha_is_None() -> None: with pytest.raises(ValueError, match=r".*too many values to unpackt*"): y_pred, y_pis = mapie_ts_reg.predict(X_toy, alpha=None) + with pytest.raises(ValueError, match=r".*too many values to unpackt*"): + y_pred, y_pis = mapie_ts_reg.root_predict(X_toy, alpha=None) def test_MapieTimeSeriesRegressor_partial_fit_ensemble() -> None: @@ -484,3 +446,19 @@ def test_MapieTimeSeriesRegressor_partial_fit_ensemble() -> None: assert round(mapie_ts_reg.conformity_scores_[-1], 2) == round( 17.5 - 18.665, 2 ) + + +def test_MapieTimeSeriesRegressor_partial_fit_two_big() -> None: + """Test ``partial_fit`` raised error.""" + mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) + with pytest.raises(ValueError, match=r".*You try to update more*"): + mapie_ts_reg = mapie_ts_reg.partial_fit(X=X, y=y) + + +def test_MapieTimeSeriesRegressor_beta_optimize_eeror() -> None: + """Test ``partial_fit`` raised error.""" + mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1) + with pytest.raises(ValueError, match=r".*Lower and upper bounds arrays*"): + mapie_ts_reg._beta_optimize( + alpha=0.1, upper_bounds=X, lower_bounds=X_toy, beta_optimize=True + ) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 2cab837c5..27f3b18b1 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -46,12 +46,13 @@ def __init__( super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) self.cv_need_agg_function.append("BlockBootstrap") self.valid_methods_.append("enbpi") + self.plus_like_method.append("enbpi") def _relative_conformity_scores( self, - X: ArrayLike, - y: ArrayLike, - ) -> ArrayLike: + X: NDArray, + y: NDArray, + ) -> NDArray: """ Compute the conformity scores on a data set. @@ -67,7 +68,7 @@ def _relative_conformity_scores( ------- The conformity scores corresponding to the input data set. """ - y_pred, _ = super().predict(X, alpha=0.5, ensemble=True) + y_pred, _ = self.root_predict(X, alpha=0.5, ensemble=True) return np.asarray(y) - np.asarray(y_pred) def fit( @@ -113,12 +114,14 @@ def partial_fit( MapieTimeSeriesRegressor The model itself. """ + X = cast(NDArray, X) + y = cast(NDArray, y) if len(X) > len(self.conformity_scores_): raise ValueError("You try to update more residuals than tere are!") new_conformity_scores_ = self._relative_conformity_scores(X, y) self.conformity_scores_ = np.concatenate( [ - self.conformity_scores_[-len(new_conformity_scores_) :], + self.conformity_scores_[-len(new_conformity_scores_):], new_conformity_scores_, ] ) @@ -127,10 +130,10 @@ def partial_fit( def _beta_optimize( self, - alpha: NDArray, + alpha: Union[float, NDArray], upper_bounds: NDArray, lower_bounds: NDArray, - beta_optimize: bool = True, + beta_optimize: bool = False, ) -> NDArray: """ ``_beta_optimize`` offers to minimize the width of the PIs, for a given @@ -158,6 +161,7 @@ def _beta_optimize( raise ValueError( "Lower and upper bounds arrays should have the same shape." ) + alpha = cast(NDArray, alpha) betas_0 = np.full( shape=(len(alpha), len(lower_bounds)), fill_value=np.nan, @@ -173,7 +177,7 @@ def _beta_optimize( _alpha / (len(lower_bounds) + 1), _alpha, num=len(lower_bounds), - endpoint=False, + endpoint=True, ) one_alpha_beta = np.nanquantile( upper_bounds, @@ -187,17 +191,13 @@ def _beta_optimize( axis=1, method="lower", ) # type: ignore - if len(betas_0.shape) == 2: - betas_0[ind_alpha, :] = betas[ - np.argmin(one_alpha_beta - beta, axis=0) - ] - else: - betas_0[ind_alpha] = betas[ - np.argmin(one_alpha_beta - beta, axis=0)[0] - ] + betas_0[ind_alpha, :] = betas[ + np.argmin(one_alpha_beta - beta, axis=0) + ] + return betas_0 - def _pred_multi(self, X: NDArray) -> NDArray: + def _pred_multi(self, X: ArrayLike) -> NDArray: """ Return a prediction per train sample for each test sample, by aggregation with matrix ``k_``. @@ -257,13 +257,13 @@ def predict( alpha_np = cast(NDArray, alpha) check_alpha_and_n_samples(alpha_np, len(self.conformity_scores_)) - if (self.method in ["enbpi", "naive", "base"]) or ( + if (self.method in ["base", "enbpi", "minmax", "naive"]) or ( self.cv == "prefit" ): betas_0 = self._beta_optimize( alpha=alpha_np, - lower_bounds=self.conformity_scores_, - upper_bounds=self.conformity_scores_, + lower_bounds=self.conformity_scores_.reshape(1, -1), + upper_bounds=self.conformity_scores_.reshape(1, -1), beta_optimize=beta_optimize, ) lower_quantiles = np.nanquantile( @@ -282,29 +282,23 @@ def predict( if (self.method in ["naive", "base"]) or (self.cv == "prefit"): y_pred_low = y_pred[:, np.newaxis] + lower_quantiles y_pred_up = y_pred[:, np.newaxis] + higher_quantiles - else: # method == "enbpi" - # Correspond to "Conformal prediction for dynamic time - # series". - # Its PIs are closed to the oracle's ones if beta_optimized - # is True. + else: y_pred_multi = self._pred_multi(X) - pred = aggregate_all(self.agg_function, y_pred_multi) - y_pred_low = np.column_stack( - [ - pred + lower_quantiles[k] - for k, _ in enumerate(alpha_np) - ] - ) - y_pred_up = np.column_stack( - [ - pred + higher_quantiles[k] - for k, _ in enumerate(alpha_np) - ] - ) - if self.method == "minmax": - lower_bounds = np.min(y_pred_multi, axis=1, keepdims=True) - upper_bounds = np.max(y_pred_multi, axis=1, keepdims=True) + if self.method == "enbpi": + # Correspond to "Conformal prediction for dynamic time + # series". Its PIs are closed to the oracle's ones if + # beta_optimized is True. + pred = aggregate_all(self.agg_function, y_pred_multi) + lower_bounds, upper_bounds = pred, pred + else: # self.method == "minmax": + lower_bounds = np.min( + y_pred_multi, axis=1, keepdims=True + ) + upper_bounds = np.max( + y_pred_multi, axis=1, keepdims=True + ) + y_pred_low = np.column_stack( [ lower_bounds + lower_quantiles[k] @@ -317,14 +311,17 @@ def predict( for k, _ in enumerate(alpha_np) ] ) - elif self.method == "plus": - # This version of predict corresponds to "Predictive Inference - # Is Free with the Jackknife+-after-Bootstrap.". + + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) + + else: # self.method == "plus": + # This version of predict corresponds to "Predictive + # Inference is Free with the Jackknife+-after-Bootstrap.". # Its PIs are wider. It does not coorespond to "Conformal # prediction for dynamic time series". It is a try. It is - # slower because the betas (width optimization parameters of - # the PIs) are optimized for every point. - + # slower because the betas (width optimization parameters + # of the PIs) are optimized for every point y_pred_multi = self._pred_multi(X) y_pred_low = np.empty((len(y_pred), len(alpha)), dtype=float) y_pred_up = np.empty((len(y_pred), len(alpha)), dtype=float) @@ -357,11 +354,12 @@ def predict( axis=0, method="higher", ) # type: ignore - y_pred_low[:, ind_alpha] = lower_quantiles - y_pred_up[:, ind_alpha] = upper_quantiles + y_pred_low[:, ind_alpha] = lower_quantiles + y_pred_up[:, ind_alpha] = upper_quantiles + + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) - if ensemble: - y_pred = aggregate_all(self.agg_function, y_pred_multi) return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) def root_predict( diff --git a/mapie/utils.py b/mapie/utils.py index d39710def..39d41a29f 100644 --- a/mapie/utils.py +++ b/mapie/utils.py @@ -3,7 +3,6 @@ from typing import Any, Iterable, Optional, Tuple, Union, cast import numpy as np -import numpy.ma as ma from sklearn.base import ClassifierMixin, RegressorMixin from sklearn.model_selection import BaseCrossValidator, KFold, LeaveOneOut from sklearn.utils.validation import _check_sample_weight, _num_features From 1c1e8b06679d5109c64297181cc8055a0c2409d6 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Thu, 14 Apr 2022 12:14:36 +0200 Subject: [PATCH 18/32] [CORRECT] Correct compatibility with numpy versions --- mapie/_compatibility.py | 23 +++++++++++++++++++++++ mapie/regression.py | 7 ++++--- mapie/time_series_regression.py | 15 ++++++++------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/mapie/_compatibility.py b/mapie/_compatibility.py index 204831ace..380ae34b8 100644 --- a/mapie/_compatibility.py +++ b/mapie/_compatibility.py @@ -26,8 +26,31 @@ def np_quantile_version_above_122( return np.quantile(a, q, method=method, **kwargs) # type: ignore +def np_nanquantile_version_below_122( + a: ArrayLike, + q: ArrayLike, + method: str = "linear", + **kwargs: Any +) -> NDArray: + """Wrapper of np.quantile function for numpy version < 1.22.""" + return np.nanquantile(a, q, interpolation=method, **kwargs) # type: ignore + + +def np_nanquantile_version_above_122( + a: ArrayLike, + q: ArrayLike, + method: str = "linear", + **kwargs: Any +) -> NDArray: + """Wrapper of np.quantile function for numpy version >= 1.22.""" + return np.nanquantile(a, q, method=method, **kwargs) # type: ignore + + numpy_version = parse_version(np.__version__) if numpy_version < parse_version("1.22"): np_quantile = np_quantile_version_below_122 + np_nanquantile = np_nanquantile_version_below_122 + else: np_quantile = np_quantile_version_above_122 + np_nanquantile = np_nanquantile_version_above_122 diff --git a/mapie/regression.py b/mapie/regression.py index ae7a48adc..566dd919b 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -17,6 +17,7 @@ ) from ._typing import ArrayLike, NDArray +from ._compatibility import np_nanquantile from .aggregation_functions import aggregate_all, phi2D from .utils import ( check_cv, @@ -610,7 +611,7 @@ def predict( alpha_np = cast(NDArray, alpha) check_alpha_and_n_samples(alpha_np, n) if self.method in ["naive", "base"] or self.cv == "prefit": - quantile = np.nanquantile( + quantile = np_nanquantile( self.conformity_scores_, 1 - alpha_np, method="higher" ) y_pred_low = y_pred[:, np.newaxis] - quantile @@ -645,7 +646,7 @@ def predict( y_pred_low = np.column_stack( [ - np.nanquantile( + np_nanquantile( lower_bounds, _alpha, axis=1, @@ -657,7 +658,7 @@ def predict( y_pred_up = np.column_stack( [ - np.nanquantile( + np_nanquantile( upper_bounds, 1 - _alpha, axis=1, diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 27f3b18b1..81509a7c1 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -8,9 +8,10 @@ from sklearn.utils import check_array from sklearn.utils.validation import check_is_fitted +from ._compatibility import np_nanquantile +from ._typing import ArrayLike, NDArray from .aggregation_functions import aggregate_all from .regression import MapieRegressor -from ._typing import ArrayLike, NDArray from .utils import ( check_alpha, check_alpha_and_n_samples, @@ -179,13 +180,13 @@ def _beta_optimize( num=len(lower_bounds), endpoint=True, ) - one_alpha_beta = np.nanquantile( + one_alpha_beta = np_nanquantile( upper_bounds, 1 - _alpha + betas, axis=1, method="higher", ) # type: ignore - beta = np.nanquantile( + beta = np_nanquantile( lower_bounds, betas, axis=1, @@ -266,13 +267,13 @@ def predict( upper_bounds=self.conformity_scores_.reshape(1, -1), beta_optimize=beta_optimize, ) - lower_quantiles = np.nanquantile( + lower_quantiles = np_nanquantile( self.conformity_scores_, betas_0[:, 0], axis=0, method="lower", ).T # type: ignore - higher_quantiles = np.nanquantile( + higher_quantiles = np_nanquantile( self.conformity_scores_, 1 - alpha_np + betas_0[:, 0], axis=0, @@ -341,14 +342,14 @@ def predict( upper_quantiles = np.empty((betas_0.shape[1],)) for ind_beta_0, beta_0 in enumerate(betas_0[ind_alpha, :]): - lower_quantiles[ind_beta_0] = np.nanquantile( + lower_quantiles[ind_beta_0] = np_nanquantile( lower_bounds[ind_beta_0, :], beta_0, axis=0, method="lower", ) # type: ignore - upper_quantiles[ind_beta_0] = np.nanquantile( + upper_quantiles[ind_beta_0] = np_nanquantile( upper_bounds[ind_beta_0, :], 1 - _alpha + beta_0, axis=0, From cf230349a54c68fa2e81973b60faf7dcbf395cec Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Thu, 14 Apr 2022 14:45:22 +0200 Subject: [PATCH 19/32] [CORRECT] Documentation --- examples/regression/plot_timeseries_enbpi.py | 160 ++----------------- mapie/time_series_regression.py | 10 +- 2 files changed, 17 insertions(+), 153 deletions(-) diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/plot_timeseries_enbpi.py index 05ca7350b..194b5c341 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/plot_timeseries_enbpi.py @@ -124,39 +124,6 @@ y_pis_pfit_enbpi[:, 1, 0] - y_pis_pfit_enbpi[:, 0, 0] ).mean() -print("EnbPI with partial_fit, NO width optimization") -mapie_enpbi = mapie_enpbi.fit(X_train, y_train) -y_pred_pfit_enbpi_no_opt = np.zeros(y_pred_npfit_enbpi.shape) -y_pis_pfit_enbpi_no_opt = np.zeros(y_pis_npfit_enbpi.shape) -( - y_pred_pfit_enbpi_no_opt[:gap], - y_pis_pfit_enbpi_no_opt[:gap, :, :], -) = mapie_enpbi.predict( - X_test.iloc[:gap, :], alpha=alpha, ensemble=True, beta_optimize=False -) - -for step in range(gap, len(X_test), gap): - mapie_enpbi.partial_fit( - X_test.iloc[(step - gap):step, :], - y_test.iloc[(step - gap):step], - ) - ( - y_pred_pfit_enbpi_no_opt[step:step + gap], - y_pis_pfit_enbpi_no_opt[step:step + gap, :, :], - ) = mapie_enpbi.predict( - X_test.iloc[step:step + gap, :], - alpha=alpha, - ensemble=True, - beta_optimize=False, - ) -coverage_pfit_enbpi_no_opt = regression_coverage_score( - y_test, y_pis_pfit_enbpi_no_opt[:, 0, 0], y_pis_pfit_enbpi_no_opt[:, 1, 0] -) -width_pfit_enbpi_no_opt = ( - y_pis_pfit_enbpi_no_opt[:, 1, 0] - y_pis_pfit_enbpi_no_opt[:, 0, 0] -).mean() - - print("Plus, with partial_fit, width optimization") mapie_plus = mapie_plus.fit(X_train, y_train) y_pred_pfit_plus = np.zeros(y_pred_npfit_enbpi.shape) @@ -186,61 +153,11 @@ ) width_pfit_plus = (y_pis_pfit_plus[:, 1, 0] - y_pis_pfit_plus[:, 0, 0]).mean() -print("Plus, with partial_fit, NO width optimization") -mapie_plus = mapie_plus.fit(X_train, y_train) -y_pred_pfit_plus_no_opt = np.zeros(y_pred_npfit_enbpi.shape) -y_pis_pfit_plus_no_opt = np.zeros(y_pis_npfit_enbpi.shape) -( - y_pred_pfit_plus_no_opt[:gap], - y_pis_pfit_plus_no_opt[:gap, :, :], -) = mapie_plus.predict( - X_test.iloc[:gap, :], - alpha=alpha, - beta_optimize=False, -) -for step in range(gap, len(X_test), gap): - mapie_plus.partial_fit( - X_test.iloc[step - gap:step, :], - y_test.iloc[step - gap: step], - ) - ( - y_pred_pfit_plus_no_opt[step:step + gap], - y_pis_pfit_plus_no_opt[step:step + gap, :, :], - ) = mapie_plus.predict( - X_test.iloc[step:step + gap, :], - alpha=alpha, - ensemble=True, - beta_optimize=False, - ) - -coverage_pfit_plus_no_opt = regression_coverage_score( - y_test, y_pis_pfit_plus_no_opt[:, 0, 0], y_pis_pfit_plus_no_opt[:, 1, 0] -) -width_pfit_plus_no_opt = ( - y_pis_pfit_plus_no_opt[:, 1, 0] - y_pis_pfit_plus_no_opt[:, 0, 0] -).mean() - -print("Plus, with partial_fit, MapieRegressor_Like") +print("Plus, with NO partial_fit, MapieRegressor_Like no") mapie_plus = mapie_plus.fit(X_train, y_train) -y_pred_pfit_MR = np.zeros(y_pred_npfit_enbpi.shape) -y_pis_pfit_MR = np.zeros(y_pis_npfit_enbpi.shape) -y_pred_pfit_MR[:gap], y_pis_pfit_MR[:gap, :, :] = mapie_plus.root_predict( - X_test.iloc[:gap, :], alpha=alpha +y_pred_pfit_MR, y_pis_pfit_MR = mapie_enpbi.predict( + X_test, alpha=alpha, ensemble=True ) -for step in range(gap, len(X_test), gap): - mapie_plus.partial_fit( - X_test.iloc[step - gap:step, :], - y_test.iloc[step - gap:step], - ) - ( - y_pred_pfit_MR[step:step + gap], - y_pis_pfit_MR[step:step + gap, :, :], - ) = mapie_plus.root_predict( - X_test.iloc[step:step + gap, :], - alpha=alpha, - ensemble=True, - ) - coverage_pfit_MR = regression_coverage_score( y_test, y_pis_pfit_MR[:, 0, 0], y_pis_pfit_MR[:, 1, 0] ) @@ -257,21 +174,11 @@ "\nEnbPI with partial_fit:" f"{coverage_pfit_enbpi:.3f}, {width_pfit_enbpi:.3f}" ) -print( - "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nEnbPI with partial_fit, no with optimization:" - f"{coverage_pfit_enbpi_no_opt:.3f}, {width_pfit_enbpi_no_opt:.3f}" -) print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " "\nPlus, with partial_fit:" f"{coverage_pfit_plus:.3f}, {width_pfit_plus:.3f}" ) -print( - "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nPlus, with partial_fit. no width optimization:" - f"{coverage_pfit_plus_no_opt:.3f}, {width_pfit_plus_no_opt:.3f}" -) print( "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " "\nMR_Like, with partial_fit:" @@ -279,11 +186,11 @@ ) # Plot estimated prediction intervals on test set -fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots( - nrows=2, ncols=3, figsize=(30, 25), sharey="row", sharex="col" +fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots( + nrows=2, ncols=2, figsize=(30, 25), sharey="row", sharex="col" ) -for ax in [ax1, ax2, ax3, ax4, ax5, ax6]: +for ax in [ax1, ax2, ax3, ax4]: ax.set_ylabel("Hourly demand (GW)") ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") @@ -320,35 +227,15 @@ f"Coverage:{coverage_pfit_enbpi:.3f} Width:{width_pfit_enbpi:.3f}" ) -ax3.plot( - demand_test.index, - y_pred_pfit_enbpi_no_opt, - lw=2, - c="C2", - label="Predictions", -) -ax3.fill_between( - demand_test.index, - y_pis_pfit_enbpi_no_opt[:, 0, 0], - y_pis_pfit_enbpi_no_opt[:, 1, 0], - color="C2", - alpha=0.2, - label="MapieTimeSeriesRegressor PIs", -) -ax3.set_title( - "EnbPI with partial_fit. No width optimization\n" - f"Coverage:{coverage_pfit_enbpi_no_opt:.3f}" - f"Width:{width_pfit_enbpi_no_opt:.3f}" -) -ax4.plot( +ax3.plot( demand_test.index, y_pred_pfit_plus, lw=2, c="C2", label="Predictions", ) -ax4.fill_between( +ax3.fill_between( demand_test.index, y_pis_pfit_plus[:, 0, 0], y_pis_pfit_plus[:, 1, 0], @@ -356,36 +243,13 @@ alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax4.set_title( +ax3.set_title( "Plus, with partial_fit.\n" f"Coverage:{coverage_pfit_plus:.3f}" f"Width:{width_pfit_plus:.3f}" ) - -ax5.plot( - demand_test.index, - y_pred_pfit_plus_no_opt, - lw=2, - c="C2", - label="Predictions", -) -ax5.fill_between( - demand_test.index, - y_pis_pfit_plus_no_opt[:, 0, 0], - y_pis_pfit_plus_no_opt[:, 1, 0], - color="C2", - alpha=0.2, - label="MapieTimeSeriesRegressor PIs", -) -ax5.set_title( - "Plus, with partial_fit no width optimization\n" - f"Coverage:{coverage_pfit_plus_no_opt:.3f}" - f"Width:{width_pfit_plus_no_opt:.3f}" -) - - -ax6.plot(demand_test.index, y_pred_pfit_MR, lw=2, c="C2", label="Predictions") -ax6.fill_between( +ax4.plot(demand_test.index, y_pred_pfit_MR, lw=2, c="C2", label="Predictions") +ax4.fill_between( demand_test.index, y_pis_pfit_MR[:, 0, 0], y_pis_pfit_MR[:, 1, 0], @@ -393,7 +257,7 @@ alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) -ax6.set_title( +ax4.set_title( "MapieRegressor Like, with partial_fit\n" f"Coverage:{coverage_pfit_MR:.3f} Width:{width_pfit_MR:.3f}" ) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 81509a7c1..f272b7ec0 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -120,11 +120,11 @@ def partial_fit( if len(X) > len(self.conformity_scores_): raise ValueError("You try to update more residuals than tere are!") new_conformity_scores_ = self._relative_conformity_scores(X, y) - self.conformity_scores_ = np.concatenate( - [ - self.conformity_scores_[-len(new_conformity_scores_):], - new_conformity_scores_, - ] + self.conformity_scores_ = np.roll( + self.conformity_scores_, -len(new_conformity_scores_) + ) + self.conformity_scores_[-len(new_conformity_scores_):] = ( + new_conformity_scores_ ) self.k_[:, -len(new_conformity_scores_)] = 1.0 return self From 76eb321622707c1060a23ea8fc1b0814c4c1b60f Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Fri, 6 May 2022 12:29:53 +0200 Subject: [PATCH 20/32] prune enbpi commit --- .../plot_timeseries_enbpi.py | 32 ++- .../regression/plot_timeseries_enbpi_train.py | 81 ------- mapie/regression.py | 2 +- mapie/subsample.py | 4 +- mapie/tests/test_time_series_regression.py | 117 ++-------- mapie/time_series_regression.py | 203 ++++++------------ 6 files changed, 110 insertions(+), 329 deletions(-) rename examples/regression/{ => 2-advanced-analysis}/plot_timeseries_enbpi.py (89%) delete mode 100644 examples/regression/plot_timeseries_enbpi_train.py diff --git a/examples/regression/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py similarity index 89% rename from examples/regression/plot_timeseries_enbpi.py rename to examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 194b5c341..0cb4da004 100644 --- a/examples/regression/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -28,7 +28,9 @@ import numpy as np import pandas as pd from matplotlib import pylab as plt +from scipy.stats import randint from sklearn.ensemble import RandomForestRegressor +from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit from mapie.metrics import regression_coverage_score from mapie.subsample import BlockBootstrap @@ -64,8 +66,31 @@ X_test = demand_test.loc[:, features] y_test = demand_test["Demand"] -# Model: Random Forest previously optimized with a cross-validation -model = RandomForestRegressor(max_depth=10, n_estimators=50, random_state=59) +model_params_fit_not_done = False +if model_params_fit_not_done: + # CV parameter search + n_iter = 100 + n_splits = 5 + tscv = TimeSeriesSplit(n_splits=n_splits) + random_state = 59 + rf_model = RandomForestRegressor(random_state=random_state) + rf_params = {"max_depth": randint(2, 30), "n_estimators": randint(10, 100)} + cv_obj = RandomizedSearchCV( + rf_model, + param_distributions=rf_params, + n_iter=n_iter, + cv=tscv, + scoring="neg_root_mean_squared_error", + random_state=random_state, + verbose=0, + n_jobs=-1, + ) + cv_obj.fit(X_train, y_train) + model = cv_obj.best_estimator_ +else: + # Model: Random Forest previously optimized with a cross-validation + model = RandomForestRegressor( + max_depth=10, n_estimators=50, random_state=59) # Estimate prediction intervals on test set with best estimator alpha = 0.05 @@ -99,6 +124,7 @@ y_pred_pfit_enbpi = np.zeros(y_pred_npfit_enbpi.shape) y_pis_pfit_enbpi = np.zeros(y_pis_npfit_enbpi.shape) + y_pred_pfit_enbpi[:gap], y_pis_pfit_enbpi[:gap, :, :] = mapie_enpbi.predict( X_test.iloc[:gap, :], alpha=alpha, ensemble=True, beta_optimize=True ) @@ -155,7 +181,7 @@ print("Plus, with NO partial_fit, MapieRegressor_Like no") mapie_plus = mapie_plus.fit(X_train, y_train) -y_pred_pfit_MR, y_pis_pfit_MR = mapie_enpbi.predict( +y_pred_pfit_MR, y_pis_pfit_MR = mapie_plus.predict( X_test, alpha=alpha, ensemble=True ) coverage_pfit_MR = regression_coverage_score( diff --git a/examples/regression/plot_timeseries_enbpi_train.py b/examples/regression/plot_timeseries_enbpi_train.py deleted file mode 100644 index 2200d0635..000000000 --- a/examples/regression/plot_timeseries_enbpi_train.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -================================================================== -Estimating prediction intervals of time series forecast with EnbPI -================================================================== -This example uses -:class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate -prediction intervals associated with time series forecast. It follows [6] and -an alternative expermimental implemetation inspired from [2] - -We use here the Victoria electricity demand dataset used in the book -"Forecasting: Principles and Practice" by R. J. Hyndman and G. Athanasopoulos. -The electricity demand features daily and weekly seasonalities and is impacted -by the temperature, considered here as a exogeneous variable. - -A Random Forest model is aloready fitted on data. The hyper-parameters are -optimized with a :class:`sklearn.model_selection.RandomizedSearchCV` using a -sequential :class:`sklearn.model_selection.TimeSeriesSplit` cross validation, -in which the training set is prior to the validation set. -The best model is then feeded into -:class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the -associated prediction intervals. We compare two approaches: one with no -`partial_fit` call and one with `partial_fit` every step. -""" -import warnings - -import numpy as np -import pandas as pd -from scipy.stats import randint -from sklearn.ensemble import RandomForestRegressor -from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit - -warnings.simplefilter("ignore") - -# Load input data and feature engineering -demand_df = pd.read_csv( - "../data/demand_temperature.csv", parse_dates=True, index_col=0 -) - -demand_df["Date"] = pd.to_datetime(demand_df.index) -demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") -demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") -demand_df["Hour"] = demand_df.index.hour - -n_lags = 5 -for hour in range(1, n_lags): - demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) - -# Train/validation/test split -num_test_steps = 24 * 7 -demand_train = demand_df.iloc[:-num_test_steps, :].copy() -demand_test = demand_df.iloc[-num_test_steps:, :].copy() -features = ["Weekofyear", "Weekday", "Hour", "Temperature"] + [ - f"Lag_{hour}" for hour in range(1, n_lags) -] - -X_train = demand_train.loc[ - ~np.any(demand_train[features].isnull(), axis=1), features -] -y_train = demand_train.loc[X_train.index, "Demand"] -X_test = demand_test.loc[:, features] -y_test = demand_test["Demand"] - -# CV parameter search -n_iter = 100 -n_splits = 5 -tscv = TimeSeriesSplit(n_splits=n_splits) -random_state = 59 -rf_model = RandomForestRegressor(random_state=random_state) -rf_params = {"max_depth": randint(2, 30), "n_estimators": randint(10, 100)} -cv_obj = RandomizedSearchCV( - rf_model, - param_distributions=rf_params, - n_iter=n_iter, - cv=tscv, - scoring="neg_root_mean_squared_error", - random_state=random_state, - verbose=0, - n_jobs=-1, -) -cv_obj.fit(X_train, y_train) -print(cv_obj.best_estimator_) diff --git a/mapie/regression.py b/mapie/regression.py index 566dd919b..7670c5ebe 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -224,7 +224,7 @@ def _check_parameters(self) -> None: if self.method not in self.valid_methods_: raise ValueError( "Invalid method. " - "Allowed values are 'naive', 'base', 'plus' and 'minmax'." + f"Allowed values are {self.valid_methods_}." ) check_n_jobs(self.n_jobs) diff --git a/mapie/subsample.py b/mapie/subsample.py index 049ac50f6..43f86035f 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -26,9 +26,9 @@ class Subsample(BaseCrossValidator): Number of samples in each resampling. By default ``None``, the size of the training set. replace: bool - Whether to replace samples in resamplings or not. + Whether to replace samples in resamplings or not. By default ``True``. random_state: Optional - int or RandomState instance. + int or RandomState instance. . By default ``None`` Examples diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 1016f8d3f..78636df39 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -20,7 +20,7 @@ y_toy = (5.0 + 2.0 * X_toy ** 1.1).flatten() X, y = make_regression(n_samples=500, n_features=10, noise=1.0, random_state=1) k = np.ones(shape=(5, X.shape[1])) -METHODS = ["base", "enbpi", "minmax", "naive", "plus"] +METHODS = ["enbpi"] Params = TypedDict( "Params", @@ -31,35 +31,11 @@ }, ) STRATEGIES = { - "naive": Params(method="naive", agg_function="median", cv=None), - "jackknife": Params(method="base", agg_function="mean", cv=-1), - "jackknife_plus": Params(method="plus", agg_function="mean", cv=-1), - "jackknife_minmax": Params(method="minmax", agg_function="mean", cv=-1), - "cv": Params( - method="base", - agg_function="mean", - cv=KFold(n_splits=3, shuffle=True, random_state=1), - ), - "cv_plus": Params( - method="plus", - agg_function="mean", - cv=KFold(n_splits=3, shuffle=True, random_state=1), - ), - "cv_minmax": Params( - method="minmax", - agg_function="mean", - cv=KFold(n_splits=3, shuffle=True, random_state=1), - ), - "jackknife_enbpi_ab_wopt": Params( + "jackknife_enbpi_mean_ab_wopt": Params( method="enbpi", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), ), - "jackknife_minmax_ab": Params( - method="minmax", - agg_function="mean", - cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), - ), "jackknife_enbpi_median_ab_wopt": Params( method="enbpi", agg_function="median", @@ -69,7 +45,7 @@ random_state=1, ), ), - "jackknife_enbpi_ab": Params( + "jackknife_enbpi_mean_ab": Params( method="enbpi", agg_function="mean", cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), @@ -83,72 +59,20 @@ random_state=1, ), ), - "jackknife_plus_ab_MR": Params( - method="plus", - agg_function="mean", - cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), - ), - "jackknife_minmax_ab_MR": Params( - method="minmax", - agg_function="mean", - cv=BlockBootstrap(n_resamplings=30, n_blocks=5, random_state=1), - ), - "jackknife_plus_ab_median_MR": Params( - method="plus", - agg_function="median", - cv=BlockBootstrap( - n_resamplings=30, - n_blocks=5, - random_state=1, - ), - ), } WIDTHS = { - "naive": 3.83, - "jackknife": 3.83, - "jackknife_plus": 3.82, - "jackknife_minmax": 3.90, - "cv": 3.83, - "cv_plus": 3.87, - "cv_minmax": 4.03, - "prefit": 4.79, - "cv_plus_median": 3.90, - "jackknife_enbpi_ab_wopt": 3.76, - "jackknife_minmax_ab": 3.96, + "jackknife_enbpi_mean_ab_wopt": 3.76, "jackknife_enbpi_median_ab_wopt": 3.76, - "jackknife_enbpi_ab": 3.76, - "jackknife_minmax_ab": 3.96, + "jackknife_enbpi_mean_ab": 3.76, "jackknife_enbpi_median_ab": 3.76, - "jackknife_plus_ab": 3.76, - "jackknife_plus_median_ab": 3.83, - "jackknife_plus_ab_MR": 3.82, - "jackknife_minmax_ab_MR": 3.96, - "jackknife_plus_ab_median_MR": 3.83, } COVERAGES = { - "naive": 0.952, - "jackknife": 0.952, - "jackknife_plus": 0.94, - "jackknife_minmax": 0.952, - "cv": 0.958, - "cv_plus": 0.956, - "cv_minmax": 0.966, - "prefit": 0.98, - "cv_plus_median": 0.954, - "jackknife_enbpi_ab_wopt": 0.952, - "jackknife_minmax_ab": 0.960, + "jackknife_enbpi_mean_ab_wopt": 0.952, "jackknife_enbpi_median_ab_wopt": 0.946, - "jackknife_enbpi_ab": 0.952, - "jackknife_minmax_ab": 0.960, + "jackknife_enbpi_mean_ab": 0.952, "jackknife_enbpi_median_ab": 0.946, - "jackknife_plus_ab_plus": 0.92, - "jackknife_minmax_ab_plus": 0.940, - "jackknife_plus_median_ab_plus": 0.94, - "jackknife_plus_ab_MR": 0.954, - "jackknife_minmax_ab_MR": 0.958, - "jackknife_plus_ab_median_MR": 0.954, } @@ -262,7 +186,7 @@ def test_results_with_constant_sample_weights(strategy: str) -> None: np.testing.assert_allclose(y_pis1, y_pis2) -@pytest.mark.parametrize("method", ["plus", "minmax", "enbpi"]) +@pytest.mark.parametrize("method", ["enbpi"]) @pytest.mark.parametrize("cv", [-1, 2, 3, 5]) @pytest.mark.parametrize("agg_function", ["mean", "median"]) @pytest.mark.parametrize("alpha", [0.05, 0.1, 0.2]) @@ -292,6 +216,7 @@ def test_linear_regression_results(strategy: str) -> None: a multivariate linear regression problem with fixed random state. """ + mapie_ts = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) mapie_ts.fit(X, y) if "opt" in strategy: @@ -302,29 +227,11 @@ def test_linear_regression_results(strategy: str) -> None: y_pred_low, y_pred_up = y_pis[:, 0, 0], y_pis[:, 1, 0] width_mean = (y_pred_up - y_pred_low).mean() - if mapie_ts.method == "plus": - coverage = regression_coverage_score(y, y_pred_low, y_pred_up) - else: - coverage = regression_coverage_score(y, y_pred_low, y_pred_up) + coverage = regression_coverage_score(y, y_pred_low, y_pred_up) np.testing.assert_allclose(width_mean, WIDTHS[strategy], rtol=1e-2) np.testing.assert_allclose(coverage, COVERAGES[strategy], rtol=1e-2) -def test_results_prefit_ignore_method() -> None: - """Test that method is ignored when ``cv="prefit"``.""" - estimator = LinearRegression().fit(X, y) - all_y_pis: List[NDArray] = [] - for method in METHODS: - mapie_ts_reg = MapieTimeSeriesRegressor( - estimator=estimator, cv="prefit", method=method - ) - mapie_ts_reg.fit(X, y) - _, y_pis = mapie_ts_reg.predict(X, alpha=0.1) - all_y_pis.append(y_pis) - for y_pis1, y_pis2 in combinations(all_y_pis, 2): - np.testing.assert_allclose(y_pis1, y_pis2) - - def test_results_prefit_naive() -> None: """ Test that prefit, fit and predict on the same dataset @@ -456,9 +363,9 @@ def test_MapieTimeSeriesRegressor_partial_fit_two_big() -> None: def test_MapieTimeSeriesRegressor_beta_optimize_eeror() -> None: - """Test ``partial_fit`` raised error.""" + """Test ``beta_optimize`` raised error.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1) with pytest.raises(ValueError, match=r".*Lower and upper bounds arrays*"): mapie_ts_reg._beta_optimize( - alpha=0.1, upper_bounds=X, lower_bounds=X_toy, beta_optimize=True + alpha=0.1, upper_bounds=X, lower_bounds=X_toy ) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index f272b7ec0..4ffc596fc 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -22,11 +22,12 @@ class MapieTimeSeriesRegressor(MapieRegressor): """ Prediction intervals with out-of-fold residuals for time series. - This class implements the EnbPI strategy and some variants for estimating - prediction intervals on single-output time series. + This class implements the EnbPI strategy for estimating + prediction intervals on single-output time series. The only valid + ``method`` is 'enbpi'. Actually, EnbPI only corresponds to ``MapieTimeSeriesRegressor`` if the - ``cv`` argument is of type ``BlockBootstrap`` and ``method`` is "enbpi". + ``cv`` argument is of type ``BlockBootstrap``. References ---------- @@ -46,7 +47,7 @@ def __init__( ) -> None: super().__init__(estimator, method, cv, n_jobs, agg_function, verbose) self.cv_need_agg_function.append("BlockBootstrap") - self.valid_methods_.append("enbpi") + self.valid_methods_ = ["enbpi"] self.plus_like_method.append("enbpi") def _relative_conformity_scores( @@ -69,7 +70,7 @@ def _relative_conformity_scores( ------- The conformity scores corresponding to the input data set. """ - y_pred, _ = self.root_predict(X, alpha=0.5, ensemble=True) + y_pred, _ = super().predict(X, alpha=0.5, ensemble=True) return np.asarray(y) - np.asarray(y_pred) def fit( @@ -114,11 +115,18 @@ def partial_fit( ------- MapieTimeSeriesRegressor The model itself. + + Raises + ------ + ValueError + If the lenght of y is greater than the lenght of the training set. """ X = cast(NDArray, X) y = cast(NDArray, y) if len(X) > len(self.conformity_scores_): - raise ValueError("You try to update more residuals than tere are!") + raise ValueError( + "You try to update more residuals than there are!" + ) new_conformity_scores_ = self._relative_conformity_scores(X, y) self.conformity_scores_ = np.roll( self.conformity_scores_, -len(new_conformity_scores_) @@ -126,7 +134,6 @@ def partial_fit( self.conformity_scores_[-len(new_conformity_scores_):] = ( new_conformity_scores_ ) - self.k_[:, -len(new_conformity_scores_)] = 1.0 return self def _beta_optimize( @@ -134,7 +141,6 @@ def _beta_optimize( alpha: Union[float, NDArray], upper_bounds: NDArray, lower_bounds: NDArray, - beta_optimize: bool = False, ) -> NDArray: """ ``_beta_optimize`` offers to minimize the width of the PIs, for a given @@ -142,21 +148,23 @@ def _beta_optimize( Parameters ---------- - alpha: Optional[NDArray] - The quantiles to compute. - upper_bounds: NDArray - The array of upper values. - lower_bounds: NDArray - The array of lower values. - optimize: bool - Whether to optimize or not. If ``False``, betas are the half of - alphas. + alpha: Union[float, NDArray] + The quantiles to compute. + upper_bounds: NDArray + The array of upper values. + lower_bounds: NDArray + The array of lower values. Returns ------- NDArray Array of betas minimizing the differences ``(1-alpa+beta)-quantile - beta-quantile``. + + Raises + ------ + ValueError + If lower and upper bounds arrays don't have the same shape. """ if lower_bounds.shape != upper_bounds.shape: raise ValueError( @@ -168,10 +176,6 @@ def _beta_optimize( fill_value=np.nan, dtype=float, ) - if not beta_optimize: - for ind_alpha, _alpha in enumerate(alpha): - betas_0[ind_alpha, :] = _alpha / 2.0 - return betas_0 for ind_alpha, _alpha in enumerate(alpha): betas = np.linspace( @@ -231,12 +235,7 @@ def predict( beta_optimize: bool = True, ) -> Union[NDArray, Tuple[NDArray, NDArray]]: """ - Correspond to the ``MapieRegressor``'s one with the - method ``'plus'``. In case ``method`` is ``'enbpi'``, predictions - correspond to 'Conformal prediction for dynamic time-series'. The - method ``'plus'`` is slower because of PI-wise optimization. However, - you can choose not to optimize the width of the PIs by setting - ``beta_optimize`` to ``False``. + Correspond to 'Conformal prediction for dynamic time-series'. Parameters ---------- @@ -258,126 +257,56 @@ def predict( alpha_np = cast(NDArray, alpha) check_alpha_and_n_samples(alpha_np, len(self.conformity_scores_)) - if (self.method in ["base", "enbpi", "minmax", "naive"]) or ( - self.cv == "prefit" - ): + if beta_optimize: betas_0 = self._beta_optimize( alpha=alpha_np, lower_bounds=self.conformity_scores_.reshape(1, -1), upper_bounds=self.conformity_scores_.reshape(1, -1), - beta_optimize=beta_optimize, ) - lower_quantiles = np_nanquantile( - self.conformity_scores_, - betas_0[:, 0], - axis=0, - method="lower", - ).T # type: ignore - higher_quantiles = np_nanquantile( - self.conformity_scores_, - 1 - alpha_np + betas_0[:, 0], - axis=0, - method="higher", - ).T # type: ignore - - if (self.method in ["naive", "base"]) or (self.cv == "prefit"): - y_pred_low = y_pred[:, np.newaxis] + lower_quantiles - y_pred_up = y_pred[:, np.newaxis] + higher_quantiles - else: - y_pred_multi = self._pred_multi(X) - - if self.method == "enbpi": - # Correspond to "Conformal prediction for dynamic time - # series". Its PIs are closed to the oracle's ones if - # beta_optimized is True. - pred = aggregate_all(self.agg_function, y_pred_multi) - lower_bounds, upper_bounds = pred, pred - else: # self.method == "minmax": - lower_bounds = np.min( - y_pred_multi, axis=1, keepdims=True - ) - upper_bounds = np.max( - y_pred_multi, axis=1, keepdims=True - ) - - y_pred_low = np.column_stack( - [ - lower_bounds + lower_quantiles[k] - for k, _ in enumerate(alpha_np) - ] - ) - y_pred_up = np.column_stack( - [ - upper_bounds + higher_quantiles[k] - for k, _ in enumerate(alpha_np) - ] - ) - - if ensemble: - y_pred = aggregate_all(self.agg_function, y_pred_multi) - - else: # self.method == "plus": - # This version of predict corresponds to "Predictive - # Inference is Free with the Jackknife+-after-Bootstrap.". - # Its PIs are wider. It does not coorespond to "Conformal - # prediction for dynamic time series". It is a try. It is - # slower because the betas (width optimization parameters - # of the PIs) are optimized for every point - y_pred_multi = self._pred_multi(X) - y_pred_low = np.empty((len(y_pred), len(alpha)), dtype=float) - y_pred_up = np.empty((len(y_pred), len(alpha)), dtype=float) + else: + betas_0 = np.full( + shape=(len(alpha), len(self.conformity_scores_)), + fill_value=np.nan, + dtype=float, + ) + for ind_alpha, _alpha in enumerate(alpha): + betas_0[ind_alpha, :] = _alpha / 2.0 - lower_bounds = y_pred_multi + self.conformity_scores_ - upper_bounds = y_pred_multi + self.conformity_scores_ + lower_quantiles = np_nanquantile( + self.conformity_scores_, + betas_0[:, 0], + axis=0, + method="lower", + ).T # type: ignore + higher_quantiles = np_nanquantile( + self.conformity_scores_, + 1 - alpha_np + betas_0[:, 0], + axis=0, + method="higher", + ).T # type: ignore - betas_0 = self._beta_optimize( - alpha=alpha_np, - lower_bounds=lower_bounds, - upper_bounds=upper_bounds, - beta_optimize=beta_optimize, + if self.cv == "prefit": + y_pred_low = y_pred[:, np.newaxis] + lower_quantiles + y_pred_up = y_pred[:, np.newaxis] + higher_quantiles + else: + y_pred_multi = self._pred_multi(X) + pred = aggregate_all(self.agg_function, y_pred_multi) + lower_bounds, upper_bounds = pred, pred + + y_pred_low = np.column_stack( + [ + lower_bounds + lower_quantiles[k] + for k, _ in enumerate(alpha_np) + ] + ) + y_pred_up = np.column_stack( + [ + upper_bounds + higher_quantiles[k] + for k, _ in enumerate(alpha_np) + ] ) - - for ind_alpha, _alpha in enumerate(alpha_np): - lower_quantiles = np.empty((betas_0.shape[1],)) - upper_quantiles = np.empty((betas_0.shape[1],)) - - for ind_beta_0, beta_0 in enumerate(betas_0[ind_alpha, :]): - lower_quantiles[ind_beta_0] = np_nanquantile( - lower_bounds[ind_beta_0, :], - beta_0, - axis=0, - method="lower", - ) # type: ignore - - upper_quantiles[ind_beta_0] = np_nanquantile( - upper_bounds[ind_beta_0, :], - 1 - _alpha + beta_0, - axis=0, - method="higher", - ) # type: ignore - y_pred_low[:, ind_alpha] = lower_quantiles - y_pred_up[:, ind_alpha] = upper_quantiles if ensemble: y_pred = aggregate_all(self.agg_function, y_pred_multi) return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) - - def root_predict( - self, - X: ArrayLike, - ensemble: bool = False, - alpha: Optional[Union[float, Iterable[float]]] = None, - ) -> Union[NDArray, Tuple[NDArray, NDArray]]: - """ - ``root_predict`` method correspond to the one of ``MapieRegressor``'s. - """ - conformity_scores_save = self.conformity_scores_.copy() - self.conformity_scores_ = np.abs(self.conformity_scores_) - if alpha is None: - y_pred = super().predict(X=X, ensemble=ensemble, alpha=alpha) - self.conformity_scores_ = conformity_scores_save - return y_pred - y_pred, y_pis = super().predict(X=X, ensemble=ensemble, alpha=alpha) - self.conformity_scores_ = conformity_scores_save - return y_pred, y_pis From 0017fd36dc9ded35313d3c73e8732b4e98dc015b Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Fri, 6 May 2022 12:48:45 +0200 Subject: [PATCH 21/32] all test pass after enbpi pruning --- mapie/tests/test_time_series_regression.py | 24 +++++----------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 78636df39..1f5648632 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -1,7 +1,6 @@ from __future__ import annotations -from itertools import combinations -from typing import Any, List, Optional, Tuple, Union +from typing import Any, Optional, Tuple, Union import numpy as np import pytest @@ -66,6 +65,8 @@ "jackknife_enbpi_median_ab_wopt": 3.76, "jackknife_enbpi_mean_ab": 3.76, "jackknife_enbpi_median_ab": 3.76, + "prefit": 4.79, + } COVERAGES = { @@ -73,6 +74,8 @@ "jackknife_enbpi_median_ab_wopt": 0.946, "jackknife_enbpi_mean_ab": 0.952, "jackknife_enbpi_median_ab": 0.946, + "prefit": 0.98, + } @@ -232,21 +235,6 @@ def test_linear_regression_results(strategy: str) -> None: np.testing.assert_allclose(coverage, COVERAGES[strategy], rtol=1e-2) -def test_results_prefit_naive() -> None: - """ - Test that prefit, fit and predict on the same dataset - is equivalent to the "naive" method. - """ - estimator = LinearRegression().fit(X, y) - mapie_ts_reg = MapieTimeSeriesRegressor(estimator=estimator, cv="prefit") - mapie_ts_reg.fit(X, y) - _, y_pis = mapie_ts_reg.predict(X, alpha=0.05) - width_mean = (y_pis[:, 1, 0] - y_pis[:, 0, 0]).mean() - coverage = regression_coverage_score(y, y_pis[:, 0, 0], y_pis[:, 1, 0]) - np.testing.assert_allclose(width_mean, WIDTHS["naive"], rtol=1e-2) - np.testing.assert_allclose(coverage, COVERAGES["naive"], rtol=1e-2) - - def test_results_prefit() -> None: """Test prefit results on a standard train/validation/test split.""" X_train_val, X_test, y_train_val, y_test = train_test_split( @@ -337,8 +325,6 @@ def test_MapieTimeSeriesRegressor_alpha_is_None() -> None: with pytest.raises(ValueError, match=r".*too many values to unpackt*"): y_pred, y_pis = mapie_ts_reg.predict(X_toy, alpha=None) - with pytest.raises(ValueError, match=r".*too many values to unpackt*"): - y_pred, y_pis = mapie_ts_reg.root_predict(X_toy, alpha=None) def test_MapieTimeSeriesRegressor_partial_fit_ensemble() -> None: From 57a07c2a56555f527cb6c94e57097dc9571d5a7c Mon Sep 17 00:00:00 2001 From: Vianney Taquet Date: Mon, 30 May 2022 14:17:25 +0200 Subject: [PATCH 22/32] Add notebook with TS changepoint --- mapie/time_series_regression.py | 2 + notebooks/regression/ts-changepoint.ipynb | 734 ++++++++++++++++++++++ notebooks/regression/ts-changepoint.md | 372 +++++++++++ 3 files changed, 1108 insertions(+) create mode 100644 notebooks/regression/ts-changepoint.ipynb create mode 100644 notebooks/regression/ts-changepoint.md diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 4ffc596fc..ae7955f42 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -284,6 +284,8 @@ def predict( axis=0, method="higher", ).T # type: ignore + self.lower_quantiles_ = lower_quantiles + self.higher_quantiles_ = higher_quantiles if self.cv == "prefit": y_pred_low = y_pred[:, np.newaxis] + lower_quantiles diff --git a/notebooks/regression/ts-changepoint.ipynb b/notebooks/regression/ts-changepoint.ipynb new file mode 100644 index 000000000..103b4aafd --- /dev/null +++ b/notebooks/regression/ts-changepoint.ipynb @@ -0,0 +1,734 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Estimating prediction intervals of time series forecast with EnbPI" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/scikit-learn-contrib/MAPIE/blob/add-ts-notebooks/notebooks/regression/ts-changepoint.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This example uses `mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate\n", + "prediction intervals associated with time series forecast. It follows Xu \\& Xie (2021).\n", + "We use here the Victoria electricity demand dataset used in the book\n", + "\"Forecasting: Principles and Practice\" by R. J. Hyndman and G. Athanasopoulos.\n", + "The electricity demand features daily and weekly seasonalities and is impacted\n", + "by the temperature, considered here as a exogeneous variable.\n", + "A Random Forest model is already fitted on data. The hyper-parameters are\n", + "optimized with a `sklearn.model_selection.RandomizedSearchCV` using a\n", + "sequential `sklearn.model_selection.TimeSeriesSplit` cross validation,\n", + "in which the training set is prior to the validation set.\n", + "The best model is then feeded into\n", + "`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the\n", + "associated prediction intervals. We compare four approaches: with or without\n", + "``partial_fit`` called at every step. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "install_mapie = False\n", + "if install_mapie:\n", + " !pip install \"git+https://github.com/scikit-learn-contrib/MAPIE.git@add-ts-notebooks\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "from matplotlib import pylab as plt\n", + "from scipy.stats import randint\n", + "from sklearn.ensemble import RandomForestRegressor\n", + "from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit\n", + "\n", + "from mapie.metrics import regression_coverage_score, regression_mean_width_score\n", + "from mapie.subsample import BlockBootstrap\n", + "from mapie.time_series_regression import MapieTimeSeriesRegressor\n", + "\n", + "%reload_ext autoreload\n", + "%autoreload 2\n", + "warnings.simplefilter(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Load input data and feature engineering" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "url_file = \"https://raw.githubusercontent.com/scikit-learn-contrib/MAPIE/master/examples/data/demand_temperature.csv\"\n", + "demand_df = pd.read_csv(\n", + " url_file, parse_dates=True, index_col=0\n", + ")\n", + "\n", + "demand_df[\"Date\"] = pd.to_datetime(demand_df.index)\n", + "demand_df[\"Weekofyear\"] = demand_df.Date.dt.isocalendar().week.astype(\"int64\")\n", + "demand_df[\"Weekday\"] = demand_df.Date.dt.isocalendar().day.astype(\"int64\")\n", + "demand_df[\"Hour\"] = demand_df.index.hour\n", + "n_lags = 5\n", + "for hour in range(1, n_lags):\n", + " demand_df[f\"Lag_{hour}\"] = demand_df[\"Demand\"].shift(hour)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Train/validation/test split" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "num_test_steps = 24 * 7\n", + "demand_train = demand_df.iloc[:-num_test_steps, :].copy()\n", + "demand_test = demand_df.iloc[-num_test_steps:, :].copy()\n", + "features = [\"Weekofyear\", \"Weekday\", \"Hour\", \"Temperature\"] \n", + "features += [f\"Lag_{hour}\" for hour in range(1, n_lags)]\n", + "\n", + "X_train = demand_train.loc[\n", + " ~np.any(demand_train[features].isnull(), axis=1), features\n", + "]\n", + "y_train = demand_train.loc[X_train.index, \"Demand\"]\n", + "X_test = demand_test.loc[:, features]\n", + "y_test = demand_test[\"Demand\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Hourly demand (GW)')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(16, 5))\n", + "plt.plot(y_train)\n", + "plt.plot(y_test)\n", + "plt.ylabel(\"Hourly demand (GW)\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Optimize the base estimator" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "model_params_fit_not_done = False\n", + "if model_params_fit_not_done:\n", + " # CV parameter search\n", + " n_iter = 100\n", + " n_splits = 5\n", + " tscv = TimeSeriesSplit(n_splits=n_splits)\n", + " random_state = 59\n", + " rf_model = RandomForestRegressor(random_state=random_state)\n", + " rf_params = {\"max_depth\": randint(2, 30), \"n_estimators\": randint(10, 100)}\n", + " cv_obj = RandomizedSearchCV(\n", + " rf_model,\n", + " param_distributions=rf_params,\n", + " n_iter=n_iter,\n", + " cv=tscv,\n", + " scoring=\"neg_root_mean_squared_error\",\n", + " random_state=random_state,\n", + " verbose=0,\n", + " n_jobs=-1,\n", + " )\n", + " cv_obj.fit(X_train, y_train)\n", + " model = cv_obj.best_estimator_\n", + "else:\n", + " # Model: Random Forest previously optimized with a cross-validation\n", + " model = RandomForestRegressor(\n", + " max_depth=10, n_estimators=50, random_state=59)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Estimate prediction intervals on the test set" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "alpha = 0.05\n", + "gap = 1\n", + "cv_mapiets = BlockBootstrap(\n", + " n_resamplings=100, length=48, overlapping=True, random_state=59\n", + ")\n", + "mapie_enbpi = MapieTimeSeriesRegressor(\n", + " model, method=\"enbpi\", cv=cv_mapiets, agg_function=\"mean\", n_jobs=-1\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Without partial fit" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EnbPI, with no partial_fit, width optimization\n" + ] + } + ], + "source": [ + "print(\"EnbPI, with no partial_fit, width optimization\")\n", + "mapie_enbpi = mapie_enbpi.fit(X_train, y_train)\n", + "y_pred_npfit, y_pis_npfit = mapie_enbpi.predict(\n", + " X_test, alpha=alpha, ensemble=True, beta_optimize=True\n", + ")\n", + "coverage_npfit = regression_coverage_score(\n", + " y_test, y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0]\n", + ")\n", + "width_npfit = regression_mean_width_score(\n", + " y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With partial fit" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EnbPI with partial_fit, width optimization\n" + ] + } + ], + "source": [ + "print(\"EnbPI with partial_fit, width optimization\")\n", + "mapie_enbpi = mapie_enbpi.fit(X_train, y_train)\n", + "\n", + "y_pred_pfit = np.zeros(y_pred_npfit.shape)\n", + "y_pis_pfit = np.zeros(y_pis_npfit.shape)\n", + "y_pred_pfit[:gap], y_pis_pfit[:gap, :, :] = mapie_enbpi.predict(\n", + " X_test.iloc[:gap, :], alpha=alpha, ensemble=True\n", + ")\n", + "for step in range(gap, len(X_test), gap):\n", + " mapie_enbpi.partial_fit(\n", + " X_test.iloc[(step - gap):step, :],\n", + " y_test.iloc[(step - gap):step],\n", + " )\n", + " (\n", + " y_pred_pfit[step:step + gap],\n", + " y_pis_pfit[step:step + gap, :, :],\n", + " ) = mapie_enbpi.predict(\n", + " X_test.iloc[step:(step + gap), :],\n", + " alpha=alpha,\n", + " ensemble=True\n", + " )\n", + "coverage_pfit = regression_coverage_score(\n", + " y_test, y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0]\n", + ")\n", + "width_pfit = regression_mean_width_score(\n", + " y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## V. Plot estimated prediction intervals on test set" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "y_preds = [y_pred_npfit, y_pred_pfit]\n", + "y_pis = [y_pis_npfit, y_pis_pfit]\n", + "coverages = [coverage_npfit, coverage_pfit]\n", + "widths = [width_npfit, width_pfit]" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_forecast(y_train, y_test, y_preds, y_pis, coverages, widths, plot_coverage=True):\n", + " fig, axs = plt.subplots(\n", + " nrows=2, ncols=1, figsize=(14, 8), sharey=\"row\", sharex=\"col\"\n", + " )\n", + " for i, (ax, w) in enumerate(zip(axs, [\"without\", \"with\"])):\n", + " ax.set_ylabel(\"Hourly demand (GW)\")\n", + " ax.plot(y_train[int(-len(y_test)/2):], lw=2, label=\"Training data\", c=\"C0\")\n", + " ax.plot(y_test, lw=2, label=\"Test data\", c=\"C1\")\n", + "\n", + " ax.plot(\n", + " y_test.index, y_preds[i], lw=2, c=\"C2\", label=\"Predictions\"\n", + " )\n", + " ax.fill_between(\n", + " y_test.index,\n", + " y_pis[i][:, 0, 0],\n", + " y_pis[i][:, 1, 0],\n", + " color=\"C2\",\n", + " alpha=0.2,\n", + " label=\"Prediction intervals\",\n", + " )\n", + " title = f\"EnbPI, {w} update of residuals. \"\n", + " if plot_coverage:\n", + " title += f\"Coverage:{coverages[i]:.3f} and Width:{widths[i]:.3f}\"\n", + " ax.set_title(title)\n", + " ax.legend()\n", + " fig.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_forecast(y_train, y_test, y_preds, y_pis, coverages, widths)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## VI. Forecast on test dataset with change point" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now see how MAPIE adapts its prediction intervals when a brutal changepoint arises in the test set. To simulate this, we will artificially decrease the electricity demand by 2 GW in the test set, aiming at simulating an effect, such as blackout or lockdown due to a pandemic, that was not taken into account by the model during its training. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Corrupt the dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "demand_df_corrupted = demand_df.copy()\n", + "demand_df_corrupted.Demand.iloc[-int(num_test_steps/2):] -= 2" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "n_lags = 5\n", + "for hour in range(1, n_lags):\n", + " demand_df[f\"Lag_{hour}\"] = demand_df[\"Demand\"].shift(hour)\n", + "demand_train_corrupted = demand_df_corrupted.iloc[:-num_test_steps, :].copy()\n", + "demand_test_corrupted = demand_df_corrupted.iloc[-num_test_steps:, :].copy()\n", + "\n", + "X_train = demand_train_corrupted.loc[\n", + " ~np.any(demand_train_corrupted[features].isnull(), axis=1), features\n", + "]\n", + "y_train = demand_train_corrupted.loc[X_train.index, \"Demand\"]\n", + "X_test = demand_test_corrupted.loc[:, features]\n", + "y_test = demand_test_corrupted[\"Demand\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(16, 5))\n", + "plt.ylabel(\"Hourly demand (GW)\")\n", + "plt.plot(y_train)\n", + "plt.plot(y_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prediction intervals without partial fit" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EnbPI, with no partial_fit, width optimization\n" + ] + } + ], + "source": [ + "print(\"EnbPI, with no partial_fit, width optimization\")\n", + "mapie_enbpi = mapie_enbpi.fit(X_train, y_train)\n", + "y_pred_npfit, y_pis_npfit = mapie_enbpi.predict(\n", + " X_test, alpha=alpha, ensemble=True, beta_optimize=True\n", + ")\n", + "coverage_npfit = regression_coverage_score(\n", + " y_test, y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0]\n", + ")\n", + "width_npfit = regression_mean_width_score(\n", + " y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prediction intervals with partial fit" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "EnbPI with partial_fit, width optimization\n" + ] + } + ], + "source": [ + "print(\"EnbPI with partial_fit, width optimization\")\n", + "mapie_enbpi = mapie_enbpi.fit(X_train, y_train)\n", + "\n", + "y_pred_pfit = np.zeros(y_pred_npfit.shape)\n", + "y_pis_pfit = np.zeros(y_pis_npfit.shape)\n", + "conformity_scores_pfit, lower_quantiles_pfit, higher_quantiles_pfit = [], [], []\n", + "y_pred_pfit[:gap], y_pis_pfit[:gap, :, :] = mapie_enbpi.predict(\n", + " X_test.iloc[:gap, :], alpha=alpha, ensemble=True\n", + ")\n", + "for step in range(gap, len(X_test), gap):\n", + " mapie_enbpi.partial_fit(\n", + " X_test.iloc[(step - gap):step, :],\n", + " y_test.iloc[(step - gap):step],\n", + " )\n", + " (\n", + " y_pred_pfit[step:step + gap],\n", + " y_pis_pfit[step:step + gap, :, :],\n", + " ) = mapie_enbpi.predict(\n", + " X_test.iloc[step:(step + gap), :],\n", + " alpha=alpha,\n", + " ensemble=True\n", + " )\n", + " conformity_scores_pfit.append(mapie_enbpi.conformity_scores_)\n", + " lower_quantiles_pfit.append(mapie_enbpi.lower_quantiles_)\n", + " higher_quantiles_pfit.append(mapie_enbpi.higher_quantiles_)\n", + "coverage_pfit = regression_coverage_score(\n", + " y_test, y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0]\n", + ")\n", + "width_pfit = regression_mean_width_score(\n", + " y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plot estimated prediction intervals on test set" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "y_preds = [y_pred_npfit, y_pred_pfit]\n", + "y_pis = [y_pis_npfit, y_pis_pfit]\n", + "coverages = [coverage_npfit, coverage_pfit]\n", + "widths = [width_npfit, width_pfit]" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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64gN2fGqW6p73cbpdJKbmwvA1VdfzQMPPMC3vvWt9ucDgQBms7nf537ooZ6v1K5du5aIiIj2AP3WW2/t9zm6kpqa2mtwDgML0Af6WgghhBBCDAubCxZeAx/9CT59uPcA/T8/gLoiSJ4vBc/6wmKDhKnDPQohRg1Z4t5Pp556Kvv372ft2rWsXr2aq666innz5hEIBPif//kflixZwvz58/nrX/8KgNaar3/968yePZvzzz+fo0ePth9r1apVbNy4EYA333yTRYsWsWDBAs444wwKCgp44IEH+P3vf092djYffPABd955J/fccw8Aubm5nHTSScyfP59LLrmE6urq9mN+73vfY+nSpUyfPp0PPvjghO+hoKCAuXONXKnHHnuMSy+9lHPPPZdp06bx3e8a7TG+//3v09zcTHZ2NldffTUATz75JEuXLiU7O5tbbrmFQCAAQEREBD/5yU9YtmwZv/rVr/j85z/ffq61a9dy4YUXAvCVr3yFnJwc5syZw09/+tMTxhUIBLj++uuZO3cu8+bN4/e///0g/qeEEEIIIfoo50bjdvtzUH2o++22PAWbHjPas332T2CSSQkhRGiNuhn04eT3+3njjTc499xzAdiwYQPbt28nKyuLBx98kOjoaD799FM8Hg8rVqzg7LPPZsuWLezZs4dt27ZRVlbG7NmzufHGGzsdt7y8nJtuuol169aRlZVFVVUVcXFx3HrrrURERPCd73wHgDVr1rTvc+2113LfffexcuVKfvKTn3DXXXfxhz/8oX2cGzZs4PXXX+euu+7inXfe6fH7ys3NZcuWLdjtdmbMmMFtt93Gb37zG/70pz+1rxzYtWsXzzzzDB9++CFWq5WvfvWrPPXUU1x77bU0NjYyd+5cfvazn+H3+5k8eTKNjY243W6eeeYZrrjiCgB++ctfEhcXRyAQ4IwzziAvL4/58+d3GkdxcTHbt28HoKamZsD/V0IIIYQQfRY/xciZ3v0qPPFZuPFNiEzuvM2RbfBa6wrK839rFJcTQogQkxn0PmibSc7JySEjI4MvfelLACxdupSsrCwA3nrrLZ544gmys7NZtmwZlZWV7Nu3j3Xr1nHllVdiNptJTU3l9NNPP+H4H3/8Maeddlr7seLies5lqq2tpaamhpUrVwJw3XXXsW7duvbnL730UgAWL15MQUFBr9/fGWecQXR0NA6H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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_forecast(y_train, y_test, y_preds, y_pis, coverages, widths, plot_coverage=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "window = 24\n", + "rolling_coverage_pfit, rolling_coverage_npfit = [], []\n", + "for i in range(window, len(y_test), 1):\n", + " rolling_coverage_pfit.append(\n", + " regression_coverage_score(\n", + " y_test[i-window:i], y_pis_pfit[i-window:i, 0, 0], y_pis_pfit[i-window:i, 1, 0]\n", + " )\n", + " )\n", + " rolling_coverage_npfit.append(\n", + " regression_coverage_score(\n", + " y_test[i-window:i], y_pis_npfit[i-window:i, 0, 0], y_pis_npfit[i-window:i, 1, 0]\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Marginal coverage on a 24-hour rolling window of prediction intervals" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(10, 5))\n", + "plt.ylabel(f\"Rolling coverage [{window} hours]\")\n", + "plt.plot(y_test[window:].index, rolling_coverage_npfit, label=\"Without update of residuals\")\n", + "plt.plot(y_test[window:].index, rolling_coverage_pfit, label=\"With update of residuals\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Temporal evolution of the distribution of residuals used for estimating prediction intervals" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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gEOHDrXp06dP9P333x8cHR0dlZOT0+b555/v0r9//37h4eFRc+fODQKAM2fOuN122219IyIiosLCwqJrrvv999/vlJSUVAJYi59x48aF1Py3WbhwYedLr6u8vFx27NjRbujQoRHR0dH9br755rCff/7ZEwDi4uIiHnzwwZ6DBw+ODAsLi/7666/bOXK/t2/f3r5Xr16VUVFRF7y9vdW9995bvGbNmusAIDw8/EJJSYnHsWPHnGqsYksXERGAgPyvLtvmWdmuwc+o9UtPT287cODAc/V99uGHH163f//+tpmZmRkFBQUecXFx/UaOHFkOAJmZmW3T0tKOhISEXBwyZEjk1q1bfZ544omi7777zmf06NGl06ZNO/3+++83eHx6enr7ffv2ZURGRl7YsGGDb1ZWVts1a9Yc6dy5c1WvXr1ivLy8ivbv35/55z//ufPixYs7r1ixIq8m17Rp004vXbq082uvvZZ36623nrNYLJg/f36P/Px8j6CgoKoVK1Z0mjp1qt0DRU+ePOm+adMmvyNHjhxwc3NDUVGROwA8+uijwbfcckvZggULcqqqqlBaWuq+Y8eOdv/61786paamZiqlMGTIkH4jRowoCwgIqM7NzfVevnx57sqVK4+tXbu2w+HDh73T09MzlVKIj4/vu3nzZp+TJ096dO3a9eL27dsPA8CpU6fcAeCHH37wmTx5cjEA7N69u11BQYFndnZ2BgAUFRW5BwQEVNe9rsrKSpk9e3bwxo0bDwcFBVUtX77c78knn+z+73//OxcAzp0757Zv376szZs3+0yfPr13dnZ2xhdffOE7b968npfey7Zt21r27duXlZeX16Z79+61LW09evS48MMPP9R+myYmJubcV1995TN16tSSpvw+ASy6iIiIrtqOHTt877vvvmIPDw/07Nmz6oYbbijfuXNnu44dO1piYmLOhoaGXgSA6Ojoczk5OZeNU7rS8QMGDDgbGRlZ+49/TEzM2V69el0EgODg4MrExMRSABg4cGDFN998c8VxRm5ubrjvvvtOLV++3P+xxx47tXfvXp+1a9cerbuPv79/tZeXl2XixIm9Ro0aVTphwoRSANi1a5fvmjVrjgKAh4cHOnXqVL19+3afu+66q6RDhw4WABg1atTpr7/+2nf8+PEl3bp1uzBixIizALBly5YO3377bYeoqKgowFoEZWVl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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(7, 5))\n", + "for i, j in enumerate([0, -1]):\n", + " plt.hist(conformity_scores_pfit[j], range=[-2.5, 0.5], bins=30, color=f\"C{i}\", alpha=0.3, label=f\"Conformity scores(step={j})\")\n", + " plt.axvline(lower_quantiles_pfit[j], ls=\"--\", color=f\"C{i}\")\n", + " plt.axvline(higher_quantiles_pfit[j], ls=\"--\", color=f\"C{i}\")\n", + "plt.legend(loc=[1, 0])" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "b42a3cd8426fe817ae9ba121b635fcbd42db4879a102a9a759ab9c3dd732f904" + }, + "jupytext": { + "formats": "ipynb,md" + }, + "kernelspec": { + "display_name": "mapie-notebooks", + "language": "python", + "name": "mapie-notebooks" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/regression/ts-changepoint.md b/notebooks/regression/ts-changepoint.md new file mode 100644 index 000000000..c8e511520 --- /dev/null +++ b/notebooks/regression/ts-changepoint.md @@ -0,0 +1,372 @@ +--- +jupyter: + jupytext: + formats: ipynb,md + text_representation: + extension: .md + format_name: markdown + format_version: '1.3' + jupytext_version: 1.13.6 + kernelspec: + display_name: mapie-notebooks + language: python + name: mapie-notebooks +--- + +# Estimating prediction intervals of time series forecast with EnbPI + + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/scikit-learn-contrib/MAPIE/blob/add-ts-notebooks/notebooks/regression/ts-changepoint.ipynb) + + +This example uses `mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate +prediction intervals associated with time series forecast. It follows Xu \& Xie (2021). +We use here the Victoria electricity demand dataset used in the book +"Forecasting: Principles and Practice" by R. J. Hyndman and G. Athanasopoulos. +The electricity demand features daily and weekly seasonalities and is impacted +by the temperature, considered here as a exogeneous variable. +A Random Forest model is already fitted on data. The hyper-parameters are +optimized with a `sklearn.model_selection.RandomizedSearchCV` using a +sequential `sklearn.model_selection.TimeSeriesSplit` cross validation, +in which the training set is prior to the validation set. +The best model is then feeded into +`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the +associated prediction intervals. We compare four approaches: with or without +``partial_fit`` called at every step. + +```python +install_mapie = False +if install_mapie: + !pip install "git+https://github.com/scikit-learn-contrib/MAPIE.git@add-ts-notebooks" +``` + +```python +import warnings + +import numpy as np +import pandas as pd +from matplotlib import pylab as plt +from scipy.stats import randint +from sklearn.ensemble import RandomForestRegressor +from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit + +from mapie.metrics import regression_coverage_score, regression_mean_width_score +from mapie.subsample import BlockBootstrap +from mapie.time_series_regression import MapieTimeSeriesRegressor + +%reload_ext autoreload +%autoreload 2 +warnings.simplefilter("ignore") +``` + +## 1. Load input data and feature engineering + +```python +url_file = "https://raw.githubusercontent.com/scikit-learn-contrib/MAPIE/master/examples/data/demand_temperature.csv" +demand_df = pd.read_csv( + url_file, parse_dates=True, index_col=0 +) + +demand_df["Date"] = pd.to_datetime(demand_df.index) +demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") +demand_df["Weekday"] = demand_df.Date.dt.isocalendar().day.astype("int64") +demand_df["Hour"] = demand_df.index.hour +n_lags = 5 +for hour in range(1, n_lags): + demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) + +``` + +## 2. Train/validation/test split + +```python +num_test_steps = 24 * 7 +demand_train = demand_df.iloc[:-num_test_steps, :].copy() +demand_test = demand_df.iloc[-num_test_steps:, :].copy() +features = ["Weekofyear", "Weekday", "Hour", "Temperature"] +features += [f"Lag_{hour}" for hour in range(1, n_lags)] + +X_train = demand_train.loc[ + ~np.any(demand_train[features].isnull(), axis=1), features +] +y_train = demand_train.loc[X_train.index, "Demand"] +X_test = demand_test.loc[:, features] +y_test = demand_test["Demand"] +``` + +```python +plt.figure(figsize=(16, 5)) +plt.plot(y_train) +plt.plot(y_test) +plt.ylabel("Hourly demand (GW)") +``` + +## 3. Optimize the base estimator + +```python +model_params_fit_not_done = False +if model_params_fit_not_done: + # CV parameter search + n_iter = 100 + n_splits = 5 + tscv = TimeSeriesSplit(n_splits=n_splits) + random_state = 59 + rf_model = RandomForestRegressor(random_state=random_state) + rf_params = {"max_depth": randint(2, 30), "n_estimators": randint(10, 100)} + cv_obj = RandomizedSearchCV( + rf_model, + param_distributions=rf_params, + n_iter=n_iter, + cv=tscv, + scoring="neg_root_mean_squared_error", + random_state=random_state, + verbose=0, + n_jobs=-1, + ) + cv_obj.fit(X_train, y_train) + model = cv_obj.best_estimator_ +else: + # Model: Random Forest previously optimized with a cross-validation + model = RandomForestRegressor( + max_depth=10, n_estimators=50, random_state=59) +``` + +## 4. Estimate prediction intervals on the test set + +```python +alpha = 0.05 +gap = 1 +cv_mapiets = BlockBootstrap( + n_resamplings=100, length=48, overlapping=True, random_state=59 +) +mapie_enbpi = MapieTimeSeriesRegressor( + model, method="enbpi", cv=cv_mapiets, agg_function="mean", n_jobs=-1 +) +``` + +### Without partial fit + +```python +print("EnbPI, with no partial_fit, width optimization") +mapie_enbpi = mapie_enbpi.fit(X_train, y_train) +y_pred_npfit, y_pis_npfit = mapie_enbpi.predict( + X_test, alpha=alpha, ensemble=True, beta_optimize=True +) +coverage_npfit = regression_coverage_score( + y_test, y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0] +) +width_npfit = regression_mean_width_score( + y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0] +) +``` + +### With partial fit + +```python +print("EnbPI with partial_fit, width optimization") +mapie_enbpi = mapie_enbpi.fit(X_train, y_train) + +y_pred_pfit = np.zeros(y_pred_npfit.shape) +y_pis_pfit = np.zeros(y_pis_npfit.shape) +y_pred_pfit[:gap], y_pis_pfit[:gap, :, :] = mapie_enbpi.predict( + X_test.iloc[:gap, :], alpha=alpha, ensemble=True +) +for step in range(gap, len(X_test), gap): + mapie_enbpi.partial_fit( + X_test.iloc[(step - gap):step, :], + y_test.iloc[(step - gap):step], + ) + ( + y_pred_pfit[step:step + gap], + y_pis_pfit[step:step + gap, :, :], + ) = mapie_enbpi.predict( + X_test.iloc[step:(step + gap), :], + alpha=alpha, + ensemble=True + ) +coverage_pfit = regression_coverage_score( + y_test, y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0] +) +width_pfit = regression_mean_width_score( + y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0] +) +``` + +## V. Plot estimated prediction intervals on test set + +```python +y_preds = [y_pred_npfit, y_pred_pfit] +y_pis = [y_pis_npfit, y_pis_pfit] +coverages = [coverage_npfit, coverage_pfit] +widths = [width_npfit, width_pfit] +``` + +```python +def plot_forecast(y_train, y_test, y_preds, y_pis, coverages, widths, plot_coverage=True): + fig, axs = plt.subplots( + nrows=2, ncols=1, figsize=(14, 8), sharey="row", sharex="col" + ) + for i, (ax, w) in enumerate(zip(axs, ["without", "with"])): + ax.set_ylabel("Hourly demand (GW)") + ax.plot(y_train[int(-len(y_test)/2):], lw=2, label="Training data", c="C0") + ax.plot(y_test, lw=2, label="Test data", c="C1") + + ax.plot( + y_test.index, y_preds[i], lw=2, c="C2", label="Predictions" + ) + ax.fill_between( + y_test.index, + y_pis[i][:, 0, 0], + y_pis[i][:, 1, 0], + color="C2", + alpha=0.2, + label="Prediction intervals", + ) + title = f"EnbPI, {w} update of residuals. " + if plot_coverage: + title += f"Coverage:{coverages[i]:.3f} and Width:{widths[i]:.3f}" + ax.set_title(title) + ax.legend() + fig.tight_layout() + plt.show() +``` + +```python +plot_forecast(y_train, y_test, y_preds, y_pis, coverages, widths) +``` + +## VI. Forecast on test dataset with change point + + +We will now see how MAPIE adapts its prediction intervals when a brutal changepoint arises in the test set. To simulate this, we will artificially decrease the electricity demand by 2 GW in the test set, aiming at simulating an effect, such as blackout or lockdown due to a pandemic, that was not taken into account by the model during its training. + + +### Corrupt the dataset + +```python +demand_df_corrupted = demand_df.copy() +demand_df_corrupted.Demand.iloc[-int(num_test_steps/2):] -= 2 +``` + +```python +n_lags = 5 +for hour in range(1, n_lags): + demand_df[f"Lag_{hour}"] = demand_df["Demand"].shift(hour) +demand_train_corrupted = demand_df_corrupted.iloc[:-num_test_steps, :].copy() +demand_test_corrupted = demand_df_corrupted.iloc[-num_test_steps:, :].copy() + +X_train = demand_train_corrupted.loc[ + ~np.any(demand_train_corrupted[features].isnull(), axis=1), features +] +y_train = demand_train_corrupted.loc[X_train.index, "Demand"] +X_test = demand_test_corrupted.loc[:, features] +y_test = demand_test_corrupted["Demand"] +``` + +```python +plt.figure(figsize=(16, 5)) +plt.ylabel("Hourly demand (GW)") +plt.plot(y_train) +plt.plot(y_test) +``` + +### Prediction intervals without partial fit + +```python +print("EnbPI, with no partial_fit, width optimization") +mapie_enbpi = mapie_enbpi.fit(X_train, y_train) +y_pred_npfit, y_pis_npfit = mapie_enbpi.predict( + X_test, alpha=alpha, ensemble=True, beta_optimize=True +) +coverage_npfit = regression_coverage_score( + y_test, y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0] +) +width_npfit = regression_mean_width_score( + y_pis_npfit[:, 0, 0], y_pis_npfit[:, 1, 0] +) +``` + +### Prediction intervals with partial fit + +```python +print("EnbPI with partial_fit, width optimization") +mapie_enbpi = mapie_enbpi.fit(X_train, y_train) + +y_pred_pfit = np.zeros(y_pred_npfit.shape) +y_pis_pfit = np.zeros(y_pis_npfit.shape) +conformity_scores_pfit, lower_quantiles_pfit, higher_quantiles_pfit = [], [], [] +y_pred_pfit[:gap], y_pis_pfit[:gap, :, :] = mapie_enbpi.predict( + X_test.iloc[:gap, :], alpha=alpha, ensemble=True +) +for step in range(gap, len(X_test), gap): + mapie_enbpi.partial_fit( + X_test.iloc[(step - gap):step, :], + y_test.iloc[(step - gap):step], + ) + ( + y_pred_pfit[step:step + gap], + y_pis_pfit[step:step + gap, :, :], + ) = mapie_enbpi.predict( + X_test.iloc[step:(step + gap), :], + alpha=alpha, + ensemble=True + ) + conformity_scores_pfit.append(mapie_enbpi.conformity_scores_) + lower_quantiles_pfit.append(mapie_enbpi.lower_quantiles_) + higher_quantiles_pfit.append(mapie_enbpi.higher_quantiles_) +coverage_pfit = regression_coverage_score( + y_test, y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0] +) +width_pfit = regression_mean_width_score( + y_pis_pfit[:, 0, 0], y_pis_pfit[:, 1, 0] +) +``` + +### Plot estimated prediction intervals on test set + +```python +y_preds = [y_pred_npfit, y_pred_pfit] +y_pis = [y_pis_npfit, y_pis_pfit] +coverages = [coverage_npfit, coverage_pfit] +widths = [width_npfit, width_pfit] +``` + +```python +plot_forecast(y_train, y_test, y_preds, y_pis, coverages, widths, plot_coverage=False) +``` + +```python +window = 24 +rolling_coverage_pfit, rolling_coverage_npfit = [], [] +for i in range(window, len(y_test), 1): + rolling_coverage_pfit.append( + regression_coverage_score( + y_test[i-window:i], y_pis_pfit[i-window:i, 0, 0], y_pis_pfit[i-window:i, 1, 0] + ) + ) + rolling_coverage_npfit.append( + regression_coverage_score( + y_test[i-window:i], y_pis_npfit[i-window:i, 0, 0], y_pis_npfit[i-window:i, 1, 0] + ) + ) +``` + +### Marginal coverage on a 24-hour rolling window of prediction intervals + +```python +plt.figure(figsize=(10, 5)) +plt.ylabel(f"Rolling coverage [{window} hours]") +plt.plot(y_test[window:].index, rolling_coverage_npfit, label="Without update of residuals") +plt.plot(y_test[window:].index, rolling_coverage_pfit, label="With update of residuals") +``` + +### Temporal evolution of the distribution of residuals used for estimating prediction intervals + +```python +plt.figure(figsize=(7, 5)) +for i, j in enumerate([0, -1]): + plt.hist(conformity_scores_pfit[j], range=[-2.5, 0.5], bins=30, color=f"C{i}", alpha=0.3, label=f"Conformity scores(step={j})") + plt.axvline(lower_quantiles_pfit[j], ls="--", color=f"C{i}") + plt.axvline(higher_quantiles_pfit[j], ls="--", color=f"C{i}") +plt.legend(loc=[1, 0]) +``` From c06b0e609b6750511baaef4721739c63a9880922 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 1 Jun 2022 12:26:44 +0200 Subject: [PATCH 23/32] Take VTA remarks into account --- .gitignore | 1 + HISTORY.rst | 2 +- doc/images/quickstart_1.png | Bin 82894 -> 82894 bytes .../plot_MapieRegressor_benchmark.py | 163 ------------- .../plot_timeseries_enbpi.py | 166 ++++--------- mapie/quantile_timeit.ipynb | 230 ------------------ mapie/regression.py | 43 ++-- mapie/subsample.py | 46 ++-- mapie/tests/test_time_series_regression.py | 6 +- mapie/time_series_regression.py | 189 ++++++-------- 10 files changed, 173 insertions(+), 673 deletions(-) delete mode 100644 examples/regression/2-advanced-analysis/plot_MapieRegressor_benchmark.py delete mode 100644 mapie/quantile_timeit.ipynb diff --git a/.gitignore b/.gitignore index cad52327c..4591e989d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ *.py[cod] *$py.class .DS_Store +.mypy* # C extensions *.so diff --git a/HISTORY.rst b/HISTORY.rst index 872fe634a..4660c8661 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,7 @@ History 0.3.3 (2022-XX-XX) ------------------ * Relax and fix typing +* Add EnbPI method for Time Series Regression 0.3.2 (2022-03-11) ------------------ @@ -17,7 +18,6 @@ History * Uniformize the use of matrix k_ and add an argument "ensemble" to method "predict" in regression.py * Add replication of the Chen Xu's tutorial testing Jackknife+aB vs Jackknife+ * Add Jackknife+-after-Bootstrap documentation -* Add EnbPI method for Time Series Regression * Improve scikit-learn pipelines compatibility 0.3.1 (2021-11-19) diff --git a/doc/images/quickstart_1.png b/doc/images/quickstart_1.png index 969d76c373a61a661cad58d6a425832d8522b8e7..3322f6981ea6a768e1951c2ea27841fed0626ba1 100644 GIT binary patch delta 46 zcmX@t&U&t$b%KYSp^idENl8JmmA-y%Vo5 NDArray: - """One-dimensional x*sin(x) function.""" - return x * np.sin(x) - - -def get_1d_data_with_normal_distrib( - funct: F, mu: float, sigma: float, n_samples: int, noise: float -) -> Tuple[NDArray, NDArray, NDArray, NDArray, NDArray]: - """ - Generate noisy 1D data with normal distribution from given function - and noise standard deviation. - - Parameters - ---------- - funct : F - Base function used to generate the dataset. - mu : float - Mean of normal training distribution. - sigma : float - Standard deviation of normal training distribution. - n_samples : int - Number of training samples. - noise : float - Standard deviation of noise. - - Returns - ------- - Tuple[NDArray, AnNDArrayy, NDArray, NDArray, NDArray] - Generated training and test data. - [0]: X_train - [1]: y_train - [2]: X_test - [3]: y_test - [4]: y_mesh - """ - np.random.seed(42) - X_train = np.random.normal(mu, sigma, n_samples) - X_test = np.arange(mu - 4 * sigma, mu + 4 * sigma, sigma / 20.0) - y_train, y_mesh, y_test = funct(X_train), funct(X_test), funct(X_test) - y_train += np.random.normal(0, noise, y_train.shape[0]) - y_test += np.random.normal(0, noise, y_test.shape[0]) - return ( - X_train.reshape(-1, 1), - y_train, - X_test.reshape(-1, 1), - y_test, - y_mesh, - ) - - -# Data generation -mu, sigma, n_samples, noise = 0, 2.5, 300, 0.5 -X_train, y_train, X_test, y_test, y_mesh = get_1d_data_with_normal_distrib( - x_sinx, mu, sigma, n_samples, noise -) - -# Definition of our base model -degree_polyn = 10 -polyn_model = Pipeline( - [ - ("poly", PolynomialFeatures(degree=degree_polyn)), - ("linear", LinearRegression()), - ] -) - -# Estimating prediction intervals -Params = TypedDict("Params", {"method": str, "cv": Union[int, Subsample]}) -STRATEGIES = { - "jackknife_plus": Params(method="plus", cv=-1), - "jackknife_minmax": Params(method="minmax", cv=-1), - "cv_plus": Params(method="plus", cv=10), - "cv_minmax": Params(method="minmax", cv=10), - "jackknide-plus-after-bootstrap": Params(method="plus", cv=10), - "jackknide-minmax-after-bootstrap": Params(method="minmax", cv=10), -} -y_pred, y_pis = {}, {} -for strategy, params in STRATEGIES.items(): - mapie = MapieRegressor(polyn_model, **params) - mapie.fit(X_train, y_train) - y_pred[strategy], y_pis[strategy] = mapie.predict(X_test, alpha=0.05) - - -# Visualization -def plot_1d_data( - X_train: NDArray, - y_train: NDArray, - X_test: NDArray, - y_test: NDArray, - y_sigma: float, - y_pred: NDArray, - y_pred_low: NDArray, - y_pred_up: NDArray, - ax: plt.Axes, - title: str, -) -> None: - ax.set_xlabel("x") - ax.set_ylabel("y") - ax.set_xlim([-10, 10]) - ax.set_ylim([np.min(y_test) * 1.3, np.max(y_test) * 1.3]) - ax.fill_between(X_test, y_pred_low, y_pred_up, alpha=0.3) - ax.scatter(X_train, y_train, color="red", alpha=0.3, label="Training data") - ax.plot(X_test, y_test, color="gray", label="True confidence intervals") - ax.plot(X_test, y_test - y_sigma, color="gray", ls="--") - ax.plot(X_test, y_test + y_sigma, color="gray", ls="--") - ax.plot(X_test, y_pred, color="b", alpha=0.5, label="Prediction intervals") - if title is not None: - ax.set_title(title) - ax.legend() - - -n_figs = len(STRATEGIES) -fig, axs = plt.subplots(2, 2, figsize=(13, 12)) -coords = [axs[0, 0], axs[0, 1], axs[1, 0], axs[1, 1]] -for strategy, coord in zip(STRATEGIES, coords): - plot_1d_data( - X_train.ravel(), - y_train.ravel(), - X_test.ravel(), - y_mesh.ravel(), - 1.96 * noise, - y_pred[strategy].ravel(), - y_pis[strategy][:, 0, 0].ravel(), - y_pis[strategy][:, 1, 0].ravel(), - ax=coord, - title=strategy, - ) - - -fig, ax = plt.subplots(1, 1, figsize=(7, 5)) -ax.set_xlim([-8, 8]) -ax.set_ylim([0, 4]) -for strategy in STRATEGIES: - ax.plot(X_test, y_pis[strategy][:, 1, 0] - y_pis[strategy][:, 0, 0]) -ax.axhline(1.96 * 2 * noise, ls="--", color="k") -ax.set_xlabel("x") -ax.set_ylabel("Prediction Interval Width") -ax.legend(list(STRATEGIES.keys()) + ["True width"], fontsize=8) -plt.show() diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 0cb4da004..d86ecd1f4 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -2,28 +2,27 @@ ================================================================== Estimating prediction intervals of time series forecast with EnbPI ================================================================== + This example uses :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate -prediction intervals associated with time series forecast. It follows [6] and -an alternative expermimental implemetation inspired from [2] +prediction intervals associated with time series forecast. It follows [6]. We use here the Victoria electricity demand dataset used in the book "Forecasting: Principles and Practice" by R. J. Hyndman and G. Athanasopoulos. The electricity demand features daily and weekly seasonalities and is impacted by the temperature, considered here as a exogeneous variable. -A Random Forest model is aloready fitted on data. The hyper-parameters are +A Random Forest model is already fitted on data. The hyper-parameters are optimized with a :class:`sklearn.model_selection.RandomizedSearchCV` using a sequential :class:`sklearn.model_selection.TimeSeriesSplit` cross validation, in which the training set is prior to the validation set. The best model is then feeded into :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the associated prediction intervals. We compare four approaches: with or without -``partial_fit`` called at every step, and following [6] or a approach inspired -from [2]. It appears that the approach inspired from [2] and ``partial_fit`` -offer higher coverage, but with higher width of PIs and are much slower. +``partial_fit`` called at every step, and following [6]. It appears that +``partial_fit`` offer higher coverage, but with higher width of PIs and is much +slower. """ -import warnings import numpy as np import pandas as pd @@ -32,12 +31,13 @@ from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit -from mapie.metrics import regression_coverage_score +from mapie.metrics import ( + regression_coverage_score, + regression_mean_width_score, +) from mapie.subsample import BlockBootstrap from mapie.time_series_regression import MapieTimeSeriesRegressor -warnings.simplefilter("ignore") - # Load input data and feature engineering demand_df = pd.read_csv( "../data/demand_temperature.csv", parse_dates=True, index_col=0 @@ -66,8 +66,8 @@ X_test = demand_test.loc[:, features] y_test = demand_test["Demand"] -model_params_fit_not_done = False -if model_params_fit_not_done: +perform_hyperparameters_search = False +if perform_hyperparameters_search: # CV parameter search n_iter = 100 n_splits = 5 @@ -90,104 +90,69 @@ else: # Model: Random Forest previously optimized with a cross-validation model = RandomForestRegressor( - max_depth=10, n_estimators=50, random_state=59) + max_depth=10, n_estimators=50, random_state=59 + ) # Estimate prediction intervals on test set with best estimator alpha = 0.05 -cv_MapieTimeSeries = BlockBootstrap( +cv_mapietimeseries = BlockBootstrap( n_resamplings=100, length=48, overlapping=True, random_state=59 ) -mapie_plus = MapieTimeSeriesRegressor( - model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 -) mapie_enpbi = MapieTimeSeriesRegressor( - model, method="plus", cv=cv_MapieTimeSeries, agg_function="mean", n_jobs=-1 + model, + method="enbpi", + cv=cv_mapietimeseries, + agg_function="mean", + n_jobs=-1, ) -gap = 1 - print("EnbPI, with no partial_fit, width optimization") mapie_enpbi = mapie_enpbi.fit(X_train, y_train) y_pred_npfit_enbpi, y_pis_npfit_enbpi = mapie_enpbi.predict( - X_test, alpha=alpha, ensemble=True, beta_optimize=True + X_test, alpha=alpha, ensemble=True, optimize_beta=True ) coverage_npfit_enbpi = regression_coverage_score( y_test, y_pis_npfit_enbpi[:, 0, 0], y_pis_npfit_enbpi[:, 1, 0] ) -width_npfit_enbpi = ( - y_pis_npfit_enbpi[:, 1, 0] - y_pis_npfit_enbpi[:, 0, 0] -).mean() + +width_npfit_enbpi = regression_mean_width_score( + y_pis_npfit_enbpi[:, 1, 0], y_pis_npfit_enbpi[:, 0, 0] +) print("EnbPI with partial_fit, width optimization") mapie_enpbi = mapie_enpbi.fit(X_train, y_train) y_pred_pfit_enbpi = np.zeros(y_pred_npfit_enbpi.shape) y_pis_pfit_enbpi = np.zeros(y_pis_npfit_enbpi.shape) - -y_pred_pfit_enbpi[:gap], y_pis_pfit_enbpi[:gap, :, :] = mapie_enpbi.predict( - X_test.iloc[:gap, :], alpha=alpha, ensemble=True, beta_optimize=True +step_size = 1 +( + y_pred_pfit_enbpi[:step_size], + y_pis_pfit_enbpi[:step_size, :, :], +) = mapie_enpbi.predict( + X_test.iloc[:step_size, :], alpha=alpha, ensemble=True, optimize_beta=True ) -for step in range(gap, len(X_test), gap): +for step in range(step_size, len(X_test), step_size): mapie_enpbi.partial_fit( - X_test.iloc[(step - gap):step, :], - y_test.iloc[(step - gap):step], + X_test.iloc[(step - step_size) : step, :], + y_test.iloc[(step - step_size) : step], ) ( - y_pred_pfit_enbpi[step:step + gap], - y_pis_pfit_enbpi[step:step + gap, :, :], + y_pred_pfit_enbpi[step : step + step_size], + y_pis_pfit_enbpi[step : step + step_size, :, :], ) = mapie_enpbi.predict( - X_test.iloc[step:(step + gap), :], + X_test.iloc[step : (step + step_size), :], alpha=alpha, ensemble=True, - beta_optimize=True, + optimize_beta=True, ) coverage_pfit_enbpi = regression_coverage_score( y_test, y_pis_pfit_enbpi[:, 0, 0], y_pis_pfit_enbpi[:, 1, 0] ) -width_pfit_enbpi = ( - y_pis_pfit_enbpi[:, 1, 0] - y_pis_pfit_enbpi[:, 0, 0] -).mean() - -print("Plus, with partial_fit, width optimization") -mapie_plus = mapie_plus.fit(X_train, y_train) -y_pred_pfit_plus = np.zeros(y_pred_npfit_enbpi.shape) -y_pis_pfit_plus = np.zeros(y_pis_npfit_enbpi.shape) -(y_pred_pfit_plus[:gap], y_pis_pfit_plus[:gap, :, :],) = mapie_plus.predict( - X_test.iloc[:gap, :], - alpha=alpha, - beta_optimize=True, +width_pfit_enbpi = regression_mean_width_score( + y_pis_pfit_enbpi[:, 1, 0], y_pis_pfit_enbpi[:, 0, 0] ) -for step in range(gap, len(X_test), gap): - mapie_plus.partial_fit( - X_test.iloc[step - gap:step, :], - y_test.iloc[step - gap:step], - ) - ( - y_pred_pfit_plus[step:step + gap], - y_pis_pfit_plus[step:step + gap, :, :], - ) = mapie_plus.predict( - X_test.iloc[step:step + gap, :], - alpha=alpha, - ensemble=True, - beta_optimize=True, - ) - -coverage_pfit_plus = regression_coverage_score( - y_test, y_pis_pfit_plus[:, 0, 0], y_pis_pfit_plus[:, 1, 0] -) -width_pfit_plus = (y_pis_pfit_plus[:, 1, 0] - y_pis_pfit_plus[:, 0, 0]).mean() - -print("Plus, with NO partial_fit, MapieRegressor_Like no") -mapie_plus = mapie_plus.fit(X_train, y_train) -y_pred_pfit_MR, y_pis_pfit_MR = mapie_plus.predict( - X_test, alpha=alpha, ensemble=True -) -coverage_pfit_MR = regression_coverage_score( - y_test, y_pis_pfit_MR[:, 0, 0], y_pis_pfit_MR[:, 1, 0] -) -width_pfit_MR = (y_pis_pfit_MR[:, 1, 0] - y_pis_pfit_MR[:, 0, 0]).mean() # Print results print( @@ -200,27 +165,16 @@ "\nEnbPI with partial_fit:" f"{coverage_pfit_enbpi:.3f}, {width_pfit_enbpi:.3f}" ) -print( - "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nPlus, with partial_fit:" - f"{coverage_pfit_plus:.3f}, {width_pfit_plus:.3f}" -) -print( - "Coverage / prediction interval width mean for MapieTimeSeriesRegressor: " - "\nMR_Like, with partial_fit:" - f"{coverage_pfit_MR:.3f}, {width_pfit_MR:.3f}" -) # Plot estimated prediction intervals on test set -fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots( - nrows=2, ncols=2, figsize=(30, 25), sharey="row", sharex="col" +fig, (ax1, ax2) = plt.subplots( + nrows=2, ncols=1, figsize=(30, 25), sharey="row", sharex="col" ) -for ax in [ax1, ax2, ax3, ax4]: +for ax in [ax1, ax2]: ax.set_ylabel("Hourly demand (GW)") ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") - ax1.plot( demand_test.index, y_pred_npfit_enbpi, lw=2, c="C2", label="Predictions" ) @@ -253,39 +207,5 @@ f"Coverage:{coverage_pfit_enbpi:.3f} Width:{width_pfit_enbpi:.3f}" ) - -ax3.plot( - demand_test.index, - y_pred_pfit_plus, - lw=2, - c="C2", - label="Predictions", -) -ax3.fill_between( - demand_test.index, - y_pis_pfit_plus[:, 0, 0], - y_pis_pfit_plus[:, 1, 0], - color="C2", - alpha=0.2, - label="MapieTimeSeriesRegressor PIs", -) -ax3.set_title( - "Plus, with partial_fit.\n" - f"Coverage:{coverage_pfit_plus:.3f}" - f"Width:{width_pfit_plus:.3f}" -) -ax4.plot(demand_test.index, y_pred_pfit_MR, lw=2, c="C2", label="Predictions") -ax4.fill_between( - demand_test.index, - y_pis_pfit_MR[:, 0, 0], - y_pis_pfit_MR[:, 1, 0], - color="C2", - alpha=0.2, - label="MapieTimeSeriesRegressor PIs", -) -ax4.set_title( - "MapieRegressor Like, with partial_fit\n" - f"Coverage:{coverage_pfit_MR:.3f} Width:{width_pfit_MR:.3f}" -) ax1.legend() plt.show() diff --git a/mapie/quantile_timeit.ipynb b/mapie/quantile_timeit.ipynb deleted file mode 100644 index d9770966d..000000000 --- a/mapie/quantile_timeit.ipynb +++ /dev/null @@ -1,230 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "d4595769-7300-44d1-8850-c269ebf72fde", - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "sys.path.append(\"/Users/tmorzadec/Missions/MAPIE\")\n", - "import numpy as np\n", - "from mapie.utils import masked_quantile\n", - "from mapie.regression import MapieRegressor\n", - "from sklearn.datasets import make_regression\n", - "import numpy.ma as ma\n", - "from pycallgraph2 import PyCallGraph, Config\n", - "from pycallgraph2.output import GraphvizOutput\n", - "from functools import lru_cache\n", - "import callgraph.decorator as callgraph\n", - "%load_ext autoreload\n", - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "4e824af9-8e84-4b89-b6de-e983a168c02f", - "metadata": {}, - "outputs": [], - "source": [ - "X = np.random.uniform(low=-100, high=100, size = int(1e8)).reshape(int(1e4), -1)\n", - "\n", - "# indices1 = np.random.choice(X.shape[0], size=1, replace=True)\n", - "# indices2 = np.random.choice(X.shape[1], size=1, replace=True)\n", - "# indices = zip(indices1, indices2)\n", - "\n", - "# for (x, y) in indices:\n", - "# X[x,y] = np.nan\n", - "# " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "ea9ba9ba-3dcf-43f0-9425-91437fd6f000", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(1000, 10000)\n" - ] - } - ], - "source": [ - "q = list(np.linspace(0.1, 0.9, 1000))\n", - "print(X.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f90f220e-cc20-40cc-a33c-566470b61856", - "metadata": {}, - "outputs": [], - "source": [ - "@callgraph()\n", - "@lru_cache()\n", - "def mask():\n", - " masked_quantile(ma.masked_invalid(X), q, axis=0, method=\"higher\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "29592f5d", - "metadata": {}, - "outputs": [], - "source": [ - "%timeit -n 1 -r 1 masked_quantile(ma.masked_invalid(X), q, axis=0, method=\"higher\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "843ab182-fd23-40f9-a569-647b1a2ad165", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "7.48 s ± 401 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], - "source": [ - "%timeit np.nanquantile(X, q, axis=0, interpolation=\"higher\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "260ba3a6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "7.35 s ± 502 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" - ] - } - ], - "source": [ - "%timeit np.quantile(X, q, axis=0, interpolation=\"higher\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "5349359a", - "metadata": {}, - "outputs": [], - "source": [ - "config = Config(max_depth=10)\n", - "with PyCallGraph(output=GraphvizOutput(), config=config):\n", - " masked_quantile(ma.masked_invalid(X), q, axis=0, method=\"higher\")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2acbef74-3f03-457a-b31e-a5dad75d70c4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2\n", - "500\n", - "(1, 500)\n", - "2\n", - "500\n", - "(1, 500)\n", - "2\n", - "500\n", - "(1, 500)\n", - "2\n", - "500\n", - "(1, 500)\n", - "[-118.90258408 209.02916237 -22.18296168 234.40885886 -159.61745268\n", - " -184.84111207 26.85546511 -19.12965127 -162.97950999 -53.36413493]\n", - "(1, 2, 1000)\n" - ] - } - ], - "source": [ - "mapie_reg = MapieRegressor(method=\"minmax\", agg_function=\"mean\", cv=-1)\n", - "alpha = [0.2, 0.8]\n", - "mapie_reg.fit(X, y)\n", - "#y_pred_float1, y_pis_float1 = mapie_reg.predict(X, alpha=alpha[0])\n", - "#y_pred_float2, y_pis_float2 = mapie_reg.predict(X, alpha=alpha[1])\n", - "y_pred_array, y_pis_array = mapie_reg.predict(X, alpha=alpha)\n", - "#print(y_pis_float1[0,1,:10])\n", - "#print(y_pis_float2[0,1,:10])\n", - "print(y_pis_array[0,1,:10])\n", - "print(y_pis_array.shape)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f82a3d9-cacc-42c6-9350-0709811a0af9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([-0.44901419, -0.31701145, 0.03759213, -1.03072401, 0.32107287,\n", - " 0.73329445, -0.16373546, -0.58167561, 0.24257418, -0.40065236])" - ] - }, - "execution_count": 106, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "X[0,:].flatten()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d03bf675-6fd1-4407-ae2d-0937c2e46df9", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "interpreter": { - "hash": "e1d6b69c58a8ab3fab9d4bd10bf376ef86c3438c956e9d4e062e4cc32a9f8bce" - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/mapie/regression.py b/mapie/regression.py index 7670c5ebe..6150b389c 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -439,6 +439,32 @@ def aggregate_with_mask(self, x: NDArray, k: NDArray) -> NDArray: return np.matmul(x, (K / (K.sum(axis=1, keepdims=True))).T) raise ValueError("The value of self.agg_function is not correct") + def _pred_multi(self, X: ArrayLike) -> NDArray: + """ + Return a prediction per train sample for each test sample, by + aggregation with matrix ``k_``. + + Parameters + ---------- + X: NDArray of shape (n_samples_test, n_features) + Input data + + Returns + ------- + NDArray of shape (n_samples_test, n_samples_train) + """ + y_pred_multi = np.column_stack( + [e.predict(X) for e in self.estimators_] + ) + # At this point, y_pred_multi is of shape + # (n_samples_test, n_estimators_). The method + # ``aggregate_with_mask`` fits it to the right size + # thanks to the shape of k_. + + y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + return y_pred_multi + + def fit( self, X: ArrayLike, @@ -617,22 +643,7 @@ def predict( y_pred_low = y_pred[:, np.newaxis] - quantile y_pred_up = y_pred[:, np.newaxis] + quantile else: - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] - ) - - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). - # If ``method`` is "plus": - # - if ``cv`` is not a ``Subsample``, - # we enforce y_pred_multi to be of shape - # (n_samples_test, n_samples_train), - # thanks to the folds identifier. - # - if ``cv``is a ``Subsample``, the methode - # ``aggregate_with_mask`` fits it to the right size - # thanks to the shape of k_. - - y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + y_pred_multi = self._pred_multi(X) if self.method in self.plus_like_method: lower_bounds = y_pred_multi - self.conformity_scores_ diff --git a/mapie/subsample.py b/mapie/subsample.py index 43f86035f..899dbeacc 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -27,8 +27,8 @@ class Subsample(BaseCrossValidator): the size of the training set. replace: bool Whether to replace samples in resamplings or not. By default ``True``. - random_state: Optional - int or RandomState instance. . By default ``None`` + random_state: Optional[Union[int, RandomState]] + int or RandomState instance. By default ``None`` Examples @@ -106,8 +106,8 @@ def get_n_splits(self, *args: Any, **kargs: Any) -> int: class BlockBootstrap(BaseCrossValidator): # type: ignore """ Generate a sampling method, that block bootstraps the training set. - It can replace KFold, LeaveOneOut or SubSample as cv argument in the MAPIE - class. + It can replace KFold, LeaveOneOut or SubSample as cv argument in the + MapieRegressor class. Parameters ---------- @@ -117,8 +117,8 @@ class BlockBootstrap(BaseCrossValidator): # type: ignore Length of the blocks. By default ``None``, the length of the training set divided by ``n_blocks``. overlapping: bool - Whether the blocks can overlapp or not. By default ``False``. - n_blocsk: int + Whether the blocks can overlap or not. By default ``False``. + n_blocks: int Number of blocks in each resampling. By default ``None``, the size of the training set divided by ``length``. random_state: Optional @@ -133,8 +133,8 @@ class BlockBootstrap(BaseCrossValidator): # type: ignore -------- >>> import numpy as np >>> from mapie.subsample import BlockBootstrap - >>> cv = BlockBootstrap(n_resamplings=2, length = 3, random_state=0) - >>> X = np.array([1,2,3,4,5,6,7,8,9,10]) + >>> cv = BlockBootstrap(n_resamplings=2, length=3, random_state=0) + >>> X = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) >>> for train_index, test_index in cv.split(X): ... print(f"train index is {train_index}, test index is {test_index}") train index is [1 2 3 4 5 6 1 2 3 4 5 6], test index is [8 9 7] @@ -177,33 +177,35 @@ def split( ValueError If ``length`` is not positive or greater than the train set size. """ + if (self.n_blocks is not None) + (self.length is not None) != 1: + raise ValueError( + "Exactly one argument between ``length`` or " + "``n_blocks`` has to be not None" + ) + + n = len(X) + if self.n_blocks is not None: length = ( - self.length - if self.length is not None - else len(X) // self.n_blocks + self.length if self.length is not None else n // self.n_blocks ) n_blocks = self.n_blocks elif self.length is not None: length = self.length - n_blocks = (len(X) // self.length) + 1 - else: - raise ValueError( - "At least one argument between ``length`` or " - "``n_blocks`` has to be not None" - ) - indices = np.arange(len(X)) - if (length <= 0) or (length > len(indices)): + n_blocks = (n // self.length) + 1 + + indices = np.arange(n) + if (length <= 0) or (length > n): raise ValueError( - "The length of blocks is <= 0 or greater than the lenght" + "The length of blocks is <= 0 or greater than the length" "of training set." ) if self.overlapping: blocks = sliding_window_view(indices, window_shape=length) else: - indices = indices[(len(indices) % length):] - blocks_number = len(indices) // length + indices = indices[(n % length) :] + blocks_number = n // length blocks = np.asarray( np.array_split(indices, indices_or_sections=blocks_number) ) diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 1f5648632..8caf3d0fd 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -223,10 +223,10 @@ def test_linear_regression_results(strategy: str) -> None: mapie_ts = MapieTimeSeriesRegressor(**STRATEGIES[strategy]) mapie_ts.fit(X, y) if "opt" in strategy: - beta_optimize = True + optimize_beta = True else: - beta_optimize = False - _, y_pis = mapie_ts.predict(X, alpha=0.05, beta_optimize=beta_optimize) + optimize_beta = False + _, y_pis = mapie_ts.predict(X, alpha=0.05, optimize_beta=optimize_beta) y_pred_low, y_pred_up = y_pis[:, 0, 0], y_pis[:, 1, 0] width_mean = (y_pred_up - y_pred_low).mean() diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 4ffc596fc..61f989120 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -73,69 +73,6 @@ def _relative_conformity_scores( y_pred, _ = super().predict(X, alpha=0.5, ensemble=True) return np.asarray(y) - np.asarray(y_pred) - def fit( - self, - X: ArrayLike, - y: ArrayLike, - sample_weight: Optional[ArrayLike] = None, - ) -> MapieTimeSeriesRegressor: - """ - Compare to the method ``fit`` of ``MapieRegressor``, the ``fit`` method - of ``MapieTimeSeriesRegressor`` computes the ``conformity_scores_`` - with relative values. - - Returns - ------- - MapieTimeSeriesRegressor - The model itself. - """ - self = super().fit(X=X, y=y, sample_weight=sample_weight) - self.conformity_scores_ = self._relative_conformity_scores(X, y) - return self - - def partial_fit( - self, - X: ArrayLike, - y: ArrayLike, - ) -> MapieTimeSeriesRegressor: - """ - Update the ``conformity_scores_`` and ``k_`` attributes when new data - with known labels are available. - Note: Don't use ``partial_fit`` with samples of the training set. - - Parameters - ---------- - X : ArrayLike of shape (n_samples_test, n_features) - Input data. - - y : ArrayLike of shape (n_samples_test,) - Input labels. - - Returns - ------- - MapieTimeSeriesRegressor - The model itself. - - Raises - ------ - ValueError - If the lenght of y is greater than the lenght of the training set. - """ - X = cast(NDArray, X) - y = cast(NDArray, y) - if len(X) > len(self.conformity_scores_): - raise ValueError( - "You try to update more residuals than there are!" - ) - new_conformity_scores_ = self._relative_conformity_scores(X, y) - self.conformity_scores_ = np.roll( - self.conformity_scores_, -len(new_conformity_scores_) - ) - self.conformity_scores_[-len(new_conformity_scores_):] = ( - new_conformity_scores_ - ) - return self - def _beta_optimize( self, alpha: Union[float, NDArray], @@ -143,8 +80,7 @@ def _beta_optimize( lower_bounds: NDArray, ) -> NDArray: """ - ``_beta_optimize`` offers to minimize the width of the PIs, for a given - difference of quantiles. + Minimize the width of the PIs, for a given difference of quantiles. Parameters ---------- @@ -172,7 +108,7 @@ def _beta_optimize( ) alpha = cast(NDArray, alpha) betas_0 = np.full( - shape=(len(alpha), len(lower_bounds)), + shape=(len(lower_bounds), len(alpha)), fill_value=np.nan, dtype=float, ) @@ -189,57 +125,96 @@ def _beta_optimize( 1 - _alpha + betas, axis=1, method="higher", - ) # type: ignore + ) beta = np_nanquantile( lower_bounds, betas, axis=1, method="lower", - ) # type: ignore - betas_0[ind_alpha, :] = betas[ + ) + betas_0[:, ind_alpha] = betas[ np.argmin(one_alpha_beta - beta, axis=0) ] return betas_0 - def _pred_multi(self, X: ArrayLike) -> NDArray: + def fit( + self, + X: ArrayLike, + y: ArrayLike, + sample_weight: Optional[ArrayLike] = None, + ) -> MapieTimeSeriesRegressor: + """ + Compared to the method ``fit`` of ``MapieRegressor``, the ``fit`` + method of ``MapieTimeSeriesRegressor`` computes the + ``conformity_scores_`` with relative values. + + Returns + ------- + MapieTimeSeriesRegressor + The model itself. """ - Return a prediction per train sample for each test sample, by - aggregation with matrix ``k_``. + self = super().fit(X=X, y=y, sample_weight=sample_weight) + self.conformity_scores_ = self._relative_conformity_scores(X, y) + return self + + def partial_fit( + self, + X: ArrayLike, + y: ArrayLike, + ) -> MapieTimeSeriesRegressor: + """ + Update the ``conformity_scores_`` attribute when new data with known + labels are available. + Note: Don't use ``partial_fit`` with samples of the training set. Parameters ---------- - X: NDArray of shape (n_samples_test, n_features) - Input data + X : ArrayLike of shape (n_samples_test, n_features) + Input data. + + y : ArrayLike of shape (n_samples_test,) + Input labels. Returns ------- - NDArray of shape (n_samples_test, n_samples_train) + MapieTimeSeriesRegressor + The model itself. + + Raises + ------ + ValueError + If the length of y is greater than the length of the training set. """ - y_pred_multi = np.column_stack( - [e.predict(X) for e in self.estimators_] + X = cast(NDArray, X) + y = cast(NDArray, y) + n = len(self.conformity_scores_) + if len(X) > n: + raise ValueError( + "You try to update more residuals than there are!" + ) + new_conformity_scores_ = self._relative_conformity_scores(X, y) + self.conformity_scores_ = np.roll( + self.conformity_scores_, -len(new_conformity_scores_) ) - # At this point, y_pred_multi is of shape - # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size - # thanks to the shape of k_. - - y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) - return y_pred_multi + self.conformity_scores_[ + -len(new_conformity_scores_) : + ] = new_conformity_scores_ + return self def predict( self, X: ArrayLike, ensemble: bool = False, alpha: Optional[Union[float, Iterable[float]]] = None, - beta_optimize: bool = True, + optimize_beta: bool = True, ) -> Union[NDArray, Tuple[NDArray, NDArray]]: """ Correspond to 'Conformal prediction for dynamic time-series'. Parameters ---------- - beta_optimize: bool + optimize_beta: bool Whether to optimize the PIs' width or not. """ @@ -248,42 +223,36 @@ def predict( check_is_fitted(self, self.fit_attributes) self._check_ensemble(ensemble) alpha = cast(Optional[NDArray], check_alpha(alpha)) - X = check_array(X, force_all_finite=False, dtype=["float64", "object"]) y_pred = self.single_estimator_.predict(X) + n = len(self.conformity_scores_) if alpha is None: return np.array(y_pred) else: alpha_np = cast(NDArray, alpha) - check_alpha_and_n_samples(alpha_np, len(self.conformity_scores_)) + check_alpha_and_n_samples(alpha_np, n) - if beta_optimize: + if optimize_beta: betas_0 = self._beta_optimize( - alpha=alpha_np, - lower_bounds=self.conformity_scores_.reshape(1, -1), - upper_bounds=self.conformity_scores_.reshape(1, -1), + alpha_np, + self.conformity_scores_.reshape(1, -1), + self.conformity_scores_.reshape(1, -1), ) else: - betas_0 = np.full( - shape=(len(alpha), len(self.conformity_scores_)), - fill_value=np.nan, - dtype=float, - ) - for ind_alpha, _alpha in enumerate(alpha): - betas_0[ind_alpha, :] = _alpha / 2.0 + betas_0 = np.repeat(alpha[:, np.newaxis] / 2, n, axis=0) lower_quantiles = np_nanquantile( self.conformity_scores_, - betas_0[:, 0], + betas_0[0, :], axis=0, method="lower", - ).T # type: ignore + ).T higher_quantiles = np_nanquantile( self.conformity_scores_, - 1 - alpha_np + betas_0[:, 0], + 1 - alpha_np + betas_0[0, :], axis=0, method="higher", - ).T # type: ignore + ).T if self.cv == "prefit": y_pred_low = y_pred[:, np.newaxis] + lower_quantiles @@ -293,18 +262,8 @@ def predict( pred = aggregate_all(self.agg_function, y_pred_multi) lower_bounds, upper_bounds = pred, pred - y_pred_low = np.column_stack( - [ - lower_bounds + lower_quantiles[k] - for k, _ in enumerate(alpha_np) - ] - ) - y_pred_up = np.column_stack( - [ - upper_bounds + higher_quantiles[k] - for k, _ in enumerate(alpha_np) - ] - ) + y_pred_low = lower_bounds + lower_quantiles.reshape(-1, 1) + y_pred_up = upper_bounds + higher_quantiles.reshape(-1, 1) if ensemble: y_pred = aggregate_all(self.agg_function, y_pred_multi) From 98a87f61f3596efb768193695e84ebeca3e9865f Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 1 Jun 2022 14:24:55 +0200 Subject: [PATCH 24/32] all test pass after remarks of VTA integration --- .../2-advanced-analysis/plot_timeseries_enbpi.py | 10 +++++----- mapie/regression.py | 1 - mapie/subsample.py | 2 +- mapie/tests/test_subsample.py | 2 +- mapie/time_series_regression.py | 7 +++---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index d86ecd1f4..7b5d57c11 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -135,14 +135,14 @@ for step in range(step_size, len(X_test), step_size): mapie_enpbi.partial_fit( - X_test.iloc[(step - step_size) : step, :], - y_test.iloc[(step - step_size) : step], + X_test.iloc[(step - step_size):step, :], + y_test.iloc[(step - step_size):step], ) ( - y_pred_pfit_enbpi[step : step + step_size], - y_pis_pfit_enbpi[step : step + step_size, :, :], + y_pred_pfit_enbpi[step:step + step_size], + y_pis_pfit_enbpi[step:step + step_size, :, :], ) = mapie_enpbi.predict( - X_test.iloc[step : (step + step_size), :], + X_test.iloc[step:(step + step_size), :], alpha=alpha, ensemble=True, optimize_beta=True, diff --git a/mapie/regression.py b/mapie/regression.py index 6150b389c..9d52193de 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -464,7 +464,6 @@ def _pred_multi(self, X: ArrayLike) -> NDArray: y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) return y_pred_multi - def fit( self, X: ArrayLike, diff --git a/mapie/subsample.py b/mapie/subsample.py index 899dbeacc..4f7671669 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -204,7 +204,7 @@ def split( if self.overlapping: blocks = sliding_window_view(indices, window_shape=length) else: - indices = indices[(n % length) :] + indices = indices[(n % length):] blocks_number = n // length blocks = np.asarray( np.array_split(indices, indices_or_sections=blocks_number) diff --git a/mapie/tests/test_subsample.py b/mapie/tests/test_subsample.py index 533a50b7f..22320ffff 100644 --- a/mapie/tests/test_subsample.py +++ b/mapie/tests/test_subsample.py @@ -79,7 +79,7 @@ def test_split_BlockBootstrap_error() -> None: cv = BlockBootstrap() print(cv.length) print(cv.n_blocks) - with pytest.raises(ValueError, match=r".*At least one argument*"): + with pytest.raises(ValueError, match=r".*Exactly one argument*"): next(cv.split(X)) cv = BlockBootstrap(length=20) with pytest.raises(ValueError, match=r".*The length of blocks is <= 0 *"): diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index f34cf0aa9..2e9f6492d 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -5,7 +5,6 @@ import numpy as np from sklearn.base import RegressorMixin from sklearn.model_selection import BaseCrossValidator -from sklearn.utils import check_array from sklearn.utils.validation import check_is_fitted from ._compatibility import np_nanquantile @@ -198,7 +197,7 @@ def partial_fit( self.conformity_scores_, -len(new_conformity_scores_) ) self.conformity_scores_[ - -len(new_conformity_scores_) : + -len(new_conformity_scores_): ] = new_conformity_scores_ return self @@ -264,8 +263,8 @@ def predict( pred = aggregate_all(self.agg_function, y_pred_multi) lower_bounds, upper_bounds = pred, pred - y_pred_low = lower_bounds + lower_quantiles.reshape(-1, 1) - y_pred_up = upper_bounds + higher_quantiles.reshape(-1, 1) + y_pred_low = lower_bounds.reshape(-1, 1) + lower_quantiles + y_pred_up = upper_bounds.reshape(-1, 1) + higher_quantiles if ensemble: y_pred = aggregate_all(self.agg_function, y_pred_multi) From 8590a9cb5c998e1927615244183343f42752db82 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 1 Jun 2022 14:40:14 +0200 Subject: [PATCH 25/32] the maximun test coverage is reached after remarks integration --- mapie/subsample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapie/subsample.py b/mapie/subsample.py index 4f7671669..fd0f5a347 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -190,7 +190,7 @@ def split( self.length if self.length is not None else n // self.n_blocks ) n_blocks = self.n_blocks - elif self.length is not None: + else: length = self.length n_blocks = (n // self.length) + 1 From 9cff9bfba8c78a9625eb31abf4fb03bbdea50e86 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 1 Jun 2022 15:06:59 +0200 Subject: [PATCH 26/32] a bug to be fixed --- mapie/subsample.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mapie/subsample.py b/mapie/subsample.py index fd0f5a347..91843dae0 100644 --- a/mapie/subsample.py +++ b/mapie/subsample.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Generator, Optional, Tuple, Union +from typing import Any, Generator, Optional, Tuple, Union, cast import numpy as np from numpy.lib.stride_tricks import sliding_window_view @@ -191,8 +191,8 @@ def split( ) n_blocks = self.n_blocks else: - length = self.length - n_blocks = (n // self.length) + 1 + length = cast(int, self.length) + n_blocks = (n // length) + 1 indices = np.arange(n) if (length <= 0) or (length > n): From a7c5c57bb77e6ecc749c1bc06c0ddf566e804102 Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Tue, 7 Jun 2022 09:57:14 +0200 Subject: [PATCH 27/32] an import error add to be corrected in the documentation --- .../2-advanced-analysis/plot_timeseries_enbpi.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 7b5d57c11..9c6464cae 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -39,9 +39,10 @@ from mapie.time_series_regression import MapieTimeSeriesRegressor # Load input data and feature engineering -demand_df = pd.read_csv( - "../data/demand_temperature.csv", parse_dates=True, index_col=0 -) +url_file = ("https://raw.githubusercontent.com/scikit-learn-contrib/MAPIE/" + + "master/examples/data/demand_temperature.csv" + ) +demand_df = pd.read_csv(url_file, parse_dates=True, index_col=0) demand_df["Date"] = pd.to_datetime(demand_df.index) demand_df["Weekofyear"] = demand_df.Date.dt.isocalendar().week.astype("int64") From 247e26dfc99183e97b564a01b05d8e1305cbd24f Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Wed, 8 Jun 2022 19:06:02 +0200 Subject: [PATCH 28/32] VTA remarks taken into account --- .../plot_timeseries_enbpi.py | 76 ++++++++--------- mapie/regression.py | 6 +- mapie/tests/test_regression.py | 6 +- mapie/tests/test_time_series_regression.py | 32 ++------ mapie/time_series_regression.py | 81 ++++++++++--------- 5 files changed, 90 insertions(+), 111 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 9c6464cae..0254d5ec2 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -18,15 +18,16 @@ in which the training set is prior to the validation set. The best model is then feeded into :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the -associated prediction intervals. We compare four approaches: with or without -``partial_fit`` called at every step, and following [6]. It appears that +associated prediction intervals. We compare two approaches: with or without +``partial_fit`` called at every step following [6]. It appears that ``partial_fit`` offer higher coverage, but with higher width of PIs and is much slower. """ +import matplotlib +from matplotlib import pylab as plt import numpy as np import pandas as pd -from matplotlib import pylab as plt from scipy.stats import randint from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit @@ -39,9 +40,10 @@ from mapie.time_series_regression import MapieTimeSeriesRegressor # Load input data and feature engineering -url_file = ("https://raw.githubusercontent.com/scikit-learn-contrib/MAPIE/" + - "master/examples/data/demand_temperature.csv" - ) +url_file = ( + "https://raw.githubusercontent.com/scikit-learn-contrib/MAPIE/" + + "master/examples/data/demand_temperature.csv" +) demand_df = pd.read_csv(url_file, parse_dates=True, index_col=0) demand_df["Date"] = pd.to_datetime(demand_df.index) @@ -168,45 +170,37 @@ ) # Plot estimated prediction intervals on test set -fig, (ax1, ax2) = plt.subplots( +fig, axs = plt.subplots( nrows=2, ncols=1, figsize=(30, 25), sharey="row", sharex="col" ) +font = {"family": "normal", "weight": "bold", "size": 22} +matplotlib.rc("font", **font) + -for ax in [ax1, ax2]: +for i, (ax, w) in enumerate( + zip(axs, ["EnbPI, without partial_fit", "EnbPI with partial_fit"]) +): ax.set_ylabel("Hourly demand (GW)") ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") -ax1.plot( - demand_test.index, y_pred_npfit_enbpi, lw=2, c="C2", label="Predictions" -) -ax1.fill_between( - demand_test.index, - y_pis_npfit_enbpi[:, 0, 0], - y_pis_npfit_enbpi[:, 1, 0], - color="C2", - alpha=0.2, - label="MapieTimeSeriesRegressor PIs", -) -ax1.set_title( - "EnbPI, without partial_fit.\n" - f"Coverage:{coverage_npfit_enbpi:.3f} Width:{width_npfit_enbpi:.3f}" -) - -ax2.plot( - demand_test.index, y_pred_pfit_enbpi, lw=2, c="C2", label="Predictions" -) -ax2.fill_between( - demand_test.index, - y_pis_pfit_enbpi[:, 0, 0], - y_pis_pfit_enbpi[:, 1, 0], - color="C2", - alpha=0.2, - label="MapieTimeSeriesRegressor PIs", -) -ax2.set_title( - "EnbPI with partial_fit.\n" - f"Coverage:{coverage_pfit_enbpi:.3f} Width:{width_pfit_enbpi:.3f}" -) - -ax1.legend() + ax.plot( + demand_test.index, + y_pred_npfit_enbpi, + lw=2, + c="C2", + label="Predictions", + ) + ax.fill_between( + demand_test.index, + y_pis_npfit_enbpi[:, 0, 0], + y_pis_npfit_enbpi[:, 1, 0], + color="C2", + alpha=0.2, + label="MapieTimeSeriesRegressor PIs", + ) + ax.set_title( + w + "\n" + f"Coverage:{coverage_npfit_enbpi:.3f} Width:{width_npfit_enbpi:.3f}" + ) +axs[0].legend() plt.show() diff --git a/mapie/regression.py b/mapie/regression.py index 9d52193de..f57e8dadf 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -397,7 +397,7 @@ def _fit_and_predict_oof_model( y_pred = np.array([]) return estimator, y_pred, val_index - def aggregate_with_mask(self, x: NDArray, k: NDArray) -> NDArray: + def _aggregate_with_mask(self, x: NDArray, k: NDArray) -> NDArray: """ Take the array of predictions, made by the refitted estimators, on the testing set, and the 1-nan array indicating for each training @@ -458,10 +458,10 @@ def _pred_multi(self, X: ArrayLike) -> NDArray: ) # At this point, y_pred_multi is of shape # (n_samples_test, n_estimators_). The method - # ``aggregate_with_mask`` fits it to the right size + # ``_aggregate_with_mask`` fits it to the right size # thanks to the shape of k_. - y_pred_multi = self.aggregate_with_mask(y_pred_multi, self.k_) + y_pred_multi = self._aggregate_with_mask(y_pred_multi, self.k_) return y_pred_multi def fit( diff --git a/mapie/tests/test_regression.py b/mapie/tests/test_regression.py index e96198223..251c4c9d5 100644 --- a/mapie/tests/test_regression.py +++ b/mapie/tests/test_regression.py @@ -413,21 +413,21 @@ def test_invalid_aggregate_all() -> None: def test_aggregate_with_mask_with_prefit() -> None: """ - Test ``aggregate_with_mask`` in case ``cv`` is ``"prefit"``. + Test ``_aggregate_with_mask`` in case ``cv`` is ``"prefit"``. """ mapie_reg = MapieRegressor(cv="prefit") with pytest.raises( ValueError, match=r".*There should not be aggregation of predictions if cv is*", ): - mapie_reg.aggregate_with_mask(k, k) + mapie_reg._aggregate_with_mask(k, k) mapie_reg = MapieRegressor(agg_function="nonsense") with pytest.raises( ValueError, match=r".*The value of self.agg_function is not correct*", ): - mapie_reg.aggregate_with_mask(k, k) + mapie_reg._aggregate_with_mask(k, k) def test_pred_loof_isnan() -> None: diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 8caf3d0fd..eb0ae1889 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -197,8 +197,8 @@ def test_prediction_agg_function( method: str, cv: Union[LeaveOneOut, KFold], agg_function: str, alpha: int ) -> None: """ - Test that predictions differ when ensemble is True/False, - but not prediction intervals. + Test that PIs are the same but predictions differ when ensemble is + True or False. """ mapie = MapieTimeSeriesRegressor( method=method, cv=cv, agg_function=agg_function @@ -265,7 +265,10 @@ def test_not_enough_resamplings() -> None: def test_no_agg_fx_specified_with_subsample() -> None: - """Test that a warning is raised if at least one residual is nan.""" + """ + Test that an error is raised if ``cv`` is ``BlockBootstrap`` but + ``agg_function`` is ``None``. + """ with pytest.raises( ValueError, match=r"You need to specify an aggregation*" ): @@ -287,25 +290,6 @@ def test_invalid_aggregate_all() -> None: aggregate_all(None, X) -def test_aggregate_with_mask_with_prefit() -> None: - """ - Test ``aggregate_with_mask`` in case ``cv`` is ``"prefit"``. - """ - mapie_ts_reg = MapieTimeSeriesRegressor(cv="prefit") - with pytest.raises( - ValueError, - match=r".*There should not be aggregation of predictions if cv is*", - ): - mapie_ts_reg.aggregate_with_mask(k, k) - - mapie_ts_reg = MapieTimeSeriesRegressor(agg_function="nonsense") - with pytest.raises( - ValueError, - match=r".*The value of self.agg_function is not correct*", - ): - mapie_ts_reg.aggregate_with_mask(k, k) - - def test_pred_loof_isnan() -> None: """Test that if validation set is empty then prediction is empty.""" mapie_ts_reg = MapieTimeSeriesRegressor() @@ -341,10 +325,10 @@ def test_MapieTimeSeriesRegressor_partial_fit_ensemble() -> None: ) -def test_MapieTimeSeriesRegressor_partial_fit_two_big() -> None: +def test_MapieTimeSeriesRegressor_partial_fit_too_big() -> None: """Test ``partial_fit`` raised error.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) - with pytest.raises(ValueError, match=r".*You try to update more*"): + with pytest.raises(ValueError, match=r".*The number of observations*"): mapie_ts_reg = mapie_ts_reg.partial_fit(X=X, y=y) diff --git a/mapie/time_series_regression.py b/mapie/time_series_regression.py index 2e9f6492d..3fc2b321e 100644 --- a/mapie/time_series_regression.py +++ b/mapie/time_series_regression.py @@ -190,7 +190,8 @@ def partial_fit( n = len(self.conformity_scores_) if len(X) > n: raise ValueError( - "You try to update more residuals than there are!" + "The number of observations to update is higher than the" + "number of training instances." ) new_conformity_scores_ = self._relative_conformity_scores(X, y) self.conformity_scores_ = np.roll( @@ -227,46 +228,46 @@ def predict( if alpha is None: return np.array(y_pred) - else: - alpha_np = cast(NDArray, alpha) - check_alpha_and_n_samples(alpha_np, n) - - if optimize_beta: - betas_0 = self._beta_optimize( - alpha_np, - self.conformity_scores_.reshape(1, -1), - self.conformity_scores_.reshape(1, -1), - ) - else: - betas_0 = np.repeat(alpha[:, np.newaxis] / 2, n, axis=0) - - lower_quantiles = np_nanquantile( - self.conformity_scores_, - betas_0[0, :], - axis=0, - method="lower", - ).T - higher_quantiles = np_nanquantile( - self.conformity_scores_, - 1 - alpha_np + betas_0[0, :], - axis=0, - method="higher", - ).T - self.lower_quantiles_ = lower_quantiles - self.higher_quantiles_ = higher_quantiles - if self.cv == "prefit": - y_pred_low = y_pred[:, np.newaxis] + lower_quantiles - y_pred_up = y_pred[:, np.newaxis] + higher_quantiles - else: - y_pred_multi = self._pred_multi(X) - pred = aggregate_all(self.agg_function, y_pred_multi) - lower_bounds, upper_bounds = pred, pred + alpha_np = cast(NDArray, alpha) + check_alpha_and_n_samples(alpha_np, n) + + if optimize_beta: + betas_0 = self._beta_optimize( + alpha_np, + self.conformity_scores_.reshape(1, -1), + self.conformity_scores_.reshape(1, -1), + ) + else: + betas_0 = np.repeat(alpha[:, np.newaxis] / 2, n, axis=0) + + lower_quantiles = np_nanquantile( + self.conformity_scores_, + betas_0[0, :], + axis=0, + method="lower", + ).T + higher_quantiles = np_nanquantile( + self.conformity_scores_, + 1 - alpha_np + betas_0[0, :], + axis=0, + method="higher", + ).T + self.lower_quantiles_ = lower_quantiles + self.higher_quantiles_ = higher_quantiles + + if self.cv == "prefit": + y_pred_low = y_pred[:, np.newaxis] + lower_quantiles + y_pred_up = y_pred[:, np.newaxis] + higher_quantiles + else: + y_pred_multi = self._pred_multi(X) + pred = aggregate_all(self.agg_function, y_pred_multi) + lower_bounds, upper_bounds = pred, pred - y_pred_low = lower_bounds.reshape(-1, 1) + lower_quantiles - y_pred_up = upper_bounds.reshape(-1, 1) + higher_quantiles + y_pred_low = lower_bounds.reshape(-1, 1) + lower_quantiles + y_pred_up = upper_bounds.reshape(-1, 1) + higher_quantiles - if ensemble: - y_pred = aggregate_all(self.agg_function, y_pred_multi) + if ensemble: + y_pred = aggregate_all(self.agg_function, y_pred_multi) - return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) + return y_pred, np.stack([y_pred_low, y_pred_up], axis=1) From e1edc5fe5829304038153f9f42bbb9b059b454ca Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Thu, 9 Jun 2022 09:37:06 +0200 Subject: [PATCH 29/32] a bug fixed in enbpi example --- .../plot_timeseries_enbpi.py | 35 +++++++++++++++---- mapie/tests/test_time_series_regression.py | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 0254d5ec2..84789c84b 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -24,6 +24,8 @@ slower. """ +from typing import cast + import matplotlib from matplotlib import pylab as plt import numpy as np @@ -36,6 +38,7 @@ regression_coverage_score, regression_mean_width_score, ) +from mapie._typing import NDArray from mapie.subsample import BlockBootstrap from mapie.time_series_regression import MapieTimeSeriesRegressor @@ -169,6 +172,22 @@ f"{coverage_pfit_enbpi:.3f}, {width_pfit_enbpi:.3f}" ) +enbpi_no_pfit = { + "y_pred": y_pred_npfit_enbpi, + "y_pis": y_pis_npfit_enbpi, + "coverage": coverage_npfit_enbpi, + "width": width_npfit_enbpi, +} + +enbpi_pfit = { + "y_pred": y_pred_pfit_enbpi, + "y_pis": y_pis_pfit_enbpi, + "coverage": coverage_pfit_enbpi, + "width": width_pfit_enbpi, +} + +results = [enbpi_no_pfit, enbpi_pfit] + # Plot estimated prediction intervals on test set fig, axs = plt.subplots( nrows=2, ncols=1, figsize=(30, 25), sharey="row", sharex="col" @@ -177,30 +196,34 @@ matplotlib.rc("font", **font) -for i, (ax, w) in enumerate( - zip(axs, ["EnbPI, without partial_fit", "EnbPI with partial_fit"]) +for i, (ax, w, result) in enumerate( + zip(axs, ["EnbPI, without partial_fit", "EnbPI with partial_fit"], results) ): ax.set_ylabel("Hourly demand (GW)") ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") ax.plot( demand_test.index, - y_pred_npfit_enbpi, + result["y_pred"], lw=2, c="C2", label="Predictions", ) + + y_pis = cast(NDArray, result["y_pis"]) + ax.fill_between( demand_test.index, - y_pis_npfit_enbpi[:, 0, 0], - y_pis_npfit_enbpi[:, 1, 0], + y_pis[:, 0, 0], + y_pis[:, 1, 0], color="C2", alpha=0.2, label="MapieTimeSeriesRegressor PIs", ) + ax.set_title( w + "\n" - f"Coverage:{coverage_npfit_enbpi:.3f} Width:{width_npfit_enbpi:.3f}" + f"Coverage:{result['coverage']:.3f} Width:{result['width']:.3f}" ) axs[0].legend() plt.show() diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index eb0ae1889..02ea8da0c 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -303,7 +303,7 @@ def test_pred_loof_isnan() -> None: assert len(y_pred) == 0 -def test_MapieTimeSeriesRegressor_alpha_is_None() -> None: +def test_MapieTimeSeriesRegressor_if_alpha_is_None() -> None: """Test ``predict`` when ``alpha`` is None.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) From 2af9f36c866ecb636955ec9d484c2739d204e02c Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 13 Jun 2022 14:26:11 +0200 Subject: [PATCH 30/32] Last remarks of VTA taken into account --- .../2-advanced-analysis/plot_timeseries_enbpi.py | 6 +++--- mapie/regression.py | 2 +- mapie/tests/test_time_series_regression.py | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 84789c84b..6b1dac7ef 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -20,8 +20,8 @@ :class:`mapie.time_series_regression.MapieTimeSeriesRegressor` to estimate the associated prediction intervals. We compare two approaches: with or without ``partial_fit`` called at every step following [6]. It appears that -``partial_fit`` offer higher coverage, but with higher width of PIs and is much -slower. +``partial_fit`` offer a coverage closer to the targeted coverage, and with +narrower PIs. """ from typing import cast @@ -190,7 +190,7 @@ # Plot estimated prediction intervals on test set fig, axs = plt.subplots( - nrows=2, ncols=1, figsize=(30, 25), sharey="row", sharex="col" + nrows=2, ncols=1, figsize=(30, 25), sharex="col" ) font = {"family": "normal", "weight": "bold", "size": 22} matplotlib.rc("font", **font) diff --git a/mapie/regression.py b/mapie/regression.py index f57e8dadf..e4fc66efb 100644 --- a/mapie/regression.py +++ b/mapie/regression.py @@ -400,7 +400,7 @@ def _fit_and_predict_oof_model( def _aggregate_with_mask(self, x: NDArray, k: NDArray) -> NDArray: """ Take the array of predictions, made by the refitted estimators, - on the testing set, and the 1-nan array indicating for each training + on the testing set, and the 1-or-nan array indicating for each training sample which one to integrate, and aggregate to produce phi-{t}(x_t) for each training sample x_t. diff --git a/mapie/tests/test_time_series_regression.py b/mapie/tests/test_time_series_regression.py index 02ea8da0c..abc5f9ad3 100644 --- a/mapie/tests/test_time_series_regression.py +++ b/mapie/tests/test_time_series_regression.py @@ -20,6 +20,8 @@ X, y = make_regression(n_samples=500, n_features=10, noise=1.0, random_state=1) k = np.ones(shape=(5, X.shape[1])) METHODS = ["enbpi"] +UPDATE_DATA = ([6], 17.5) +CONFORMITY_SCORES = [14.189 - 14.038, 17.5 - 18.665] Params = TypedDict( "Params", @@ -315,13 +317,13 @@ def test_MapieTimeSeriesRegressor_partial_fit_ensemble() -> None: """Test ``partial_fit``.""" mapie_ts_reg = MapieTimeSeriesRegressor(cv=-1).fit(X_toy, y_toy) assert round(mapie_ts_reg.conformity_scores_[-1], 2) == round( - np.abs(14.189 - 14.038), 2 + np.abs(CONFORMITY_SCORES[0]), 2 ) mapie_ts_reg = mapie_ts_reg.partial_fit( - X=np.array([[6]]), y=np.array([17.5]) + X=np.array([UPDATE_DATA[0]]), y=np.array([UPDATE_DATA[1]]) ) assert round(mapie_ts_reg.conformity_scores_[-1], 2) == round( - 17.5 - 18.665, 2 + CONFORMITY_SCORES[1], 2 ) From c3234fa99a46774cd514d6628a9fce19afbd3e7f Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 13 Jun 2022 14:56:55 +0200 Subject: [PATCH 31/32] merge with master before push for release --- .../regression/2-advanced-analysis/plot_timeseries_enbpi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index 6b1dac7ef..c980e83aa 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -192,7 +192,7 @@ fig, axs = plt.subplots( nrows=2, ncols=1, figsize=(30, 25), sharex="col" ) -font = {"family": "normal", "weight": "bold", "size": 22} +font = {"weight": "bold", "size": 22} matplotlib.rc("font", **font) From 3f779ecfd89e4104eddefc79f82d9f4945e6318e Mon Sep 17 00:00:00 2001 From: Thomas Morzadec Date: Mon, 13 Jun 2022 18:54:04 +0200 Subject: [PATCH 32/32] plot_enbpi so nice --- .../plot_timeseries_enbpi.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py index c980e83aa..80212f847 100644 --- a/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py +++ b/examples/regression/2-advanced-analysis/plot_timeseries_enbpi.py @@ -26,7 +26,6 @@ from typing import cast -import matplotlib from matplotlib import pylab as plt import numpy as np import pandas as pd @@ -190,16 +189,13 @@ # Plot estimated prediction intervals on test set fig, axs = plt.subplots( - nrows=2, ncols=1, figsize=(30, 25), sharex="col" + nrows=2, ncols=1, figsize=(15, 12), sharex="col" ) -font = {"weight": "bold", "size": 22} -matplotlib.rc("font", **font) - for i, (ax, w, result) in enumerate( zip(axs, ["EnbPI, without partial_fit", "EnbPI with partial_fit"], results) ): - ax.set_ylabel("Hourly demand (GW)") + ax.set_ylabel("Hourly demand (GW)", fontsize=20) ax.plot(demand_test.Demand, lw=2, label="Test data", c="C1") ax.plot( @@ -223,7 +219,12 @@ ax.set_title( w + "\n" - f"Coverage:{result['coverage']:.3f} Width:{result['width']:.3f}" + f"Coverage:{result['coverage']:.3f} Width:{result['width']:.3f}", + fontweight="bold", + size=20 ) -axs[0].legend() + plt.xticks(size=15, rotation=45) + plt.yticks(size=15) + +axs[0].legend(prop={'size': 22}) plt.show()