def custom_tscv():
    # periods_between_splits is less than forecast_horizon
    # so there are
    return RollingTimeSeriesSplit(forecast_horizon=2 * 7,
                                  min_train_periods=10 * 7,
                                  expanding_window=True,
                                  use_most_recent_splits=True,
                                  periods_between_splits=1 * 7,
                                  periods_between_train_test=2 * 7,
                                  max_splits=3)
def test_rolling_time_series_split():
    """Tests rolling_time_series_split with default values."""
    tscv = RollingTimeSeriesSplit(forecast_horizon=3, max_splits=None)
    assert tscv.forecast_horizon == 3
    assert tscv.min_train_periods == 6
    assert not tscv.expanding_window
    assert not tscv.use_most_recent_splits
    assert tscv.periods_between_splits == 3
    assert tscv.periods_between_train_test == 0

    X = np.random.rand(20, 2)
    expected = np.array(
        [  # offset applied, first two observations are not used in CV
            (np.array([2, 3, 4, 5, 6, 7]), np.array([8, 9, 10])),
            (np.array([5, 6, 7, 8, 9, 10]), np.array([11, 12, 13])),
            (np.array([8, 9, 10, 11, 12, 13]), np.array([14, 15, 16])),
            (np.array([11, 12, 13, 14, 15, 16]), np.array([17, 18, 19]))
        ])
    assert tscv.get_n_splits(X=X) == 4
    assert_splits_equal(tscv.split(X=X), expected)
def test_rolling_time_series_split_empty():
    """Tests rolling_time_series_split when there is not enough data to create user splits"""
    tscv = RollingTimeSeriesSplit(forecast_horizon=50,
                                  min_train_periods=160,
                                  expanding_window=True,
                                  periods_between_splits=4,
                                  periods_between_train_test=0,
                                  max_splits=None)

    with pytest.warns(Warning) as record:
        X = np.random.rand(200, 4)
        expected = np.array([
            (np.arange(180), np.arange(start=180, stop=200)),  # 90/10 split
        ])
        assert tscv.get_n_splits(X=X) == 1
        assert_splits_equal(tscv.split(X=X), expected)
        obtained_messages = "--".join([r.message.args[0] for r in record])
        assert "There are no CV splits under the requested settings" in obtained_messages

    with pytest.warns(Warning) as record:
        X = np.random.rand(150, 4)
        expected = np.array([
            (np.arange(135), np.arange(start=135, stop=150)),  # 90/10 split
        ])
        assert tscv.get_n_splits(X=X) == 1
        assert_splits_equal(tscv.split(X=X), expected)
        obtained_messages = "--".join([r.message.args[0] for r in record])
        assert "There are no CV splits under the requested settings" in obtained_messages
Example #4
0
def run_dummy_grid_search(hyperparameter_grid, n_jobs=1, **kwargs):
    """Runs a pandas.DataFrame through hyperparameter_grid search
    with custom CV splits on a simple dataset to show that
    all the pieces fit together.

    Parameters
    ----------
    hyperparameter_grid : `dict` or `list` [`dict`]
        Passed to ``get_hyperparameter_searcher``.
        Should be compatible with DummyEstimator
    n_jobs : `int` or None, default=-1
        Passed to ``get_hyperparameter_searcher``
    kwargs : additional parameters
        Passed to ``get_hyperparameter_searcher``

    Returns
    -------
    grid_search : `~sklearn.model_selection.RandomizedSearchCV`
        Grid search output (fitted RandomizedSearchCV object).
    """
    # dummy dataset, model, and CV splitter
    periods = 10
    X = pd.DataFrame({
        cst.TIME_COL:
        pd.date_range("2018-01-01", periods=periods, freq="D"),
        cst.VALUE_COL:
        np.arange(1, periods + 1)
    })
    model = DummyEstimator()
    cv = RollingTimeSeriesSplit(forecast_horizon=3)  # 1 CV split

    # requested grid searcher
    grid_search = get_hyperparameter_searcher(
        hyperparameter_grid=hyperparameter_grid,
        model=model,
        cv=cv,
        n_jobs=n_jobs,
        **kwargs)

    grid_search.fit(
        X, X[cst.VALUE_COL])  # need to pass in y to evaluate score() function
    return grid_search
def test_rolling_time_series_split_error():
    """Tests rolling_time_series_split exceptions and warnings"""
    with pytest.warns(Warning) as record:
        RollingTimeSeriesSplit(forecast_horizon=3, min_train_periods=4)
        assert "`min_train_periods` is too small for your `forecast_horizon`" in record[
            0].message.args[0]

    with pytest.warns(Warning) as record:
        X = np.random.rand(12, 1)
        tscv = RollingTimeSeriesSplit(forecast_horizon=4, min_train_periods=8)
        list(tscv.split(X=X))
        assert "There is only one CV split" in record[0].message.args[0]

    with pytest.warns(Warning) as record:
        X = np.random.rand(48, 1)
        tscv = RollingTimeSeriesSplit(forecast_horizon=4,
                                      min_train_periods=8,
                                      max_splits=None)
        list(tscv.split(X=X))
        assert "There is a high number of CV splits" in record[0].message.args[
            0]
Example #6
0
def test_forecast_pipeline_rolling_evaluation_error():
    """Checks errors of forecast_pipeline_rolling_evaluation"""
    data = generate_df_for_tests(freq="D", periods=30)
    df = data["df"]
    tscv = RollingTimeSeriesSplit(forecast_horizon=7,
                                  periods_between_train_test=7,
                                  max_splits=1)
    # Different forecast_horizon in pipeline_params and tscv
    with pytest.raises(ValueError,
                       match="Forecast horizon in 'pipeline_params' "
                       "does not match that of the 'tscv'."):
        pipeline_params = mock_pipeline(df=df, forecast_horizon=15)
        forecast_pipeline_rolling_evaluation(pipeline_params=pipeline_params,
                                             tscv=tscv)

    with pytest.raises(
            ValueError,
            match="'periods_between_train_test' in 'pipeline_params' "
            "does not match that of the 'tscv'."):
        pipeline_params = mock_pipeline(df=df,
                                        forecast_horizon=7,
                                        periods_between_train_test=2)
        forecast_pipeline_rolling_evaluation(pipeline_params=pipeline_params,
                                             tscv=tscv)
