def test_strategy_last(fh): """Test last strategy.""" f = NaiveForecaster(strategy="last") f.fit(y_train) y_pred = f.predict(fh) expected = np.repeat(y_train.iloc[-1], len(f.fh)) np.testing.assert_array_equal(y_pred, expected)
def test_strategy_mean_seasonal(fh, sp, window_length): if window_length > sp or window_length is None: f = NaiveForecaster(strategy="mean", sp=sp, window_length=window_length) f.fit(y_train) y_pred = f.predict(fh) # check predicted index np.testing.assert_array_equal(y_train.index[-1] + check_fh(fh), y_pred.index) if window_length is None: window_length = len(y_train) # check values fh = check_fh(fh) # get well formatted fh reps = np.int(np.ceil(max(fh) / sp)) last_window = y_train.iloc[-window_length:].values last_window = np.pad(last_window, (0, sp - len(last_window) % sp), 'constant', constant_values=np.nan) last_window = last_window.reshape( np.int(np.ceil(len(last_window) / sp)), sp) expected = np.tile(np.nanmean(last_window, axis=0), reps=reps)[fh - 1] np.testing.assert_array_equal(y_pred, expected)
def forecasting_example(): name = "C:\\Users\\Tony\\OneDrive - University of East Anglia\\Research\\Alex " \ "Mcgregor Grant\\randomNoise.csv" y = pd.read_csv(name, index_col=0, squeeze=True, dtype={1: np.float}) forecast_horizon = np.arange(1, 2) forecaster = NaiveForecaster(strategy="last") forecaster.fit(y) y_pred = forecaster.predict(forecast_horizon) print("Next predicted value = ",y_pred) # https://github.com/alan-turing-institute/sktime/blob/main/examples/01_forecasting.ipynb #Reduce to a regression problem through windowing. ##Transform forecasting into regression np_y = y.to_numpy() v = sliding_window_view(y, 100) print("Window shape =",v.shape) v_3d = np.expand_dims(v, axis=1) print("Window shape =",v.shape) print(v_3d.shape) z = v[:,2] print(z.shape) regressor = CNNRegressor() classifier = CNNClassifier() regressor.fit(v_3d,z) p = regressor.predict(v_3d) #print(p) d = np.array([0.0]) c = np.digitize(z,d) classifier = RandomIntervalSpectralForest() classifier.fit(v_3d,c) cls = classifier.predict(v_3d) print(cls)
def test_strategy_mean_seasonal(fh, sp, window_length): if (window_length is not None and window_length > sp) or (window_length is None): f = NaiveForecaster(strategy="mean", sp=sp, window_length=window_length) f.fit(y_train) y_pred = f.predict(fh) # check predicted index _assert_correct_pred_time_index(y_pred.index, y_train.index[-1], fh) if window_length is None: window_length = len(y_train) # check values fh = check_fh(fh) # get well formatted fh reps = int(np.ceil(max(fh) / sp)) last_window = y_train.iloc[-window_length:].to_numpy().astype(float) last_window = np.pad( last_window, (sp - len(last_window) % sp, 0), "constant", constant_values=np.nan, ) last_window = last_window.reshape(int(np.ceil(len(last_window) / sp)), sp) expected = np.tile(np.nanmean(last_window, axis=0), reps=reps)[fh - 1] np.testing.assert_array_equal(y_pred, expected)
def get_test_params(cls): """Return testing parameter settings for the estimator. Returns ------- params : dict or list of dict, default = {} Parameters to create testing instances of the class Each dict are parameters to construct an "interesting" test instance, i.e., `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. `create_test_instance` uses the first (or only) dictionary in `params` """ from sklearn.preprocessing import StandardScaler from sktime.forecasting.naive import NaiveForecaster from sktime.transformations.series.adapt import TabularToSeriesAdaptor from sktime.transformations.series.boxcox import BoxCoxTransformer STEPS1 = [ ("transformer", TabularToSeriesAdaptor(StandardScaler())), ("forecaster", NaiveForecaster()), ] params1 = {"steps": STEPS1} STEPS2 = [ ("transformer", BoxCoxTransformer()), ("forecaster", NaiveForecaster()), ] params2 = {"steps": STEPS2} return [params1, params2]
def test_multiplex_or_dunder(): """Test that the MultiplexForecaster magic "|" dunder methodbahves as expected. A MultiplexForecaster can be created by using the "|" dunder method on either forecaster or MultiplexForecaster objects. Here we test that it performs as expected on all the use cases, and raises the expected error in some others. """ # test a simple | example with two forecasters: multiplex_two_forecaster = AutoETS() | NaiveForecaster() assert isinstance(multiplex_two_forecaster, MultiplexForecaster) assert len(multiplex_two_forecaster.forecasters) == 2 # now test that | also works on two MultiplexForecasters: multiplex_one = MultiplexForecaster([("arima", AutoARIMA()), ("ets", AutoETS())]) multiplex_two = MultiplexForecaster([("theta", ThetaForecaster()), ("naive", NaiveForecaster())]) multiplex_two_multiplex = multiplex_one | multiplex_two assert isinstance(multiplex_two_multiplex, MultiplexForecaster) assert len(multiplex_two_multiplex.forecasters) == 4 # last we will check 3 forecaster with the same name - should check both that # MultiplexForecaster | forecaster works, and that ensure_unique_names works multiplex_same_name_three_test = (NaiveForecaster(strategy="last") | NaiveForecaster(strategy="mean") | NaiveForecaster(strategy="drift")) assert isinstance(multiplex_same_name_three_test, MultiplexForecaster) assert len(multiplex_same_name_three_test.forecasters) == 3 assert (len( set( multiplex_same_name_three_test._get_estimator_names( multiplex_same_name_three_test.forecasters))) == 3) # test we get a ValueError if we try to | with anything else: with pytest.raises(TypeError): multiplex_one | "this shouldn't work"
def test_strategy_mean(fh, window_length): f = NaiveForecaster(strategy="mean", window_length=window_length) f.fit(y_train) y_pred = f.predict(fh) if window_length is None: window_length = len(y_train) expected = np.repeat(y_train.iloc[-window_length:].mean(), len(f.fh)) np.testing.assert_array_equal(y_pred, expected)
def construct_M4_forecasters(sp, fh): kwargs = {"model": SEASONAL_MODEL, "sp": sp} if sp > 1 else {} theta_bc = make_pipeline( ConditionalDeseasonalizer(seasonality_test=seasonality_test_R, **kwargs), BoxCoxTransformer(bounds=(0, 1)), ThetaForecaster(deseasonalise=False)) """ MLP = make_pipeline( ConditionalDeseasonalizer(seasonality_test=seasonality_test_Python, **kwargs), Detrender(PolynomialTrendForecaster(degree=1, with_intercept=True)), RecursiveRegressionForecaster( regressor=MLPRegressor(hidden_layer_sizes=6, activation="identity", solver="adam", max_iter=100, learning_rate="adaptive", learning_rate_init=0.001), window_length=3) ) RNN = make_pipeline( ConditionalDeseasonalizer(seasonality_test=seasonality_test_Python, **kwargs), Detrender(PolynomialTrendForecaster(degree=1, with_intercept=True)), RecursiveTimeSeriesRegressionForecaster( regressor=SimpleRNNRegressor(nb_epochs=100), window_length=3) ) """ forecasters = { "Naive": NaiveForecaster(strategy="last"), "sNaive": NaiveForecaster(strategy="seasonal_last", sp=sp), "Naive2": deseasonalise(NaiveForecaster(strategy="last"), **kwargs), "SES": deseasonalise(ses, **kwargs), "Holt": deseasonalise(holt, **kwargs), "Damped": deseasonalise(damped, **kwargs), "Theta": deseasonalise(ThetaForecaster(deseasonalise=False), **kwargs), "ARIMA": AutoARIMA(suppress_warnings=True, error_action="ignore", sp=sp), "Com": deseasonalise( EnsembleForecaster([("ses", ses), ("holt", holt), ("damped", damped)]), **kwargs), # "MLP": MLP, # "RNN": RNN, "260": theta_bc, } return forecasters
def test_strategy_mean_seasonal_additional_combinations(n, window_length, sp): """Check time series of n * window_length with a 1:n-1 train/test split, for different combinations of the period and seasonal periodicity. The time series contains perfectly cyclic data. """ # given <window_length> hours of data with a seasonal periodicity of <sp> hours freq = pd.Timedelta("1H") data = pd.Series( index=pd.date_range("2021-06-01 00:00", periods=n * window_length, freq=freq, closed="left"), data=([float(i) for i in range(1, sp + 1)] * n * window_length)[:n * window_length], ) # Split into train and test data train_data = data[:window_length] test_data = data[window_length:] # Forecast data does not retain the original frequency test_data.index.freq = None # For example, for n=2, periods=4 and sp=3: # print(train_data) # 2021-06-01 00:00:00 1.0 # 2021-06-01 01:00:00 2.0 # 2021-06-01 02:00:00 3.0 # 2021-06-01 03:00:00 1.0 # Freq: H, dtype: int64 # print(test_data) # 2021-06-01 04:00:00 2.0 # (value of 3 hours earlier) # 2021-06-01 05:00:00 3.0 # (value of 3 hours earlier) # 2021-06-01 06:00:00 1.0 # (mean value of 3 and 6 hours earlier) # 2021-06-01 07:00:00 2.0 # (value of 6 hours earlier) # dtype: float64 # let's forecast the next <2 x period> hours with a periodicity of <sp> hours fh = ForecastingHorizon(test_data.index, is_relative=False) model = NaiveForecaster(strategy="mean", sp=sp) model.fit(train_data) forecast_data = model.predict(fh) if sp < window_length: # We expect a perfect forecast given our perfectly cyclic data pd.testing.assert_series_equal(forecast_data, test_data) else: # We expect a few forecasts yield NaN values for i in range(1 + len(test_data) // sp): test_data[i * sp:i * sp + sp - window_length] = np.nan pd.testing.assert_series_equal(forecast_data, test_data)
def test_strategy_last_seasonal(fh, sp): f = NaiveForecaster(strategy="last", sp=sp) f.fit(y_train) y_pred = f.predict(fh) # check predicted index _assert_correct_pred_time_index(y_pred.index, y_train.index[-1], fh) # check values fh = check_fh(fh) # get well formatted fh reps = int(np.ceil(max(fh) / sp)) expected = np.tile(y_train.iloc[-sp:], reps=reps)[fh - 1] np.testing.assert_array_equal(y_pred, expected)
def sma_forecast(y_train: pd.Series, forecast_horizon: np.array) -> pd.Series: """ Fit a simple moving average model with training data and forecast for a given horizon. Args: y_train: Historic dataset to fit model. forecast_horizon: Array of forecast periods [1, ... , n] n being number of desired periods to forecast. Returns: A pandas series of consumption forecast with a datetimeindex. """ forecaster = NaiveForecaster(strategy="mean", window_length=7) forecaster.fit(y_train) forecast = forecaster.predict(forecast_horizon).rename("consumption") return forecast
def test_strategy_mean_seasonal_simple(n_seasons, sp): """Create 2d matrix (seasons on rows, time points of each season on columns).""" values = np.random.normal(size=(n_seasons, sp)) y = pd.Series(values.ravel()) expected = values.mean(axis=0) assert expected.shape == (sp, ) f = NaiveForecaster(strategy="mean", sp=sp) f.fit(y) fh = np.arange(1, sp + 1) y_pred = f.predict(fh) np.testing.assert_array_equal(y_pred, expected)
def _fit(self, y, X=None, fh=None): """Fit forecaster to training data. Parameters ---------- y : pd.Series Target time series to which to fit the forecaster. fh : int, list, np.array or ForecastingHorizon, optional (default=None) The forecasters horizon with the steps ahead to to predict. X : pd.DataFrame, optional (default=None) Returns ------- self : returns an instance of self. """ self._stl = _STL( y.values, period=self.sp, seasonal=self.seasonal, trend=self.trend, low_pass=self.low_pass, seasonal_deg=self.seasonal_deg, trend_deg=self.trend_deg, low_pass_deg=self.low_pass_deg, robust=self.robust, seasonal_jump=self.seasonal_jump, trend_jump=self.trend_jump, low_pass_jump=self.low_pass_jump, ).fit(inner_iter=self.inner_iter, outer_iter=self.outer_iter) self.seasonal_ = pd.Series(self._stl.seasonal, index=y.index) self.resid_ = pd.Series(self._stl.resid, index=y.index) self.trend_ = pd.Series(self._stl.trend, index=y.index) self.forecaster_seasonal_ = (NaiveForecaster( sp=self.sp, strategy="last") if self.forecaster_seasonal is None else clone(self.forecaster_seasonal)) # trend forecaster does not need sp self.forecaster_trend_ = (NaiveForecaster( strategy="drift") if self.forecaster_trend is None else clone( self.forecaster_trend)) self.forecaster_resid_ = (NaiveForecaster(sp=self.sp, strategy="mean") if self.forecaster_resid is None else clone( self.forecaster_resid)) # fitting forecasters to different components self.forecaster_seasonal_.fit(y=self.seasonal_, X=X, fh=fh) self.forecaster_trend_.fit(y=self.trend_, X=X, fh=fh) self.forecaster_resid_.fit(y=self.resid_, X=X, fh=fh)
def compute_expected_y_pred(y_train, fh): # fitting yt = y_train.copy() t1 = ExponentTransformer() yt = t1.fit_transform(yt) t2 = TabularToSeriesAdaptor(MinMaxScaler()) yt = t2.fit_transform(yt) forecaster = NaiveForecaster() forecaster.fit(yt, fh=fh) # predicting y_pred = forecaster.predict() y_pred = t2.inverse_transform(y_pred) y_pred = t1.inverse_transform(y_pred) return y_pred
def test_strategy_drift_unit_slope(fh, window_length): # drift strategy for constant slope 1 if window_length != 1: f = NaiveForecaster(strategy="drift", window_length=window_length) f.fit(y_train) y_pred = f.predict(fh) if window_length is None: window_length = len(y_train) # get well formatted fh values fh = check_fh(fh) expected = y_train.iloc[-1] + np.arange(0, max(fh) + 1)[fh] np.testing.assert_array_equal(y_pred, expected)
def compute_expected_y_pred(y_train, fh): # fitting yt = y_train.copy() t1 = Deseasonalizer(sp=12, model="multiplicative") yt = t1.fit_transform(yt) t2 = Detrender(PolynomialTrendForecaster(degree=1)) yt = t2.fit_transform(yt) forecaster = NaiveForecaster() forecaster.fit(yt, fh=fh) # predicting y_pred = forecaster.predict() y_pred = t2.inverse_transform(y_pred) y_pred = t1.inverse_transform(y_pred) return y_pred
def test_multiplex_with_grid_search(): """Test MultiplexForecaster perfromas as expected with ForecastingGridSearchCV. Because the typical use case of MultiplexForecaster is to use it with the ForecastingGridSearchCV forecaster - here we simply test that the best "selected_forecaster" for MultiplexForecaster found using ForecastingGridSearchCV is the same forecaster we would find if we evaluated all the forecasters in MultiplexForecaster independently. """ y = load_shampoo_sales() forecasters = [ ("ets", AutoETS()), ("naive", NaiveForecaster()), ] multiplex_forecaster = MultiplexForecaster(forecasters=forecasters) forecaster_names = [name for name, _ in forecasters] cv = ExpandingWindowSplitter(start_with_window=True, step_length=12) gscv = ForecastingGridSearchCV( cv=cv, param_grid={"selected_forecaster": forecaster_names}, forecaster=multiplex_forecaster, ) gscv.fit(y) gscv_best_name = gscv.best_forecaster_.selected_forecaster best_name = _score_forecasters(forecasters, cv, y) assert gscv_best_name == best_name
def test_evaluate_common_configs(CV, fh, window_length, step_length, strategy, scoring): """Test evaluate common configs.""" y = make_forecasting_problem(n_timepoints=30, index_type="int") forecaster = NaiveForecaster() cv = CV(fh, window_length, step_length=step_length) out = evaluate(forecaster=forecaster, y=y, cv=cv, strategy=strategy, scoring=scoring) _check_evaluate_output(out, cv, y, scoring) # check scoring actual = out.loc[:, f"test_{scoring.name}"] n_splits = cv.get_n_splits(y) expected = np.empty(n_splits) for i, (train, test) in enumerate(cv.split(y)): f = clone(forecaster) f.fit(y.iloc[train], fh=fh) expected[i] = scoring(y.iloc[test], f.predict(), y_train=y.iloc[train]) np.testing.assert_array_equal(actual, expected)
def test_strategy_drift_flat_line(fh, window_length): # test for flat time series data if window_length != 1: y_train = pd.Series(np.ones(20)) f = NaiveForecaster(strategy="drift", window_length=window_length) f.fit(y_train) y_pred = f.predict(fh) if window_length is None: window_length = len(y_train) # get well formatted fh values fh = check_fh(fh) expected = np.ones(len(fh)) np.testing.assert_array_equal(y_pred, expected)
def test_pipeline(): y = load_airline() y_train, y_test = temporal_train_test_split(y) forecaster = TransformedTargetForecaster([ ("t1", Deseasonalizer(sp=12, model="multiplicative")), ("t2", Detrender(PolynomialTrendForecaster(degree=1))), ("forecaster", NaiveForecaster()), ]) fh = np.arange(len(y_test)) + 1 forecaster.fit(y_train, fh=fh) actual = forecaster.predict() def compute_expected_y_pred(y_train, fh): # fitting yt = y_train.copy() t1 = Deseasonalizer(sp=12, model="multiplicative") yt = t1.fit_transform(yt) t2 = Detrender(PolynomialTrendForecaster(degree=1)) yt = t2.fit_transform(yt) forecaster = NaiveForecaster() forecaster.fit(yt, fh=fh) # predicting y_pred = forecaster.predict() y_pred = t2.inverse_transform(y_pred) y_pred = t1.inverse_transform(y_pred) return y_pred expected = compute_expected_y_pred(y_train, fh) np.testing.assert_array_equal(actual, expected)
def test_pipeline(): """Test results of TransformedTargetForecaster.""" y = load_airline() y_train, y_test = temporal_train_test_split(y) forecaster = TransformedTargetForecaster([ ("t1", ExponentTransformer()), ("t2", TabularToSeriesAdaptor(MinMaxScaler())), ("forecaster", NaiveForecaster()), ]) fh = np.arange(len(y_test)) + 1 forecaster.fit(y_train, fh=fh) actual = forecaster.predict() def compute_expected_y_pred(y_train, fh): # fitting yt = y_train.copy() t1 = ExponentTransformer() yt = t1.fit_transform(yt) t2 = TabularToSeriesAdaptor(MinMaxScaler()) yt = t2.fit_transform(yt) forecaster = NaiveForecaster() forecaster.fit(yt, fh=fh) # predicting y_pred = forecaster.predict() y_pred = t2.inverse_transform(y_pred) y_pred = t1.inverse_transform(y_pred) return y_pred expected = compute_expected_y_pred(y_train, fh) np.testing.assert_array_equal(actual, expected)
def get_test_params(cls, parameter_set="default"): """Return testing parameter settings for the estimator. Parameters ---------- parameter_set : str, default="default" Name of the set of test parameters to return, for use in tests. If no special parameters are defined for a value, will return `"default"` set. Returns ------- params : dict or list of dict, default={} Parameters to create testing instances of the class. Each dict are parameters to construct an "interesting" test instance, i.e., `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance. `create_test_instance` uses the first (or only) dictionary in `params`. """ # imports from sktime.forecasting.naive import NaiveForecaster from sktime.forecasting.theta import ThetaForecaster params1 = {"forecasters": NaiveForecaster()} params2 = {"forecasters": ThetaForecaster()} return [params1, params2]
def test_multiplex_forecaster_alone(): """Test results of MultiplexForecaster. Because MultiplexForecaster is in many ways a wrapper for an underlying forecaster - we can confirm that if the selected_forecaster is set that the MultiplexForecaster performs as expected. """ from numpy.testing import assert_array_equal y = load_shampoo_sales() # Note - we select two forecasters which are deterministic. forecaster_tuples = [ ("naive", NaiveForecaster()), ("theta", ThetaForecaster()), ] forecaster_names = [name for name, _ in forecaster_tuples] forecasters = [forecaster for _, forecaster in forecaster_tuples] multiplex_forecaster = MultiplexForecaster(forecasters=forecaster_tuples) fh_test = [1, 2, 3] # for each of the forecasters - check that the wrapped forecaster predictions # agree with the unwrapped forecaster predictions! for ind, name in enumerate(forecaster_names): # make a copy to ensure we don't reference the same objectL test_forecaster = clone(forecasters[ind]) test_forecaster.fit(y) multiplex_forecaster.selected_forecaster = name # Note- MultiplexForecaster will make a copy of the forecaster before fitting. multiplex_forecaster.fit(y) y_pred_indiv = test_forecaster.predict(fh=fh_test) y_pred_multi = multiplex_forecaster.predict(fh=fh_test) assert_array_equal(y_pred_indiv, y_pred_multi)
def get_test_params(cls): """Return testing parameter settings for the estimator. Returns ------- params : dict or list of dict """ from sktime.forecasting.naive import NaiveForecaster params = { "forecasters": [ ("Naive_mean", NaiveForecaster(strategy="mean")), ("Naive_last", NaiveForecaster(strategy="last")), ("Naive_drift", NaiveForecaster(strategy="drift")), ], "selected_forecaster": "Naive_mean", } return params
def run_sktimes(dept_id, store_id): # create timeseries for fbprophet ts = CreateTimeSeries(dept_id, store_id) # sktime ensembler forecaster = EnsembleForecaster([ ('naive_ses', NaiveForecaster(sp=28, strategy="seasonal_last")), ('naive', NaiveForecaster(strategy="last")), ('theta_ses', ThetaForecaster(sp=28)), ('theta', ThetaForecaster()), ("exp_ses", ExponentialSmoothing(seasonal="additive", sp=28)), ("exp_damped", ExponentialSmoothing(trend='additive', damped=True, seasonal="additive", sp=28)) ]) forecaster.fit(ts.y + 1) y_pred = forecaster.predict(np.arange(1, 29)) return np.append(np.array([dept_id, store_id]), y_pred - 1)
def test_strategy_drift_window_length(fh, window_length): # test for checking if window_length is properly working if window_length != 1: if window_length is None: window_length = len(y_train) values = np.random.normal(size=window_length) y = pd.Series(values) f = NaiveForecaster(strategy="drift", window_length=window_length) f.fit(y) y_pred = f.predict(fh) slope = (values[-1] - values[0]) / (window_length - 1) # get well formatted fh values fh = check_fh(fh) expected = values[-1] + slope * fh np.testing.assert_array_equal(y_pred, expected)
def test_weights_for_airline_nnls(): y = load_airline() y_train, y_test = temporal_train_test_split(y) hedge_expert = NNLSEnsemble(n_estimators=3, loss_func=mean_squared_error) forecaster = OnlineEnsembleForecaster( [ ("av5", NaiveForecaster(strategy="mean", window_length=5)), ("av10", NaiveForecaster(strategy="mean", window_length=10)), ("av20", NaiveForecaster(strategy="mean", window_length=20)), ], ensemble_algorithm=hedge_expert, ) forecaster.fit(y_train) forecaster.update_predict(y_test) expected = np.array([0.04720766, 0, 1.03410876]) np.testing.assert_allclose(forecaster.weights, expected, atol=1e-8)
def test_weights_for_airline_normal_hedge(): """Test weights.""" y = load_airline() y_train, y_test = temporal_train_test_split(y) hedge_expert = NormalHedgeEnsemble(n_estimators=3, loss_func=mean_squared_error) forecaster = OnlineEnsembleForecaster( [ ("av5", NaiveForecaster(strategy="mean", window_length=5)), ("av10", NaiveForecaster(strategy="mean", window_length=10)), ("av20", NaiveForecaster(strategy="mean", window_length=20)), ], ensemble_algorithm=hedge_expert, ) forecaster.fit(y_train) forecaster.update_predict(y=y_test, cv=cv, reset_forecaster=False) expected = np.array([0.17077154, 0.48156709, 0.34766137]) np.testing.assert_allclose(forecaster.weights, expected, atol=1e-8)
def get_test_params(cls): """Return testing parameter settings for the estimator. Returns ------- params : dict or list of dict """ from sktime.forecasting.naive import NaiveForecaster FORECASTER = NaiveForecaster() params = {"forecasters": [("f1", FORECASTER), ("f2", FORECASTER)]} return params
def test_evaluate(): y = load_airline() forecaster = NaiveForecaster(strategy="drift", sp=12) cv = ExpandingWindowSplitter( initial_window=24, step_length=24, fh=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], window_length=10, ) df = evaluate(forecaster=forecaster, y=y, cv=cv, strategy="update") # just making sure the function is running assert isinstance(df, pd.DataFrame)