spotforecast2 2.2.2__tar.gz → 2.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/PKG-INFO +1 -1
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/pyproject.toml +1 -1
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/random_search.py +38 -44
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/spotoptim_search.py +87 -66
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/utils_metrics.py +34 -28
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/models/forecaster_recursive_lgbm_full.py +27 -33
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/models/forecaster_recursive_model_full.py +86 -33
- spotforecast2-2.2.3/src/spotforecast2/models/forecaster_recursive_xgb_full.py +111 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/strategies.py +1 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/outlier_plots.py +59 -52
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/plotter.py +97 -122
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/time_series_visualization.py +165 -125
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/stats/autocorrelation.py +62 -47
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_demo.py +34 -16
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1_with_covariates.py +81 -105
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1_with_covariates_and_dataframe.py +96 -103
- spotforecast2-2.2.2/src/spotforecast2/models/forecaster_recursive_xgb_full.py +0 -74
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/README.md +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/bayesian_search.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/grid_search.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/models/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/base.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/clean.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/defaults.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/factories.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/lazy.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/multi.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/optuna.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/predict.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/runner.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/search_spaces.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/spotoptim.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/assets/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/assets/prediction_report.html.j2 +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/distribution.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/spectral.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/py.typed +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/stats/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_entsoe.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1_dataframe.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/warnings/__init__.py +0 -0
- {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/warnings/exceptions.py +0 -0
{spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/random_search.py
RENAMED
|
@@ -85,50 +85,44 @@ def random_search_forecaster(
|
|
|
85
85
|
param=value pairs.
|
|
86
86
|
|
|
87
87
|
Examples:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
>>> print(results.shape[0])
|
|
127
|
-
5
|
|
128
|
-
>>> print('estimator__alpha' in results.columns)
|
|
129
|
-
True
|
|
130
|
-
>>> print('mean_squared_error' in results.columns)
|
|
131
|
-
True
|
|
88
|
+
```{python}
|
|
89
|
+
import numpy as np
|
|
90
|
+
import pandas as pd
|
|
91
|
+
from sklearn.linear_model import Ridge
|
|
92
|
+
from scipy.stats import uniform
|
|
93
|
+
from spotforecast2_safe.forecaster.recursive import ForecasterRecursive
|
|
94
|
+
from spotforecast2_safe.splitter import TimeSeriesFold
|
|
95
|
+
from spotforecast2.model_selection.random_search import random_search_forecaster
|
|
96
|
+
|
|
97
|
+
rng = np.random.default_rng(1234)
|
|
98
|
+
y = pd.Series(rng.standard_normal(30), name="y")
|
|
99
|
+
|
|
100
|
+
forecaster = ForecasterRecursive(estimator=Ridge(), lags=2)
|
|
101
|
+
cv = TimeSeriesFold(steps=2, initial_train_size=20, refit=False)
|
|
102
|
+
|
|
103
|
+
param_distributions = {
|
|
104
|
+
"estimator__alpha": uniform(0.1, 10.0),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
results = random_search_forecaster(
|
|
108
|
+
forecaster=forecaster,
|
|
109
|
+
y=y,
|
|
110
|
+
cv=cv,
|
|
111
|
+
param_distributions=param_distributions,
|
|
112
|
+
metric="mean_squared_error",
|
|
113
|
+
n_iter=2,
|
|
114
|
+
random_state=1234,
|
|
115
|
+
return_best=False,
|
|
116
|
+
verbose=False,
|
|
117
|
+
show_progress=False,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
print(results.shape)
|
|
121
|
+
print(results.columns.tolist())
|
|
122
|
+
assert results.shape[0] == 2
|
|
123
|
+
assert "estimator__alpha" in results.columns
|
|
124
|
+
assert "mean_squared_error" in results.columns
|
|
125
|
+
```
|
|
132
126
|
"""
|
|
133
127
|
|
|
134
128
|
param_grid = list(
|
{spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/spotoptim_search.py
RENAMED
|
@@ -7,7 +7,7 @@ Hyperparameter search functions for forecasters using SpotOptim.
|
|
|
7
7
|
This module provides an alternative to Bayesian (Optuna-based) search
|
|
8
8
|
by leveraging the SpotOptim surrogate-model-based optimizer. It
|
|
9
9
|
follows the same interface as
|
|
10
|
-
|
|
10
|
+
`spotforecast2.model_selection.bayesian_search_forecaster()`, so the
|
|
11
11
|
two can be used interchangeably.
|
|
12
12
|
"""
|
|
13
13
|
|
|
@@ -21,6 +21,7 @@ from typing import Any, Callable, Dict
|
|
|
21
21
|
|
|
22
22
|
import numpy as np
|
|
23
23
|
import pandas as pd
|
|
24
|
+
from tqdm.auto import tqdm
|
|
24
25
|
|
|
25
26
|
try:
|
|
26
27
|
from spotoptim import SpotOptim
|
|
@@ -66,33 +67,22 @@ def parse_lags_from_strings(lags_str: str | int | list) -> int | list:
|
|
|
66
67
|
Either an integer or a list of integers representing lags.
|
|
67
68
|
|
|
68
69
|
Examples:
|
|
69
|
-
Basic parsing:
|
|
70
|
-
|
|
71
|
-
>>> from spotforecast2.model_selection.spotoptim_search import (
|
|
72
|
-
... parse_lags_from_strings,
|
|
73
|
-
... )
|
|
74
|
-
>>> parse_lags_from_strings(24)
|
|
75
|
-
24
|
|
76
|
-
>>> parse_lags_from_strings("[1, 2, 3]")
|
|
77
|
-
[1, 2, 3]
|
|
78
|
-
|
|
79
|
-
Visualizing the safety threshold (Example of dynamic documentation):
|
|
80
|
-
|
|
81
70
|
```{python}
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
from spotforecast2.model_selection.spotoptim_search import (
|
|
72
|
+
parse_lags_from_strings,
|
|
73
|
+
)
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
result_int = parse_lags_from_strings(24)
|
|
76
|
+
print(result_int)
|
|
77
|
+
assert result_int == 24
|
|
87
78
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
result_list = parse_lags_from_strings("[1, 2, 3]")
|
|
80
|
+
print(result_list)
|
|
81
|
+
assert result_list == [1, 2, 3]
|
|
91
82
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# plt.show() # Commented for non-interactive environments
|
|
83
|
+
result_passthrough = parse_lags_from_strings([4, 8, 12])
|
|
84
|
+
print(result_passthrough)
|
|
85
|
+
assert result_passthrough == [4, 8, 12]
|
|
96
86
|
```
|
|
97
87
|
"""
|
|
98
88
|
if isinstance(lags_str, (int, list)):
|
|
@@ -129,7 +119,7 @@ def spotoptim_search_forecaster(
|
|
|
129
119
|
"""Hyperparameter optimisation for a Forecaster using SpotOptim.
|
|
130
120
|
|
|
131
121
|
Drop-in alternative to
|
|
132
|
-
|
|
122
|
+
`bayesian_search_forecaster()`
|
|
133
123
|
that uses the SpotOptim surrogate-model-based optimizer instead of
|
|
134
124
|
Optuna's TPE sampler.
|
|
135
125
|
|
|
@@ -139,7 +129,7 @@ def spotoptim_search_forecaster(
|
|
|
139
129
|
cv: Cross-validation strategy — ``TimeSeriesFold`` or
|
|
140
130
|
``OneStepAheadFold``.
|
|
141
131
|
search_space: Hyperparameter search space. Either a
|
|
142
|
-
|
|
132
|
+
`ParameterSet` or a plain
|
|
143
133
|
``dict`` (see examples below).
|
|
144
134
|
metric: Metric name, callable, or list thereof.
|
|
145
135
|
exog: Optional exogenous variable(s).
|
|
@@ -165,9 +155,8 @@ def spotoptim_search_forecaster(
|
|
|
165
155
|
``OneStepAheadFold``.
|
|
166
156
|
|
|
167
157
|
Examples:
|
|
168
|
-
**1 — Dict-based search space (no ParameterSet needed):**
|
|
169
|
-
|
|
170
158
|
```{python}
|
|
159
|
+
# 1 — Dict-based search space (no ParameterSet needed):
|
|
171
160
|
import numpy as np
|
|
172
161
|
import pandas as pd
|
|
173
162
|
from sklearn.linear_model import Ridge
|
|
@@ -209,10 +198,23 @@ def spotoptim_search_forecaster(
|
|
|
209
198
|
print(f"Contains 'alpha': {'alpha' in results.columns}")
|
|
210
199
|
```
|
|
211
200
|
|
|
212
|
-
**2 — ParameterSet-based search space:**
|
|
213
|
-
|
|
214
201
|
```{python}
|
|
202
|
+
# 2 — ParameterSet-based search space:
|
|
203
|
+
import numpy as np
|
|
204
|
+
import pandas as pd
|
|
205
|
+
from sklearn.linear_model import Ridge
|
|
215
206
|
from spotoptim.hyperparameters import ParameterSet
|
|
207
|
+
from spotforecast2_safe.forecaster.recursive import ForecasterRecursive
|
|
208
|
+
from spotforecast2_safe.splitter import TimeSeriesFold
|
|
209
|
+
from spotforecast2.model_selection import spotoptim_search_forecaster
|
|
210
|
+
|
|
211
|
+
np.random.seed(42)
|
|
212
|
+
y = pd.Series(
|
|
213
|
+
np.random.randn(200).cumsum(),
|
|
214
|
+
index=pd.date_range("2022-01-01", periods=200, freq="h"),
|
|
215
|
+
name="load",
|
|
216
|
+
)
|
|
217
|
+
cv = TimeSeriesFold(steps=5, initial_train_size=150, refit=False)
|
|
216
218
|
|
|
217
219
|
ps = ParameterSet()
|
|
218
220
|
_ = ps.add_float("alpha", low=0.01, high=10.0)
|
|
@@ -309,9 +311,8 @@ def spotoptim_objective(
|
|
|
309
311
|
np.ndarray: 1D array of results for the primary metric.
|
|
310
312
|
|
|
311
313
|
Examples:
|
|
312
|
-
Generating textual output of parameter evaluation:
|
|
313
|
-
|
|
314
314
|
```{python}
|
|
315
|
+
# Demonstrate the call structure of spotoptim_objective.
|
|
315
316
|
import numpy as np
|
|
316
317
|
import pandas as pd
|
|
317
318
|
from spotforecast2_safe.splitter import TimeSeriesFold
|
|
@@ -550,9 +551,23 @@ def spotoptim_search(
|
|
|
550
551
|
all_lags: list = []
|
|
551
552
|
all_params: list[dict] = []
|
|
552
553
|
|
|
554
|
+
# Single trial-level progress bar. Each entry in ``X`` is one trial
|
|
555
|
+
# (initial design point or sequential proposal), so we advance by
|
|
556
|
+
# ``len(X)`` per objective call. ``n_trials`` (== SpotOptim's
|
|
557
|
+
# ``max_iter``) is the total budget — it already includes the
|
|
558
|
+
# ``n_initial`` design points, so the total bar length is ``n_trials``.
|
|
559
|
+
# The inner backtesting fold loop is forced silent below to avoid the
|
|
560
|
+
# per-trial bar spam that used to stack dozens of fast "100% 35/35"
|
|
561
|
+
# bars in notebook output.
|
|
562
|
+
trial_bar = (
|
|
563
|
+
tqdm(total=n_trials, desc="SpotOptim trials", leave=True)
|
|
564
|
+
if show_progress
|
|
565
|
+
else None
|
|
566
|
+
)
|
|
567
|
+
|
|
553
568
|
# --- Objective function -----------------------------------------------
|
|
554
569
|
def _objective_wrapper(X: np.ndarray) -> np.ndarray:
|
|
555
|
-
|
|
570
|
+
result = spotoptim_objective(
|
|
556
571
|
X=X,
|
|
557
572
|
forecaster_search=forecaster_search,
|
|
558
573
|
cv_name=cv_name,
|
|
@@ -562,7 +577,7 @@ def spotoptim_search(
|
|
|
562
577
|
exog=exog,
|
|
563
578
|
n_jobs=n_jobs,
|
|
564
579
|
verbose=verbose,
|
|
565
|
-
show_progress=
|
|
580
|
+
show_progress=False,
|
|
566
581
|
suppress_warnings=suppress_warnings,
|
|
567
582
|
var_name=var_name,
|
|
568
583
|
var_type=var_type,
|
|
@@ -571,6 +586,9 @@ def spotoptim_search(
|
|
|
571
586
|
all_lags=all_lags,
|
|
572
587
|
all_params=all_params,
|
|
573
588
|
)
|
|
589
|
+
if trial_bar is not None:
|
|
590
|
+
trial_bar.update(len(X))
|
|
591
|
+
return result
|
|
574
592
|
|
|
575
593
|
# --- Run SpotOptim ----------------------------------------------------
|
|
576
594
|
optimizer = SpotOptim(
|
|
@@ -586,7 +604,11 @@ def spotoptim_search(
|
|
|
586
604
|
**kwargs_spotoptim_,
|
|
587
605
|
)
|
|
588
606
|
|
|
589
|
-
|
|
607
|
+
try:
|
|
608
|
+
optimizer.optimize()
|
|
609
|
+
finally:
|
|
610
|
+
if trial_bar is not None:
|
|
611
|
+
trial_bar.close()
|
|
590
612
|
|
|
591
613
|
# --- Build results DataFrame ------------------------------------------
|
|
592
614
|
lags_list = [
|
|
@@ -652,21 +674,20 @@ def convert_search_space(
|
|
|
652
674
|
- var_trans: List of transformation functions (e.g., log10) or None.
|
|
653
675
|
|
|
654
676
|
Examples:
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
[(0.01, 10.0)]
|
|
666
|
-
|
|
667
|
-
[
|
|
668
|
-
|
|
669
|
-
Converting a complex dictionary search space:
|
|
677
|
+
```{python}
|
|
678
|
+
from spotoptim.hyperparameters import ParameterSet
|
|
679
|
+
from spotforecast2.model_selection.spotoptim_search import (
|
|
680
|
+
convert_search_space,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
ps = ParameterSet()
|
|
684
|
+
_ = ps.add_float("alpha", 0.01, 10.0)
|
|
685
|
+
b, t, n, tr = convert_search_space(ps)
|
|
686
|
+
print(b)
|
|
687
|
+
assert b == [(0.01, 10.0)]
|
|
688
|
+
print(t)
|
|
689
|
+
assert t == ["float"]
|
|
690
|
+
```
|
|
670
691
|
|
|
671
692
|
```{python}
|
|
672
693
|
from spotforecast2.model_selection.spotoptim_search import convert_search_space
|
|
@@ -674,7 +695,7 @@ def convert_search_space(
|
|
|
674
695
|
search_space = {
|
|
675
696
|
"learning_rate": (0.001, 0.1, "log10"),
|
|
676
697
|
"max_depth": (2, 10),
|
|
677
|
-
"model_type": ["RandomForest", "XGBoost"]
|
|
698
|
+
"model_type": ["RandomForest", "XGBoost"],
|
|
678
699
|
}
|
|
679
700
|
|
|
680
701
|
bounds, vt, vn, vtr = convert_search_space(search_space)
|
|
@@ -764,21 +785,21 @@ def array_to_params(
|
|
|
764
785
|
Dictionary mapping parameter names to typed values.
|
|
765
786
|
|
|
766
787
|
Examples:
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
788
|
+
```{python}
|
|
789
|
+
import numpy as np
|
|
790
|
+
from spotforecast2.model_selection.spotoptim_search import (
|
|
791
|
+
array_to_params,
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
result = array_to_params(
|
|
795
|
+
np.array([100.0, 0.05]),
|
|
796
|
+
var_name=["n_estimators", "lr"],
|
|
797
|
+
var_type=["int", "float"],
|
|
798
|
+
bounds=[(50, 200), (0.01, 0.3)],
|
|
799
|
+
)
|
|
800
|
+
print(result)
|
|
801
|
+
assert result == {"n_estimators": 100, "lr": 0.05}
|
|
802
|
+
```
|
|
782
803
|
|
|
783
804
|
```{python}
|
|
784
805
|
import numpy as np
|
{spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/utils_metrics.py
RENAMED
|
@@ -38,34 +38,40 @@ def _calculate_metrics_one_step_ahead(
|
|
|
38
38
|
rolling features.
|
|
39
39
|
|
|
40
40
|
Examples:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
41
|
+
```{python}
|
|
42
|
+
import numpy as np
|
|
43
|
+
import pandas as pd
|
|
44
|
+
from sklearn.linear_model import LinearRegression
|
|
45
|
+
from sklearn.metrics import mean_absolute_error
|
|
46
|
+
from spotforecast2_safe.forecaster.metrics import add_y_train_argument
|
|
47
|
+
from spotforecast2_safe.forecaster.recursive import ForecasterRecursive
|
|
48
|
+
from spotforecast2.model_selection.utils_metrics import (
|
|
49
|
+
_calculate_metrics_one_step_ahead,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Predictors are already extracted lag features; y = x+1 (linear).
|
|
53
|
+
X_train = pd.DataFrame(
|
|
54
|
+
{"lag_2": [1.0, 2.0, 3.0, 4.0, 5.0], "lag_1": [2.0, 3.0, 4.0, 5.0, 6.0]}
|
|
55
|
+
)
|
|
56
|
+
y_train = pd.Series([3.0, 4.0, 5.0, 6.0, 7.0], name="y")
|
|
57
|
+
X_test = pd.DataFrame({"lag_2": [5.0], "lag_1": [6.0]})
|
|
58
|
+
# Deliberately set y_test=9.0 (model predicts 7.0) to get MAE=2.0.
|
|
59
|
+
y_test = pd.Series([9.0], name="y")
|
|
60
|
+
|
|
61
|
+
forecaster = ForecasterRecursive(LinearRegression(), lags=2)
|
|
62
|
+
# Metrics must accept y_train; use add_y_train_argument to wrap sklearn metrics.
|
|
63
|
+
metrics = [add_y_train_argument(mean_absolute_error)]
|
|
64
|
+
result = _calculate_metrics_one_step_ahead(
|
|
65
|
+
forecaster=forecaster,
|
|
66
|
+
metrics=metrics,
|
|
67
|
+
X_train=X_train,
|
|
68
|
+
y_train=y_train,
|
|
69
|
+
X_test=X_test,
|
|
70
|
+
y_test=y_test,
|
|
71
|
+
)
|
|
72
|
+
print(f"MAE: {result[0]:.4f}")
|
|
73
|
+
assert result[0] == 2.0
|
|
74
|
+
```
|
|
69
75
|
"""
|
|
70
76
|
|
|
71
77
|
if type(forecaster).__name__ == "ForecasterDirect":
|
|
@@ -3,20 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
"""LGBM forecaster with real Bayesian tuning and SHAP.
|
|
5
5
|
|
|
6
|
-
This module provides
|
|
6
|
+
This module provides `ForecasterRecursiveLGBMFull`, which combines
|
|
7
7
|
the LightGBM forecaster from ``spotforecast2-safe`` with Bayesian
|
|
8
8
|
hyperparameter optimisation (Optuna) and SHAP-based feature importance
|
|
9
|
-
from
|
|
9
|
+
from `ForecasterRecursiveModelFull`.
|
|
10
10
|
|
|
11
11
|
Examples:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
```{python}
|
|
13
|
+
from spotforecast2.models import ForecasterRecursiveLGBMFull
|
|
14
|
+
|
|
15
|
+
model = ForecasterRecursiveLGBMFull(iteration=0)
|
|
16
|
+
assert model.name == "lgbm"
|
|
17
|
+
assert model.forecaster is not None
|
|
18
|
+
assert model.n_trials == 10
|
|
19
|
+
print(f"name={model.name}, n_trials={model.n_trials}, iteration={model.iteration}")
|
|
20
|
+
```
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
from __future__ import annotations
|
|
@@ -35,12 +36,12 @@ class ForecasterRecursiveLGBMFull(
|
|
|
35
36
|
"""LGBM forecaster with real Bayesian tuning and SHAP.
|
|
36
37
|
|
|
37
38
|
Inherits the LightGBM forecaster initialisation from
|
|
38
|
-
|
|
39
|
-
(``spotforecast2-safe``) and adds the real
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
`ForecasterRecursiveLGBM`
|
|
40
|
+
(``spotforecast2-safe``) and adds the real `tune()` and
|
|
41
|
+
`get_global_shap_feature_importance()` from
|
|
42
|
+
`ForecasterRecursiveModelFull`.
|
|
42
43
|
|
|
43
|
-
The MRO ensures that
|
|
44
|
+
The MRO ensures that `tune()` and SHAP methods resolve from
|
|
44
45
|
``ForecasterRecursiveModelFull``, while the LightGBM-specific
|
|
45
46
|
``__init__`` (estimator wiring) comes from ``ForecasterRecursiveLGBM``.
|
|
46
47
|
|
|
@@ -51,25 +52,18 @@ class ForecasterRecursiveLGBMFull(
|
|
|
51
52
|
``predict_size``, ``train_size``).
|
|
52
53
|
|
|
53
54
|
Examples:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
>>> model.name
|
|
57
|
-
'lgbm'
|
|
58
|
-
>>> model.forecaster is not None
|
|
59
|
-
True
|
|
60
|
-
>>> model.n_trials
|
|
61
|
-
10
|
|
62
|
-
>>> model.iteration
|
|
63
|
-
0
|
|
55
|
+
```{python}
|
|
56
|
+
from spotforecast2.models import ForecasterRecursiveLGBMFull
|
|
64
57
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
model = ForecasterRecursiveLGBMFull(iteration=0)
|
|
59
|
+
assert model.name == "lgbm"
|
|
60
|
+
assert model.forecaster is not None
|
|
61
|
+
assert model.n_trials == 10
|
|
62
|
+
assert model.iteration == 0
|
|
63
|
+
print(f"name={model.name}, n_trials={model.n_trials}, iteration={model.iteration}")
|
|
64
|
+
print(f"tune callable: {callable(model.tune)}")
|
|
65
|
+
print(f"shap callable: {callable(model.get_global_shap_feature_importance)}")
|
|
66
|
+
```
|
|
73
67
|
"""
|
|
74
68
|
|
|
75
69
|
def __init__(self, iteration: int, lags: int = 12, **kwargs: Any):
|