def test_rolling_time_series_split2():
    """Tests rolling_time_series_split with custom values"""
    tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                  min_train_periods=4,
                                  expanding_window=True,
                                  use_most_recent_splits=False,
                                  periods_between_splits=4,
                                  periods_between_train_test=2,
                                  max_splits=None)

    assert tscv.forecast_horizon == 2
    assert tscv.min_train_periods == 4
    assert tscv.expanding_window
    assert not tscv.use_most_recent_splits
    assert tscv.periods_between_splits == 4
    assert tscv.periods_between_train_test == 2

    X = np.random.rand(20, 4)
    expected = np.array([  # no offset
        (np.array([0, 1, 2, 3]), np.array([6, 7])),
        (np.array([0, 1, 2, 3, 4, 5, 6, 7]), np.array([10, 11])),
        (np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]), np.array([14, 15])),
        (np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
                   15]), np.array([18, 19]))
    ])
    assert tscv.get_n_splits(X=X) == 4
    assert_splits_equal(tscv.split(X=X), expected)

    X = np.random.rand(25, 4)
    expected = np.array([
        # offset with expanding window, first training is set larger than min_train_periods to use all data
        (np.arange(5), np.array([7, 8])),
        (np.arange(9), np.array([11, 12])),
        (np.arange(13), np.array([15, 16])),
        (np.arange(17), np.array([19, 20])),
        (np.arange(21), np.array([23, 24]))
    ])
    assert tscv.get_n_splits(X=X) == 5
    assert_splits_equal(tscv.split(X=X), expected)
Example #8
0
def test_get_hyperparameter_searcher():
    """Tests get_hyperparameter_searcher"""
    model = DummyEstimator()
    with LogCapture(LOGGER_NAME) as log_capture:
        hyperparameter_grid = {
            "strategy": ["mean", "median", "quantile", "constant"],
            "constant": [20.0],
            "quantile": [0.8],
        }
        grid_search = get_hyperparameter_searcher(
            hyperparameter_grid=hyperparameter_grid, model=model)
        assert grid_search.n_iter == 4
        scoring, refit = get_scoring_and_refit()
        assert_scoring(scoring=grid_search.scoring,
                       expected_keys=scoring.keys())
        assert grid_search.n_jobs == 1
        assert_refit(grid_search.refit,
                     expected_metric=EvaluationMetricEnum.
                     MeanAbsolutePercentError.get_metric_name(),
                     expected_greater_is_better=False)
        assert grid_search.cv is None
        assert grid_search.verbose == 1
        assert grid_search.pre_dispatch == '2*n_jobs'
        assert grid_search.return_train_score
        log_capture.check(
            (LOGGER_NAME, "DEBUG",
             f"Setting hyperparameter_budget to 4 for full grid search."))

    with LogCapture(LOGGER_NAME) as log_capture:
        # specifies `get_scoring_and_refit` kwargs, uses a distribution
        hyperparameter_grid = [{
            "strategy": ["mean", "median", "quantile", "constant"],
            "constant": [20.0],
            "quantile": [0.8],
        }, {
            "strategy": ["constant"],
            "constant": sp_randint(1, 3, 4)
        }]
        grid_search = get_hyperparameter_searcher(
            hyperparameter_grid=hyperparameter_grid,
            model=model,
            cv=4,
            hyperparameter_budget=None,
            n_jobs=4,
            verbose=2,
            score_func=EvaluationMetricEnum.Quantile95.name,
            cv_report_metrics=CV_REPORT_METRICS_ALL)

        assert grid_search.n_iter == 10
        enum_names = set(enum.get_metric_name()
                         for enum in EvaluationMetricEnum)
        assert_scoring(scoring=grid_search.scoring, expected_keys=enum_names)
        assert grid_search.n_jobs == 4
        assert_refit(
            grid_search.refit,
            expected_metric=EvaluationMetricEnum.Quantile95.get_metric_name(),
            expected_greater_is_better=False)
        assert grid_search.cv == 4
        assert grid_search.verbose == 2
        assert grid_search.pre_dispatch == "2*n_jobs"
        assert grid_search.return_train_score
        log_capture.check(
            (LOGGER_NAME, "WARNING",
             f"Setting hyperparameter_budget to 10 to sample from"
             f" provided distributions (and lists)."))

    with LogCapture(LOGGER_NAME) as log_capture:
        # specifies RollingTimeSeriesSplit `cv`, no logging messages
        hyperparameter_grid = [{
            "strategy": ["mean", "median", "quantile", "constant"],
            "constant": [20.0],
            "quantile": [0.8],
        }, {
            "strategy": ["constant"],
            "constant": sp_randint(1, 30)
        }]
        hyperparameter_budget = 3
        cv = RollingTimeSeriesSplit(forecast_horizon=3)
        grid_search = get_hyperparameter_searcher(
            hyperparameter_grid=hyperparameter_grid,
            model=model,
            cv=cv,
            hyperparameter_budget=hyperparameter_budget,
            n_jobs=4,
            verbose=2)

        assert grid_search.n_iter == hyperparameter_budget
        assert grid_search.n_jobs == 4
        assert isinstance(grid_search.cv, RollingTimeSeriesSplit)
        assert grid_search.verbose == 2
        assert grid_search.pre_dispatch == "2*n_jobs"
        assert grid_search.return_train_score
        log_capture.check()
