Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions examples/skforecast/skforecast_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
Skforecast Integration Example - Hyperparameter Tuning for Time Series Forecasting

This example demonstrates how to use Hyperactive to tune hyperparameters of a
skforecast ForecasterRecursive model. It uses the SkforecastOptCV class which
provides a familiar sklearn-like API for integrating skforecast models with
Hyperactive's optimization algorithms.

Characteristics:
- Integration with skforecast's backtesting functionality
- Tuning of regressor hyperparameters (e.g., RandomForestRegressor)
- Uses HillClimbing optimizer (can be swapped for any Hyperactive optimizer)
- Time series cross-validation via backtesting
"""

import numpy as np
import pandas as pd
from skforecast.recursive import ForecasterRecursive
from sklearn.ensemble import RandomForestRegressor
from hyperactive.opt import HillClimbing
from hyperactive.integrations.skforecast import SkforecastOptCV

# Generate synthetic data
data = pd.Series(
np.random.randn(100),
index=pd.date_range(start="2020-01-01", periods=100, freq="D"),
name="y",
)

# Define forecaster
forecaster = ForecasterRecursive(
regressor=RandomForestRegressor(random_state=123), lags=5
)

# Define optimizer
optimizer = HillClimbing(
search_space={
"n_estimators": list(range(10, 100, 10)),
"max_depth": list(range(2, 10)),
},
n_iter=10,
)

# Define SkforecastOptCV
opt_cv = SkforecastOptCV(
forecaster=forecaster,
optimizer=optimizer,
steps=5,
metric="mean_squared_error",
initial_train_size=50,
verbose=True,
)

# Fit
print("Fitting...")
opt_cv.fit(y=data)

# Predict
print("Predicting...")
predictions = opt_cv.predict(steps=5)
print("Predictions:")
print(predictions)
print("Best params:", opt_cv.best_params_)
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ sktime-integration = [
"skpro",
'sktime; python_version < "3.14"',
]
skforecast-integration = [
"skforecast",
]
integrations = [
"scikit-learn <1.8.0",
"skpro",
'sktime; python_version < "3.14"',
"skforecast",
]
build = [
"setuptools",
"build",
Expand Down
4 changes: 4 additions & 0 deletions src/hyperactive/experiment/integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from hyperactive.experiment.integrations.sktime_forecasting import (
SktimeForecastingExperiment,
)
from hyperactive.experiment.integrations.skforecast_forecasting import (
SkforecastExperiment,
)
from hyperactive.experiment.integrations.torch_lightning_experiment import (
TorchExperiment,
)
Expand All @@ -20,5 +23,6 @@
"SkproProbaRegExperiment",
"SktimeClassificationExperiment",
"SktimeForecastingExperiment",
"SkforecastExperiment",
"TorchExperiment",
]
146 changes: 146 additions & 0 deletions src/hyperactive/experiment/integrations/skforecast_forecasting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""Experiment adapter for skforecast backtesting experiments."""
# copyright: hyperactive developers, MIT License (see LICENSE file)

import copy
import numpy as np
from hyperactive.base import BaseExperiment


class SkforecastExperiment(BaseExperiment):
"""Experiment adapter for skforecast backtesting experiments.

This class is used to perform backtesting experiments using a given
skforecast forecaster. It allows for hyperparameter tuning and evaluation of
the model's performance.

Parameters
----------
forecaster : skforecast forecaster
skforecast forecaster to benchmark

y : pandas Series
Target time series used in the evaluation experiment

steps : int
Number of steps to predict

metric : str or callable
Metric used to quantify the goodness of fit of the model

initial_train_size : int
Number of samples in the initial training set

exog : pandas Series or DataFrame, optional
Exogenous variable/s used in the evaluation experiment

refit : bool, optional
Whether to re-fit the forecaster in each iteration

fixed_train_size : bool, optional
If True, the train size doesn't increase but moves by `steps` in each iteration

gap : int, optional
Number of samples to exclude from the end of each training set and the start of the test set

allow_incomplete_fold : bool, optional
If True, the last fold is allowed to have fewer samples than `steps`

return_best : bool, optional
If True, the best model is returned

n_jobs : int or 'auto', optional
Number of jobs to run in parallel

verbose : bool, optional
Print summary figures

show_progress : bool, optional
Whether to show a progress bar
"""

def __init__(
self,
forecaster,
y,
steps,
metric,
initial_train_size,
exog=None,
refit=False,
fixed_train_size=False,
gap=0,
allow_incomplete_fold=True,
return_best=False,
n_jobs="auto",
verbose=False,
show_progress=False,
):
self.forecaster = forecaster
self.y = y
self.steps = steps
self.metric = metric
self.initial_train_size = initial_train_size
self.exog = exog
self.refit = refit
self.fixed_train_size = fixed_train_size
self.gap = gap
self.allow_incomplete_fold = allow_incomplete_fold
self.return_best = return_best
self.n_jobs = n_jobs
self.verbose = verbose
self.show_progress = show_progress

super().__init__()

def _evaluate(self, params):
"""Evaluate the parameters.

Parameters
----------
params : dict with string keys
Parameters to evaluate.

Returns
-------
float
The value of the parameters as per evaluation.
dict
Additional metadata about the search.
"""
from skforecast.model_selection import backtesting_forecaster
from skforecast.model_selection import TimeSeriesFold

forecaster = copy.deepcopy(self.forecaster)
forecaster.set_params(params)

cv = TimeSeriesFold(
steps=self.steps,
initial_train_size=self.initial_train_size,
refit=self.refit,
fixed_train_size=self.fixed_train_size,
gap=self.gap,
allow_incomplete_fold=self.allow_incomplete_fold,
)

results, _ = backtesting_forecaster(
forecaster=forecaster,
y=self.y,
cv=cv,
metric=self.metric,
exog=self.exog,
n_jobs=self.n_jobs,
verbose=self.verbose,
show_progress=self.show_progress,
)

if isinstance(self.metric, str):
metric_name = self.metric
else:
metric_name = (
self.metric.__name__ if hasattr(self.metric, "__name__") else "score"
)

# backtesting_forecaster returns a DataFrame
res_float = results[metric_name].iloc[0]

return res_float, {"results": results}
5 changes: 5 additions & 0 deletions src/hyperactive/integrations/skforecast/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# copyright: hyperactive developers, MIT License (see LICENSE file)

from .skforecast_opt_cv import SkforecastOptCV

__all__ = ["SkforecastOptCV"]
Loading
Loading