Example #9
0
def forecast_pipeline(
        # input
        df: pd.DataFrame,
        time_col=TIME_COL,
        value_col=VALUE_COL,
        date_format=None,
        tz=None,
        freq=None,
        train_end_date=None,
        anomaly_info=None,
        # model
        pipeline=None,
        regressor_cols=None,
        lagged_regressor_cols=None,
        estimator=SimpleSilverkiteEstimator(),
        hyperparameter_grid=None,
        hyperparameter_budget=None,
        n_jobs=COMPUTATION_N_JOBS,
        verbose=1,
        # forecast
        forecast_horizon=None,
        coverage=0.95,
        test_horizon=None,
        periods_between_train_test=None,
        agg_periods=None,
        agg_func=None,
        # evaluation
        score_func=EvaluationMetricEnum.MeanAbsolutePercentError.name,
        score_func_greater_is_better=False,
        cv_report_metrics=CV_REPORT_METRICS_ALL,
        null_model_params=None,
        relative_error_tolerance=None,
        # CV
        cv_horizon=None,
        cv_min_train_periods=None,
        cv_expanding_window=False,
        cv_use_most_recent_splits=False,
        cv_periods_between_splits=None,
        cv_periods_between_train_test=None,
        cv_max_splits=3):
    """Computation pipeline for end-to-end forecasting.

    Trains a forecast model end-to-end:

        1. checks input data
        2. runs cross-validation to select optimal hyperparameters e.g. best model
        3. evaluates best model on test set
        4. provides forecast of best model (re-trained on all data) into the future

    Returns forecasts with methods to plot and see diagnostics.
    Also returns the fitted pipeline and CV results.

    Provides a high degree of customization over training and evaluation parameters:

        1. model
        2. cross validation
        3. evaluation
        4. forecast horizon

    See test cases for examples.

    Parameters
    ----------
    df : `pandas.DataFrame`
        Timeseries data to forecast.
        Contains columns [`time_col`, `value_col`], and optional regressor columns
        Regressor columns should include future values for prediction

    time_col : `str`, default TIME_COL in constants.py
        name of timestamp column in df

    value_col : `str`, default VALUE_COL in constants.py
        name of value column in df (the values to forecast)

    date_format : `str` or None, default None
        strftime format to parse time column, eg ``%m/%d/%Y``.
        Note that ``%f`` will parse all the way up to nanoseconds.
        If None (recommended), inferred by `pandas.to_datetime`.

    tz : `str` or None, default None
        Passed to `pandas.tz_localize` to localize the timestamp

    freq : `str` or None, default None
        Frequency of input data. Used to generate future dates for prediction.
        Frequency strings can have multiples, e.g. '5H'.
        See https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
        for a list of frequency aliases.
        If None, inferred by `pandas.infer_freq`.
        Provide this parameter if ``df`` has missing timepoints.

    train_end_date : `datetime.datetime`, optional, default None
        Last date to use for fitting the model. Forecasts are generated after this date.
        If None, it is set to the last date with a non-null value in
        ``value_col`` of ``df``.

    anomaly_info : `dict` or `list` [`dict`] or None, default None
        Anomaly adjustment info. Anomalies in ``df``
        are corrected before any forecasting is done.

        If None, no adjustments are made.

        A dictionary containing the parameters to
        `~greykite.common.features.adjust_anomalous_data.adjust_anomalous_data`.
        See that function for details.
        The possible keys are:

            ``"value_col"`` : `str`
                The name of the column in ``df`` to adjust. You may adjust the value
                to forecast as well as any numeric regressors.
            ``"anomaly_df"`` : `pandas.DataFrame`
                Adjustments to correct the anomalies.
            ``"start_date_col"``: `str`, default START_DATE_COL
                Start date column in ``anomaly_df``.
            ``"end_date_col"``: `str`, default END_DATE_COL
                End date column in ``anomaly_df``.
            ``"adjustment_delta_col"``: `str` or None, default None
                Impact column in ``anomaly_df``.
            ``"filter_by_dict"``: `dict` or None, default None
                Used to filter ``anomaly_df`` to the relevant anomalies for
                the ``value_col`` in this dictionary.
                Key specifies the column name, value specifies the filter value.
            ``"filter_by_value_col""``: `str` or None, default None
                Adds ``{filter_by_value_col: value_col}`` to ``filter_by_dict``
                if not None, for the ``value_col`` in this dictionary.
            ``"adjustment_method"`` : `str` ("add" or "subtract"), default "add"
                How to make the adjustment, if ``adjustment_delta_col`` is provided.

        Accepts a list of such dictionaries to adjust multiple columns in ``df``.

    pipeline : `sklearn.pipeline.Pipeline` or None, default None
        Pipeline to fit. The final named step must be called "estimator".
        If None, will use the default Pipeline from
        `~greykite.framework.pipeline.utils.get_basic_pipeline`.

    regressor_cols : `list` [`str`] or None, default None
        A list of regressor columns used in the training and prediction DataFrames.
        It should contain only the regressors that are being used in the grid search.
        If None, no regressor columns are used.
        Regressor columns that are unavailable in ``df`` are dropped.

    lagged_regressor_cols : `list` [`str`] or None, default None
        A list of additional columns needed for lagged regressors in the training and prediction DataFrames.
        This list can have overlap with ``regressor_cols``.
        If None, no additional columns are added to the DataFrame.
        Lagged regressor columns that are unavailable in ``df`` are dropped.

    estimator : instance of an estimator that implements `greykite.algo.models.base_forecast_estimator.BaseForecastEstimator`
        Estimator to use as the final step in the pipeline.
        Ignored if ``pipeline`` is provided.

    forecast_horizon : `int` or None, default None
        Number of periods to forecast into the future. Must be > 0.
        If None, default is determined from input data frequency

    coverage : `float` or None, default=0.95
        Intended coverage of the prediction bands (0.0 to 1.0)
        If None, the upper/lower predictions are not returned
        Ignored if `pipeline` is provided. Uses coverage of the ``pipeline`` estimator instead.

    test_horizon : `int` or None, default None
        Numbers of periods held back from end of df for test.
        The rest is used for cross validation.
        If None, default is forecast_horizon. Set to 0 to skip backtest.

    periods_between_train_test : `int` or None, default None
        Number of periods for the gap between train and test data.
        If None, default is 0.

    agg_periods : `int` or None, default None
        Number of periods to aggregate before evaluation.

        Model is fit and forecasted on the dataset's original frequency.

        Before evaluation, the actual and forecasted values are aggregated,
        using rolling windows of size ``agg_periods`` and the function
        ``agg_func``. (e.g. if the dataset is hourly, use ``agg_periods=24, agg_func=np.sum``,
        to evaluate performance on the daily totals).

        If None, does not aggregate before evaluation.

        Currently, this is only used when calculating CV metrics and
        the R2_null_model_score metric in backtest/forecast. No pre-aggregation
        is applied for the other backtest/forecast evaluation metrics.

    agg_func : callable or None, default None
        Takes an array and returns a number, e.g. np.max, np.sum.

        Defines how to aggregate rolling windows of actual and predicted values
        before evaluation.

        Ignored if ``agg_periods`` is None.

        Currently, this is only used when calculating CV metrics and
        the R2_null_model_score metric in backtest/forecast. No pre-aggregation
        is applied for the other backtest/forecast evaluation metrics.

    score_func : `str` or callable, default ``EvaluationMetricEnum.MeanAbsolutePercentError.name``
        Score function used to select optimal model in CV.
        If a callable, takes arrays ``y_true``, ``y_pred`` and returns a float.
        If a string, must be either a
        `~greykite.common.evaluation.EvaluationMetricEnum` member name
        or `~greykite.common.constants.FRACTION_OUTSIDE_TOLERANCE`.

    score_func_greater_is_better : `bool`, default False
        True if ``score_func`` is a score function, meaning higher is better,
        and False if it is a loss function, meaning lower is better.
        Must be provided if ``score_func`` is a callable (custom function).
        Ignored if ``score_func`` is a string, because the direction is known.

    cv_report_metrics : `str`, or `list` [`str`], or None, default `~greykite.common.constants.CV_REPORT_METRICS_ALL`
        Additional metrics to compute during CV, besides the one specified by ``score_func``.

            - If the string constant `greykite.framework.constants.CV_REPORT_METRICS_ALL`,
              computes all metrics in ``EvaluationMetricEnum``. Also computes
              ``FRACTION_OUTSIDE_TOLERANCE`` if ``relative_error_tolerance`` is not None.
              The results are reported by the short name (``.get_metric_name()``) for ``EvaluationMetricEnum``
              members and ``FRACTION_OUTSIDE_TOLERANCE_NAME`` for ``FRACTION_OUTSIDE_TOLERANCE``.
              These names appear in the keys of ``forecast_result.grid_search.cv_results_``
              returned by this function.
            - If a list of strings, each of the listed metrics is computed. Valid strings are
              `~greykite.common.evaluation.EvaluationMetricEnum` member names
              and `~greykite.common.constants.FRACTION_OUTSIDE_TOLERANCE`.

              For example::

                ["MeanSquaredError", "MeanAbsoluteError", "MeanAbsolutePercentError", "MedianAbsolutePercentError", "FractionOutsideTolerance2"]

            - If None, no additional metrics are computed.

    null_model_params : `dict` or None, default None
        Defines baseline model to compute ``R2_null_model_score`` evaluation metric.
        ``R2_null_model_score`` is the improvement in the loss function relative
        to a null model. It can be used to evaluate model quality with respect to
        a simple baseline. For details, see
        `~greykite.common.evaluation.r2_null_model_score`.

        The null model is a `~sklearn.dummy.DummyRegressor`,
        which returns constant predictions.

        Valid keys are "strategy", "constant", "quantile".
        See `~sklearn.dummy.DummyRegressor`. For example::

            null_model_params = {
                "strategy": "mean",
            }
            null_model_params = {
                "strategy": "median",
            }
            null_model_params = {
                "strategy": "quantile",
                "quantile": 0.8,
            }
            null_model_params = {
                "strategy": "constant",
                "constant": 2.0,
            }

        If None, ``R2_null_model_score`` is not calculated.

        Note: CV model selection always optimizes ``score_func`, not
        the ``R2_null_model_score``.

    relative_error_tolerance : `float` or None, default None
        Threshold to compute the ``Outside Tolerance`` metric,
        defined as the fraction of forecasted values whose relative
        error is strictly greater than ``relative_error_tolerance``.
        For example, 0.05 allows for 5% relative error.
        If `None`, the metric is not computed.

    hyperparameter_grid : `dict`, `list` [`dict`] or None, default None
        Sets properties of the steps in the pipeline,
        and specifies combinations to search over.
        Should be valid input to `sklearn.model_selection.GridSearchCV` (param_grid)
        or `sklearn.model_selection.RandomizedSearchCV` (param_distributions).

        Prefix transform/estimator attributes by the name of the step in the pipeline.
        See details at: https://scikit-learn.org/stable/modules/compose.html#nested-parameters

        If None, uses the default pipeline parameters.

    hyperparameter_budget : `int` or None, default None
        Max number of hyperparameter sets to try within the ``hyperparameter_grid`` search space

        Runs a full grid search if ``hyperparameter_budget`` is sufficient to exhaust full
        ``hyperparameter_grid``, otherwise samples uniformly at random from the space.

        If None, uses defaults:

            * full grid search if all values are constant
            * 10 if any value is a distribution to sample from

    n_jobs : `int` or None, default `~greykite.framework.constants.COMPUTATION_N_JOBS`
        Number of jobs to run in parallel
        (the maximum number of concurrently running workers).
        ``-1`` uses all CPUs. ``-2`` uses all CPUs but one.
        ``None`` is treated as 1 unless in a `joblib.Parallel` backend context
        that specifies otherwise.

    verbose : `int`, default 1
        Verbosity level during CV.
        if > 0, prints number of fits
        if > 1, prints fit parameters, total score + fit time
        if > 2, prints train/test scores

    cv_horizon : `int` or None, default None
        Number of periods in each CV test set
        If None, default is ``forecast_horizon``.
        Set either ``cv_horizon`` or ``cv_max_splits`` to 0 to skip CV.

    cv_min_train_periods : `int` or None, default None
        Minimum number of periods for training each CV fold.
        If cv_expanding_window is False, every training period is this size
        If None, default is 2 * ``cv_horizon``

    cv_expanding_window : `bool`, default False
        If True, training window for each CV split is fixed to the first available date.
        Otherwise, train start date is sliding, determined by ``cv_min_train_periods``.

    cv_use_most_recent_splits: `bool`, default False
        If True, splits from the end of the dataset are used.
        Else a sampling strategy is applied. Check
        `~greykite.sklearn.cross_validation.RollingTimeSeriesSplit._sample_splits`
        for details.

    cv_periods_between_splits : `int` or None, default None
        Number of periods to slide the test window between CV splits
        If None, default is ``cv_horizon``

    cv_periods_between_train_test : `int` or None, default None
        Number of periods for the gap between train and test in a CV split.
        If None, default is ``periods_between_train_test``.

    cv_max_splits : `int` or None, default 3
        Maximum number of CV splits.
        Given the above configuration, samples up to max_splits train/test splits,
        preferring splits toward the end of available data. If None, uses all splits.
        Set either ``cv_horizon`` or ``cv_max_splits`` to 0 to skip CV.

    Returns
    -------
    forecast_result : :class:`~greykite.framework.pipeline.pipeline.ForecastResult`
        Forecast result. See :class:`~greykite.framework.pipeline.pipeline.ForecastResult`
        for details.

            * If ``cv_horizon=0``, ``forecast_result.grid_search.best_estimator_``
              and ``forecast_result.grid_search.best_params_`` attributes are defined
              according to the provided single set of parameters. There must be a single
              set of parameters to skip cross-validation.
            * If ``test_horizon=0``, ``forecast_result.backtest`` is None.
    """
    if hyperparameter_grid is None or hyperparameter_grid == []:
        hyperparameter_grid = {}
    # When hyperparameter_grid is a singleton list, unlist it
    if isinstance(hyperparameter_grid, list) and len(hyperparameter_grid) == 1:
        hyperparameter_grid = hyperparameter_grid[0]

    # Loads full dataset
    ts = UnivariateTimeSeries()
    ts.load_data(
        df=df,
        time_col=time_col,
        value_col=value_col,
        freq=freq,
        date_format=date_format,
        tz=tz,
        train_end_date=train_end_date,
        regressor_cols=regressor_cols,
        lagged_regressor_cols=lagged_regressor_cols,
        anomaly_info=anomaly_info)

    # Splits data into training and test sets. ts.df uses standardized column names
    if test_horizon == 0:
        train_df = ts.fit_df
        train_y = ts.fit_y
        test_df = pd.DataFrame(columns=list(df.columns))
    else:
        # Make sure to refit best_pipeline appropriately
        train_df, test_df, train_y, test_y = train_test_split(
            ts.fit_df,
            ts.fit_y,
            train_size=ts.fit_df.shape[0] - test_horizon - periods_between_train_test,
            test_size=test_horizon + periods_between_train_test,
            shuffle=False)  # this is important since this is timeseries forecasting!
    log_message(f"Train size: {train_df.shape[0]}. Test size: {test_df.shape[0]}", LoggingLevelEnum.INFO)

    # Defines default training pipeline
    if pipeline is None:
        pipeline = get_basic_pipeline(
            estimator=estimator,
            score_func=score_func,
            score_func_greater_is_better=score_func_greater_is_better,
            agg_periods=agg_periods,
            agg_func=agg_func,
            relative_error_tolerance=relative_error_tolerance,
            coverage=coverage,
            null_model_params=null_model_params,
            regressor_cols=ts.regressor_cols,
            lagged_regressor_cols=ts.lagged_regressor_cols)

    # Searches for the best parameters, and refits model with selected parameters on the entire training set
    if cv_horizon == 0 or cv_max_splits == 0:
        # No cross-validation. Only one set of hyperparameters is allowed.
        try:
            if len(ParameterGrid(hyperparameter_grid)) > 1:
                raise ValueError(
                    "CV is required to identify the best model because there are multiple options "
                    "in `hyperparameter_grid`. Either provide a single option or set `cv_horizon` and `cv_max_splits` "
                    "to nonzero values.")
        except TypeError:  # Parameter value is not iterable
            raise ValueError(
                "CV is required to identify the best model because `hyperparameter_grid` contains "
                "a distribution. Either remove the distribution or set `cv_horizon` and `cv_max_splits` "
                "to nonzero values.")

        # Fits model to entire train set. Params must be set manually since it's not done by grid search
        params = {k: v[0] for k, v in hyperparameter_grid.items()}  # unpack lists, `v` is a singleton list with the parameter value
        best_estimator = pipeline.set_params(**params).fit(train_df, train_y)

        # Wraps this model in a dummy RandomizedSearchCV object to return the backtest model
        grid_search = get_hyperparameter_searcher(
            hyperparameter_grid=hyperparameter_grid,
            model=pipeline,
            cv=None,  # no cross-validation
            hyperparameter_budget=hyperparameter_budget,
            n_jobs=n_jobs,
            verbose=verbose,
            score_func=score_func,
            score_func_greater_is_better=score_func_greater_is_better,
            cv_report_metrics=cv_report_metrics,
            agg_periods=agg_periods,
            agg_func=agg_func,
            relative_error_tolerance=relative_error_tolerance)
        # Sets relevant attributes. Others are undefined (cv_results_, best_score_, best_index_, scorer_, refit_time_)
        grid_search.best_estimator_ = best_estimator
        grid_search.best_params_ = params
        grid_search.n_splits_ = 0
    else:
        # Defines cross-validation splitter
        cv = RollingTimeSeriesSplit(
            forecast_horizon=cv_horizon,
            min_train_periods=cv_min_train_periods,
            expanding_window=cv_expanding_window,
            use_most_recent_splits=cv_use_most_recent_splits,
            periods_between_splits=cv_periods_between_splits,
            periods_between_train_test=cv_periods_between_train_test,
            max_splits=cv_max_splits)

        # Defines grid search approach for CV
        grid_search = get_hyperparameter_searcher(
            hyperparameter_grid=hyperparameter_grid,
            model=pipeline,
            cv=cv,
            hyperparameter_budget=hyperparameter_budget,
            n_jobs=n_jobs,
            verbose=verbose,
            score_func=score_func,
            score_func_greater_is_better=score_func_greater_is_better,
            cv_report_metrics=cv_report_metrics,
            agg_periods=agg_periods,
            agg_func=agg_func,
            relative_error_tolerance=relative_error_tolerance)
        grid_search.fit(train_df, train_y)
        best_estimator = grid_search.best_estimator_

    # Evaluates historical performance, fits model to all data (train+test)
    if test_horizon > 0:
        backtest_train_end_date = train_df[TIME_COL].max()
        # Uses pd.date_range because pd.Timedelta does not work for complicated frequencies e.g. "W-MON"
        backtest_test_start_date = pd.date_range(
            start=backtest_train_end_date,
            periods=periods_between_train_test + 2,  # Adds 2 as start parameter is inclusive
            freq=ts.freq)[-1]
        backtest = get_forecast(
            df=ts.fit_df,  # Backtest needs to happen on fit_df, not on the entire df
            trained_model=best_estimator,
            train_end_date=backtest_train_end_date,
            test_start_date=backtest_test_start_date,
            forecast_horizon=test_horizon,
            xlabel=time_col,
            ylabel=value_col,
            relative_error_tolerance=relative_error_tolerance)
        best_pipeline = clone(best_estimator)  # Copies optimal parameters
        best_pipeline.fit(ts.fit_df, ts.y)  # Refits this model on entire training dataset
    else:
        backtest = None  # Backtest training metrics are the same as forecast training metrics
        best_pipeline = best_estimator  # best_model is already fit to all data

    # Makes future predictions
    periods = forecast_horizon + periods_between_train_test
    future_df = ts.make_future_dataframe(
        periods=periods,
        include_history=True)

    forecast_train_end_date = ts.train_end_date
    # Uses pd.date_range because pd.Timedelta does not work for complicated frequencies e.g. "W-MON"
    forecast_test_start_date = pd.date_range(
        start=forecast_train_end_date,
        periods=periods_between_train_test + 2,  # Adds 2 as start parameter is inclusive
        freq=ts.freq)[-1]
    forecast = get_forecast(
        df=future_df,
        trained_model=best_pipeline,
        train_end_date=forecast_train_end_date,
        test_start_date=forecast_test_start_date,
        forecast_horizon=forecast_horizon,
        xlabel=time_col,
        ylabel=value_col,
        relative_error_tolerance=relative_error_tolerance)

    result = ForecastResult(
        timeseries=ts,
        grid_search=grid_search,
        model=best_pipeline,
        backtest=backtest,
        forecast=forecast
    )
    return result
Example #10
0
def forecast_pipeline_rolling_evaluation(pipeline_params: Dict,
                                         tscv: RollingTimeSeriesSplit):
    """Runs ``forecast_pipeline`` on a rolling window basis.

    Parameters
    ----------
    pipeline_params : `Dict`
        A dictionary containing the input to the
        :func:`~greykite.framework.pipeline.pipeline.forecast_pipeline`.
    tscv : `~greykite.sklearn.cross_validation.RollingTimeSeriesSplit`
        Cross-validation object that determines the rolling window evaluation.
        See :class:`~greykite.sklearn.cross_validation.RollingTimeSeriesSplit` for details.

    Returns
    -------
    rolling_evaluation : `dict`
        Stores benchmarking results for each split, e.g.
        split_0 contains result for first split, split_1 contains result for second split and so on.
        Number of splits is determined by the input parameters.
        Every split is a dictionary with keys "runtime_sec" and "pipeline_result".
    """
    if pipeline_params["forecast_horizon"] != tscv.forecast_horizon:
        raise ValueError(
            "Forecast horizon in 'pipeline_params' does not match that of the 'tscv'."
        )

    if pipeline_params[
            "periods_between_train_test"] != tscv.periods_between_train_test:
        raise ValueError(
            "'periods_between_train_test' in 'pipeline_params' does not match that of the 'tscv'."
        )

    df = pipeline_params["df"]
    time_col = pipeline_params.get("time_col", TIME_COL)
    date_format = pipeline_params.get("date_format")
    # Disables backtest. For rolling evaluation we know the actual values in forecast period.
    # So out of sample performance can be calculated using pipeline_result.forecast
    pipeline_params["test_horizon"] = 0

    rolling_evaluation = {}
    with tqdm(list(tscv.split(X=df)), ncols=800, leave=True) as progress_bar:
        for (split_num, (train, test)) in enumerate(progress_bar):
            # Description will be displayed on the left of progress bar
            progress_bar.set_description(f"Split '{split_num}' ")
            train_end_date = pd.to_datetime(df.iloc[train[-1]][time_col],
                                            format=date_format,
                                            infer_datetime_format=True)
            pipeline_params["train_end_date"] = train_end_date

            start_time = timeit.default_timer()
            pipeline_result = forecast_pipeline(**pipeline_params)
            runtime = timeit.default_timer() - start_time

            pipeline_output = dict(runtime_sec=round(runtime, 3),
                                   pipeline_result=pipeline_result)
            rolling_evaluation[f"split_{split_num}"] = pipeline_output

            log_message(f"Completed evaluation for split {split_num}.",
                        LoggingLevelEnum.DEBUG)

    return rolling_evaluation
Example #11
0
def test_forecast_pipeline_rolling_evaluation_prophet():
    """Checks the output rolling evaluation with Prophet template"""
    data = generate_df_with_reg_for_tests(freq="D",
                                          periods=30,
                                          remove_extra_cols=True,
                                          mask_test_actuals=True)
    reg_cols = ["regressor1", "regressor2", "regressor3"]
    keep_cols = [TIME_COL, VALUE_COL] + reg_cols
    df = data["df"][keep_cols]

    hyperparameter_grid = {
        "estimator__weekly_seasonality": [True],
        "estimator__daily_seasonality": [True, False],
        "estimator__n_changepoints":
        [0],  # to speed up test case, remove for better fit
        "estimator__uncertainty_samples": [10],  # to speed up test case
        "estimator__add_regressor_dict": [{
            "regressor1": {
                "prior_scale": 10,
                "standardize": True,
                "mode": 'additive'
            },
            "regressor2": {
                "prior_scale": 15,
                "standardize": False,
                "mode": 'additive'
            },
            "regressor3": {}
        }]
    }
    pipeline_params = mock_pipeline(
        df=df,
        forecast_horizon=3,
        regressor_cols=["regressor1", "regressor2", "regressor3"],
        estimator=ProphetEstimator(),
        hyperparameter_grid=hyperparameter_grid)
    tscv = RollingTimeSeriesSplit(forecast_horizon=3,
                                  expanding_window=True,
                                  max_splits=1)
    rolling_evaluation = forecast_pipeline_rolling_evaluation(
        pipeline_params=pipeline_params, tscv=tscv)

    expected_splits_n = tscv.max_splits
    assert len(rolling_evaluation.keys()) == expected_splits_n
    assert set(rolling_evaluation.keys()) == {"split_0"}

    split0_output = rolling_evaluation["split_0"]
    assert round(split0_output["runtime_sec"],
                 3) == split0_output["runtime_sec"]

    pipeline_result = split0_output["pipeline_result"]
    # Calculates expected pipeline
    train, test = list(tscv.split(X=df))[0]
    df_train = df.loc[train]
    pipeline_params_updated = pipeline_params
    pipeline_params_updated["test_horizon"] = 0
    pipeline_params_updated["df"] = df_train
    expected_pipeline_result = forecast_pipeline(**pipeline_params_updated)

    assert pipeline_result.backtest is None
    # Checks output is identical when there is only 1 split
    pipeline_grid_search = summarize_grid_search_results(
        pipeline_result.grid_search,
        score_func=EvaluationMetricEnum.MeanAbsolutePercentError.name)
    expected_grid_search = summarize_grid_search_results(
        expected_pipeline_result.grid_search,
        score_func=EvaluationMetricEnum.MeanAbsolutePercentError.name)
    assert_equal(pipeline_grid_search["mean_test_MAPE"],
                 expected_grid_search["mean_test_MAPE"])
    assert_equal(pipeline_result.grid_search.cv.__dict__,
                 expected_pipeline_result.grid_search.cv.__dict__)
    # Checks forecast df has the correct number of rows
    expected_rows = pipeline_result.timeseries.fit_df.shape[
        0] + tscv.forecast_horizon
    assert pipeline_result.forecast.df.shape[0] == expected_rows
Example #12
0
def test_forecast_pipeline_rolling_evaluation_silverkite():
    """Checks the output rolling evaluation with Silverkite template"""
    data = generate_df_with_reg_for_tests(
        freq="1D",
        periods=20 * 7,  # short-term: 20 weeks of data
        remove_extra_cols=True,
        mask_test_actuals=True)
    regressor_cols = ["regressor1", "regressor2", "regressor_categ"]
    keep_cols = [TIME_COL, VALUE_COL] + regressor_cols
    df = data["df"][keep_cols]

    coverage = 0.1
    hyperparameter_grid = {
        "estimator__origin_for_time_vars":
        [None],  # inferred from training data
        "estimator__fs_components_df": [
            pd.DataFrame({
                "name": ["tow"],
                "period": [7.0],
                "order": [3],
                "seas_names": ["weekly"]
            })
        ],
        "estimator__extra_pred_cols":
        [regressor_cols, regressor_cols + ["ct_sqrt"]
         ],  # two cases: no growth term and single growth term
        "estimator__fit_algorithm_dict": [{
            "fit_algorithm": "linear"
        }]
    }
    pipeline_params = mock_pipeline(
        df=df,
        time_col=TIME_COL,
        value_col=VALUE_COL,
        date_format=None,  # not recommended, but possible to specify
        freq=None,
        regressor_cols=regressor_cols,
        estimator=SilverkiteEstimator(),
        hyperparameter_grid=hyperparameter_grid,
        hyperparameter_budget=1,
        n_jobs=1,
        forecast_horizon=2 * 7,
        coverage=coverage,
        test_horizon=2 * 7,
        periods_between_train_test=2 * 7,
        agg_periods=7,
        agg_func=np.mean,
        score_func=mean_absolute_error,  # callable score_func
        null_model_params=None,
        cv_horizon=1 * 7,
        cv_expanding_window=True,
        cv_min_train_periods=8 * 7,
        cv_periods_between_splits=7,
        cv_periods_between_train_test=3 * 7,
        cv_max_splits=2)
    tscv = RollingTimeSeriesSplit(forecast_horizon=2 * 7,
                                  min_train_periods=10 * 7,
                                  expanding_window=True,
                                  use_most_recent_splits=True,
                                  periods_between_splits=2 * 7,
                                  periods_between_train_test=2 * 7,
                                  max_splits=3)
    rolling_evaluation = forecast_pipeline_rolling_evaluation(
        pipeline_params=pipeline_params, tscv=tscv)

    expected_splits_n = tscv.max_splits
    assert len(rolling_evaluation.keys()) == expected_splits_n
    assert set(rolling_evaluation.keys()) == {"split_0", "split_1", "split_2"}

    time_col = pipeline_params["time_col"]
    for split_num, (train, test) in enumerate(tscv.split(X=df)):
        split_output = rolling_evaluation[f"split_{split_num}"]
        assert round(split_output["runtime_sec"],
                     3) == split_output["runtime_sec"]

        pipeline_result = split_output["pipeline_result"]

        # Checks every split uses all the available data for training
        ts = pipeline_result.timeseries
        train_end_date = df.iloc[train[-1]][time_col]
        assert ts.train_end_date == train_end_date

        assert pipeline_result.backtest is None

        # Checks every split has forecast for train+test periods passed by tscv
        forecast = pipeline_result.forecast
        assert forecast.df.shape[0] == ts.fit_df.shape[
            0] + tscv.periods_between_train_test + tscv.forecast_horizon
def test_sample_splits():
    """Tests _sample_splits"""
    # final split
    max_splits = 1
    tscv = RollingTimeSeriesSplit(forecast_horizon=10, max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == [9]

    # last two splits
    max_splits = 2
    tscv = RollingTimeSeriesSplit(forecast_horizon=10, max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == [9, 8]

    # last two splits, plus randomly selected splits
    max_splits = 8
    tscv = RollingTimeSeriesSplit(forecast_horizon=10, max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == [9, 8, 0, 7, 3, 4, 6, 2]

    # all splits
    max_splits = None
    tscv = RollingTimeSeriesSplit(forecast_horizon=10, max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == list(range(10))

    max_splits = 10
    tscv = RollingTimeSeriesSplit(forecast_horizon=10, max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == list(range(10))

    max_splits = 15
    tscv = RollingTimeSeriesSplit(forecast_horizon=10, max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == list(range(10))

    # rolling window evaluation
    # splits from end upto max_splits are kept
    max_splits = 5
    tscv = RollingTimeSeriesSplit(forecast_horizon=10,
                                  use_most_recent_splits=True,
                                  max_splits=max_splits)
    assert tscv._sample_splits(num_splits=10) == [5, 6, 7, 8, 9]
def test_get_offset():
    """Tests _get_offset"""
    tscv = RollingTimeSeriesSplit(forecast_horizon=5,
                                  min_train_periods=10,
                                  periods_between_train_test=5,
                                  periods_between_splits=5)
    assert tscv._get_offset(X=np.random.rand(19, 1)) == 0  # no CV splits
    assert tscv._get_offset(X=np.random.rand(20, 1)) == 0
    assert tscv._get_offset(X=np.random.rand(21, 1)) == 1
    assert tscv._get_offset(X=np.random.rand(22, 1)) == 2
    assert tscv._get_offset(X=np.random.rand(23, 1)) == 3
    assert tscv._get_offset(X=np.random.rand(24, 1)) == 4
    assert tscv._get_offset(X=np.random.rand(
        25, 1)) == 0  # perfect alignment again, last data point is used

    tscv = RollingTimeSeriesSplit(forecast_horizon=3,
                                  min_train_periods=6,
                                  periods_between_train_test=11,
                                  periods_between_splits=3)
    assert tscv._get_offset(X=np.random.rand(20, 1)) == 0
    assert tscv._get_offset(X=np.random.rand(21, 1)) == 1
    assert tscv._get_offset(X=np.random.rand(22, 1)) == 2
    assert tscv._get_offset(X=np.random.rand(
        23, 1)) == 0  # perfect alignment again, last data point is used
def test_get_n_splits():
    """Tests get_n_splits and get_n_splits_without_capping"""
    X = np.random.rand(100, 3)

    # normal case, no capping
    tscv = RollingTimeSeriesSplit(forecast_horizon=10,
                                  min_train_periods=20,
                                  expanding_window=True,
                                  periods_between_splits=16,
                                  periods_between_train_test=3,
                                  max_splits=None)
    assert tscv.get_n_splits(X=X) == 5
    assert tscv.get_n_splits_without_capping(X=X) == 5

    # normal case, no capping
    tscv = RollingTimeSeriesSplit(forecast_horizon=7,
                                  min_train_periods=30,
                                  expanding_window=False,
                                  periods_between_splits=30,
                                  periods_between_train_test=10,
                                  max_splits=None)
    assert tscv.get_n_splits(X=X) == 2
    assert tscv.get_n_splits_without_capping(X=X) == 2

    # max_splits reached
    tscv = RollingTimeSeriesSplit(forecast_horizon=10,
                                  min_train_periods=20,
                                  expanding_window=True,
                                  periods_between_splits=16,
                                  periods_between_train_test=3,
                                  max_splits=4)
    assert tscv.get_n_splits(X=X) == 4
    assert tscv.get_n_splits_without_capping(X=X) == 5

    # not enough data, use default_split
    tscv = RollingTimeSeriesSplit(forecast_horizon=10,
                                  min_train_periods=90,
                                  expanding_window=True,
                                  periods_between_splits=16,
                                  periods_between_train_test=3,
                                  max_splits=None)
    assert tscv.get_n_splits(X=X) == 1
    assert tscv.get_n_splits_without_capping(X=X) == 0
def test_rolling_time_series_split3():
    """Tests rolling_time_series_split with max_splits"""
    # only the last split is kept
    with pytest.warns(Warning) as record:
        max_splits = 1
        tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                      min_train_periods=4,
                                      expanding_window=True,
                                      periods_between_splits=4,
                                      periods_between_train_test=2,
                                      max_splits=max_splits)

        X = np.random.rand(20, 4)
        expected = np.array([
            (np.array([0, 1, 2, 3]), np.array([6, 7])),
            (np.array([0, 1, 2, 3, 4, 5, 6, 7]), np.array([10, 11])),
            (np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
                       11]), np.array([14, 15])),
            (np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
                       15]), np.array([18, 19]))
        ])
        assert tscv.get_n_splits_without_capping(X=X) == 4
        assert tscv.get_n_splits(X=X) == max_splits
        assert_splits_equal(tscv.split(X=X), expected[-max_splits:])

        obtained_messages = "--".join([r.message.args[0] for r in record])
        assert "There is only one CV split" in obtained_messages

    # only the last two splits are kept
    max_splits = 2
    tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                  min_train_periods=4,
                                  expanding_window=True,
                                  use_most_recent_splits=False,
                                  periods_between_splits=4,
                                  periods_between_train_test=2,
                                  max_splits=max_splits)
    assert tscv.get_n_splits_without_capping(X=X) == 4
    assert tscv.get_n_splits(X=X) == max_splits
    assert_splits_equal(tscv.split(X=X), expected[-max_splits:])

    # two splits and a random split are kept
    max_splits = 3
    tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                  min_train_periods=4,
                                  expanding_window=True,
                                  use_most_recent_splits=False,
                                  periods_between_splits=4,
                                  periods_between_train_test=2,
                                  max_splits=max_splits)
    assert tscv.get_n_splits_without_capping(X=X) == 4
    assert tscv.get_n_splits(X=X) == max_splits
    assert_splits_equal(tscv.split(X=X), expected[[
        0, 2, 3
    ]])  # picked at random (selection fixed by random seed)

    # all splits are kept (max_splits == get_n_splits)
    max_splits = 4
    tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                  min_train_periods=4,
                                  expanding_window=True,
                                  use_most_recent_splits=False,
                                  periods_between_splits=4,
                                  periods_between_train_test=2,
                                  max_splits=max_splits)
    assert tscv.get_n_splits_without_capping(X=X) == 4
    assert tscv.get_n_splits(X=X) == 4
    assert_splits_equal(tscv.split(X=X), expected)

    # all splits are kept (max_splits > get_n_splits)
    max_splits = 5
    tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                  min_train_periods=4,
                                  expanding_window=True,
                                  use_most_recent_splits=False,
                                  periods_between_splits=4,
                                  periods_between_train_test=2,
                                  max_splits=max_splits)
    assert tscv.get_n_splits_without_capping(X=X) == 4
    assert tscv.get_n_splits(X=X) == 4
    assert_splits_equal(tscv.split(X=X), expected)

    # rolling window evaluation
    # splits from end up to max_splits are kept
    max_splits = 3
    tscv = RollingTimeSeriesSplit(forecast_horizon=2,
                                  min_train_periods=4,
                                  expanding_window=True,
                                  use_most_recent_splits=True,
                                  periods_between_splits=4,
                                  periods_between_train_test=2,
                                  max_splits=max_splits)
    assert tscv.use_most_recent_splits
    assert tscv.get_n_splits_without_capping(X=X) == 4
    assert tscv.get_n_splits(X=X) == max_splits
    assert_splits_equal(tscv.split(X=X), expected[[1, 2, 3]])
Example #17
0
# Define the Cross-Validation (CV)
# --------------------------------
# In time-series forecasting we use a Rolling Window CV.
# You can easily define it by using
# `~greykite.sklearn.cross_validation.RollingTimeSeriesSplit` class.
# The CV parameters depend on the data frequency,
# forecast horizon as well as the speed of the models.
# See ``Benchmarking documentation`` for guidance on how
# to choose CV parameters for your use case.

# Define the benchmark folds
# CV parameters are changed for illustration purpose
tscv = RollingTimeSeriesSplit(
    forecast_horizon=forecast_horizon,
    min_train_periods=2 * 365,
    expanding_window=True,
    use_most_recent_splits=True,
    periods_between_splits=5,
    periods_between_train_test=0,
    max_splits=4)  # reduced to 4 from 16 for faster runtime

# Print the train, test split for BM folds
for split_num, (train, test) in enumerate(tscv.split(X=df)):
    print(split_num, train, test)

# %%
# Run the Benchmark
# -----------------
# To start the benchmarking procedure execute its ``run`` method.
#
# If you get an error message at this point, then there is a compatibility issue between your
# benchmark inputs. Check :ref:`Debugging the Benchmark` section for instructions on how to derive valid inputs.