spotforecast2 2.2.2__tar.gz → 2.2.4__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.
Files changed (48) hide show
  1. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/PKG-INFO +1 -1
  2. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/pyproject.toml +1 -1
  3. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/model_selection/bayesian_search.py +2 -1
  4. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/model_selection/random_search.py +38 -44
  5. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/model_selection/spotoptim_search.py +89 -67
  6. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/model_selection/utils_metrics.py +34 -28
  7. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/models/forecaster_recursive_lgbm_full.py +27 -33
  8. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/models/forecaster_recursive_model_full.py +86 -33
  9. spotforecast2-2.2.4/src/spotforecast2/models/forecaster_recursive_xgb_full.py +111 -0
  10. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/strategies.py +1 -0
  11. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/outlier_plots.py +59 -52
  12. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/plotter.py +97 -122
  13. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/time_series_visualization.py +165 -125
  14. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/stats/autocorrelation.py +62 -47
  15. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/task_demo.py +34 -16
  16. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/task_n_to_1_with_covariates.py +81 -105
  17. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/task_n_to_1_with_covariates_and_dataframe.py +96 -103
  18. spotforecast2-2.2.2/src/spotforecast2/models/forecaster_recursive_xgb_full.py +0 -74
  19. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/README.md +0 -0
  20. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/__init__.py +0 -0
  21. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/model_selection/__init__.py +0 -0
  22. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/model_selection/grid_search.py +0 -0
  23. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/models/__init__.py +0 -0
  24. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/__init__.py +0 -0
  25. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/base.py +0 -0
  26. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/clean.py +0 -0
  27. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/defaults.py +0 -0
  28. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/factories.py +0 -0
  29. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/lazy.py +0 -0
  30. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/multi.py +0 -0
  31. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/optuna.py +0 -0
  32. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/predict.py +0 -0
  33. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/runner.py +0 -0
  34. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/search_spaces.py +0 -0
  35. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/multitask/spotoptim.py +0 -0
  36. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/__init__.py +0 -0
  37. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/assets/__init__.py +0 -0
  38. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/assets/prediction_report.html.j2 +0 -0
  39. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/distribution.py +0 -0
  40. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/plots/spectral.py +0 -0
  41. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/py.typed +0 -0
  42. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/stats/__init__.py +0 -0
  43. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/__init__.py +0 -0
  44. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/task_entsoe.py +0 -0
  45. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/task_n_to_1.py +0 -0
  46. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/tasks/task_n_to_1_dataframe.py +0 -0
  47. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/warnings/__init__.py +0 -0
  48. {spotforecast2-2.2.2 → spotforecast2-2.2.4}/src/spotforecast2/warnings/exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: spotforecast2
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Forecasting with spot
5
5
  Author: bartzbeielstein
6
6
  Author-email: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "spotforecast2"
3
- version = "2.2.2"
3
+ version = "2.2.4"
4
4
  description = "Forecasting with spot"
5
5
  readme = "README.md"
6
6
  license = { text = "AGPL-3.0-or-later" }
@@ -33,7 +33,8 @@ from spotforecast2_safe.splitter.utils_common import (
33
33
  from spotforecast2.model_selection.utils_metrics import (
34
34
  _calculate_metrics_one_step_ahead,
35
35
  )
36
- from spotforecast2_safe.forecaster.utils import initialize_lags, date_to_index_position
36
+ from spotforecast2_safe.forecaster.utils import date_to_index_position
37
+ from spotforecast2_safe.preprocessing.forecaster_config import initialize_lags
37
38
  from spotforecast2_safe.exceptions import IgnoredArgumentWarning
38
39
  from spotforecast2_safe.exceptions import set_skforecast_warnings
39
40
 
@@ -85,50 +85,44 @@ def random_search_forecaster(
85
85
  param=value pairs.
86
86
 
87
87
  Examples:
88
- Basic random search with continuous parameter distributions:
89
-
90
- >>> import pandas as pd
91
- >>> import numpy as np
92
- >>> from sklearn.linear_model import Ridge
93
- >>> from scipy.stats import uniform
94
- >>> from spotforecast2_safe.forecaster.recursive import ForecasterRecursive
95
- >>> from spotforecast2_safe.splitter import TimeSeriesFold
96
- >>> from spotforecast2.model_selection.random_search import random_search_forecaster
97
- >>>
98
- >>> # Create sample data
99
- >>> np.random.seed(123)
100
- >>> y = pd.Series(np.random.randn(50), name='y')
101
- >>>
102
- >>> # Set up forecaster and cross-validation
103
- >>> forecaster = ForecasterRecursive(estimator=Ridge(), lags=3)
104
- >>> cv = TimeSeriesFold(steps=3, initial_train_size=20, refit=False)
105
- >>>
106
- >>> # Define parameter distributions with scipy.stats
107
- >>> param_distributions = {
108
- ... 'estimator__alpha': uniform(0.1, 10.0) # Uniform between 0.1 and 10.1
109
- ... }
110
- >>>
111
- >>> # Run random search
112
- >>> results = random_search_forecaster(
113
- ... forecaster=forecaster,
114
- ... y=y,
115
- ... cv=cv,
116
- ... param_distributions=param_distributions,
117
- ... metric='mean_squared_error',
118
- ... n_iter=5,
119
- ... random_state=42,
120
- ... return_best=False,
121
- ... verbose=False,
122
- ... show_progress=False
123
- ... )
124
- >>>
125
- >>> # Check results
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(
@@ -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
- :func:`spotforecast2.model_selection.bayesian_search_forecaster`, so the
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
@@ -34,7 +35,8 @@ except ImportError:
34
35
 
35
36
  from spotforecast2_safe.exceptions import IgnoredArgumentWarning
36
37
  from spotforecast2_safe.forecaster.metrics import _get_metric, add_y_train_argument
37
- from spotforecast2_safe.forecaster.utils import date_to_index_position, initialize_lags
38
+ from spotforecast2_safe.forecaster.utils import date_to_index_position
39
+ from spotforecast2_safe.preprocessing.forecaster_config import initialize_lags
38
40
  from spotforecast2_safe.splitter.split_ts_cv import TimeSeriesFold
39
41
  from spotforecast2_safe.splitter.utils_common import (
40
42
  check_backtesting_input,
@@ -66,33 +68,22 @@ def parse_lags_from_strings(lags_str: str | int | list) -> int | list:
66
68
  Either an integer or a list of integers representing lags.
67
69
 
68
70
  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
71
  ```{python}
82
- import matplotlib.pyplot as plt
83
- import numpy as np
72
+ from spotforecast2.model_selection.spotoptim_search import (
73
+ parse_lags_from_strings,
74
+ )
84
75
 
85
- def check_safety_threshold(val, threshold):
86
- return 1 if val >= threshold else 0
76
+ result_int = parse_lags_from_strings(24)
77
+ print(result_int)
78
+ assert result_int == 24
87
79
 
88
- threshold = 0.95
89
- x = np.linspace(0.8, 1.0, 50)
90
- y = [check_safety_threshold(val, threshold) for val in x]
80
+ result_list = parse_lags_from_strings("[1, 2, 3]")
81
+ print(result_list)
82
+ assert result_list == [1, 2, 3]
91
83
 
92
- plt.step(x, y, where='post')
93
- plt.axvline(threshold, color='red', linestyle='--')
94
- plt.title("Safety Status Transition")
95
- # plt.show() # Commented for non-interactive environments
84
+ result_passthrough = parse_lags_from_strings([4, 8, 12])
85
+ print(result_passthrough)
86
+ assert result_passthrough == [4, 8, 12]
96
87
  ```
97
88
  """
98
89
  if isinstance(lags_str, (int, list)):
@@ -129,7 +120,7 @@ def spotoptim_search_forecaster(
129
120
  """Hyperparameter optimisation for a Forecaster using SpotOptim.
130
121
 
131
122
  Drop-in alternative to
132
- :func:`~spotforecast2.model_selection.bayesian_search_forecaster`
123
+ `bayesian_search_forecaster()`
133
124
  that uses the SpotOptim surrogate-model-based optimizer instead of
134
125
  Optuna's TPE sampler.
135
126
 
@@ -139,7 +130,7 @@ def spotoptim_search_forecaster(
139
130
  cv: Cross-validation strategy — ``TimeSeriesFold`` or
140
131
  ``OneStepAheadFold``.
141
132
  search_space: Hyperparameter search space. Either a
142
- :class:`~spotoptim.hyperparameters.ParameterSet` or a plain
133
+ `ParameterSet` or a plain
143
134
  ``dict`` (see examples below).
144
135
  metric: Metric name, callable, or list thereof.
145
136
  exog: Optional exogenous variable(s).
@@ -165,9 +156,8 @@ def spotoptim_search_forecaster(
165
156
  ``OneStepAheadFold``.
166
157
 
167
158
  Examples:
168
- **1 — Dict-based search space (no ParameterSet needed):**
169
-
170
159
  ```{python}
160
+ # 1 — Dict-based search space (no ParameterSet needed):
171
161
  import numpy as np
172
162
  import pandas as pd
173
163
  from sklearn.linear_model import Ridge
@@ -209,10 +199,23 @@ def spotoptim_search_forecaster(
209
199
  print(f"Contains 'alpha': {'alpha' in results.columns}")
210
200
  ```
211
201
 
212
- **2 — ParameterSet-based search space:**
213
-
214
202
  ```{python}
203
+ # 2 — ParameterSet-based search space:
204
+ import numpy as np
205
+ import pandas as pd
206
+ from sklearn.linear_model import Ridge
215
207
  from spotoptim.hyperparameters import ParameterSet
208
+ from spotforecast2_safe.forecaster.recursive import ForecasterRecursive
209
+ from spotforecast2_safe.splitter import TimeSeriesFold
210
+ from spotforecast2.model_selection import spotoptim_search_forecaster
211
+
212
+ np.random.seed(42)
213
+ y = pd.Series(
214
+ np.random.randn(200).cumsum(),
215
+ index=pd.date_range("2022-01-01", periods=200, freq="h"),
216
+ name="load",
217
+ )
218
+ cv = TimeSeriesFold(steps=5, initial_train_size=150, refit=False)
216
219
 
217
220
  ps = ParameterSet()
218
221
  _ = ps.add_float("alpha", low=0.01, high=10.0)
@@ -309,9 +312,8 @@ def spotoptim_objective(
309
312
  np.ndarray: 1D array of results for the primary metric.
310
313
 
311
314
  Examples:
312
- Generating textual output of parameter evaluation:
313
-
314
315
  ```{python}
316
+ # Demonstrate the call structure of spotoptim_objective.
315
317
  import numpy as np
316
318
  import pandas as pd
317
319
  from spotforecast2_safe.splitter import TimeSeriesFold
@@ -550,9 +552,23 @@ def spotoptim_search(
550
552
  all_lags: list = []
551
553
  all_params: list[dict] = []
552
554
 
555
+ # Single trial-level progress bar. Each entry in ``X`` is one trial
556
+ # (initial design point or sequential proposal), so we advance by
557
+ # ``len(X)`` per objective call. ``n_trials`` (== SpotOptim's
558
+ # ``max_iter``) is the total budget — it already includes the
559
+ # ``n_initial`` design points, so the total bar length is ``n_trials``.
560
+ # The inner backtesting fold loop is forced silent below to avoid the
561
+ # per-trial bar spam that used to stack dozens of fast "100% 35/35"
562
+ # bars in notebook output.
563
+ trial_bar = (
564
+ tqdm(total=n_trials, desc="SpotOptim trials", leave=True)
565
+ if show_progress
566
+ else None
567
+ )
568
+
553
569
  # --- Objective function -----------------------------------------------
554
570
  def _objective_wrapper(X: np.ndarray) -> np.ndarray:
555
- return spotoptim_objective(
571
+ result = spotoptim_objective(
556
572
  X=X,
557
573
  forecaster_search=forecaster_search,
558
574
  cv_name=cv_name,
@@ -562,7 +578,7 @@ def spotoptim_search(
562
578
  exog=exog,
563
579
  n_jobs=n_jobs,
564
580
  verbose=verbose,
565
- show_progress=show_progress,
581
+ show_progress=True,
566
582
  suppress_warnings=suppress_warnings,
567
583
  var_name=var_name,
568
584
  var_type=var_type,
@@ -571,6 +587,9 @@ def spotoptim_search(
571
587
  all_lags=all_lags,
572
588
  all_params=all_params,
573
589
  )
590
+ if trial_bar is not None:
591
+ trial_bar.update(len(X))
592
+ return result
574
593
 
575
594
  # --- Run SpotOptim ----------------------------------------------------
576
595
  optimizer = SpotOptim(
@@ -586,7 +605,11 @@ def spotoptim_search(
586
605
  **kwargs_spotoptim_,
587
606
  )
588
607
 
589
- optimizer.optimize()
608
+ try:
609
+ optimizer.optimize()
610
+ finally:
611
+ if trial_bar is not None:
612
+ trial_bar.close()
590
613
 
591
614
  # --- Build results DataFrame ------------------------------------------
592
615
  lags_list = [
@@ -652,21 +675,20 @@ def convert_search_space(
652
675
  - var_trans: List of transformation functions (e.g., log10) or None.
653
676
 
654
677
  Examples:
655
- Basic usage:
656
-
657
- >>> from spotoptim.hyperparameters import ParameterSet
658
- >>> from spotforecast2.model_selection.spotoptim_search import (
659
- ... convert_search_space,
660
- ... )
661
- >>> ps = ParameterSet()
662
- >>> _ = ps.add_float("alpha", 0.01, 10.0)
663
- >>> b, t, n, tr = convert_search_space(ps)
664
- >>> b
665
- [(0.01, 10.0)]
666
- >>> t
667
- ['float']
668
-
669
- Converting a complex dictionary search space:
678
+ ```{python}
679
+ from spotoptim.hyperparameters import ParameterSet
680
+ from spotforecast2.model_selection.spotoptim_search import (
681
+ convert_search_space,
682
+ )
683
+
684
+ ps = ParameterSet()
685
+ _ = ps.add_float("alpha", 0.01, 10.0)
686
+ b, t, n, tr = convert_search_space(ps)
687
+ print(b)
688
+ assert b == [(0.01, 10.0)]
689
+ print(t)
690
+ assert t == ["float"]
691
+ ```
670
692
 
671
693
  ```{python}
672
694
  from spotforecast2.model_selection.spotoptim_search import convert_search_space
@@ -674,7 +696,7 @@ def convert_search_space(
674
696
  search_space = {
675
697
  "learning_rate": (0.001, 0.1, "log10"),
676
698
  "max_depth": (2, 10),
677
- "model_type": ["RandomForest", "XGBoost"]
699
+ "model_type": ["RandomForest", "XGBoost"],
678
700
  }
679
701
 
680
702
  bounds, vt, vn, vtr = convert_search_space(search_space)
@@ -764,21 +786,21 @@ def array_to_params(
764
786
  Dictionary mapping parameter names to typed values.
765
787
 
766
788
  Examples:
767
- Basic usage:
768
-
769
- >>> import numpy as np
770
- >>> from spotforecast2.model_selection.spotoptim_search import (
771
- ... array_to_params,
772
- ... )
773
- >>> array_to_params(
774
- ... np.array([100.0, 0.05]),
775
- ... var_name=["n_estimators", "lr"],
776
- ... var_type=["int", "float"],
777
- ... bounds=[(50, 200), (0.01, 0.3)],
778
- ... )
779
- {'n_estimators': 100, 'lr': 0.05}
780
-
781
- Generating textual output of parameter mapping:
789
+ ```{python}
790
+ import numpy as np
791
+ from spotforecast2.model_selection.spotoptim_search import (
792
+ array_to_params,
793
+ )
794
+
795
+ result = array_to_params(
796
+ np.array([100.0, 0.05]),
797
+ var_name=["n_estimators", "lr"],
798
+ var_type=["int", "float"],
799
+ bounds=[(50, 200), (0.01, 0.3)],
800
+ )
801
+ print(result)
802
+ assert result == {"n_estimators": 100, "lr": 0.05}
803
+ ```
782
804
 
783
805
  ```{python}
784
806
  import numpy as np
@@ -38,34 +38,40 @@ def _calculate_metrics_one_step_ahead(
38
38
  rolling features.
39
39
 
40
40
  Examples:
41
- >>> import pandas as pd
42
- >>> import numpy as np
43
- >>> from sklearn.linear_model import LinearRegression
44
- >>> from sklearn.metrics import mean_squared_error
45
- >>> from spotforecast2_safe.forecaster.recursive import ForecasterRecursive
46
- >>> from spotforecast2.model_selection.utils_metrics import _calculate_metrics_one_step_ahead
47
- >>>
48
- >>> forecaster = ForecasterRecursive(LinearRegression(), lags=2)
49
- >>>
50
- >>> # Mock training data (already transformed into predictors/target)
51
- >>> X_train = pd.DataFrame(np.array([[1, 2], [2, 3], [3, 4]]), columns=['lag_2', 'lag_1'])
52
- >>> y_train = pd.Series(np.array([3, 4, 5]), name='y')
53
- >>>
54
- >>> # Mock test data
55
- >>> X_test = pd.DataFrame(np.array([[4, 5]]), columns=['lag_2', 'lag_1'])
56
- >>> y_test = pd.Series(np.array([6]), name='y')
57
- >>>
58
- >>> metrics = [mean_squared_error]
59
- >>> result = _calculate_metrics_one_step_ahead(
60
- ... forecaster=forecaster,
61
- ... metrics=metrics,
62
- ... X_train=X_train,
63
- ... y_train=y_train,
64
- ... X_test=X_test,
65
- ... y_test=y_test
66
- ... )
67
- >>> print(result)
68
- [0.0]
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 :class:`ForecasterRecursiveLGBMFull`, which combines
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 :class:`~spotforecast2.models.ForecasterRecursiveModelFull`.
9
+ from `ForecasterRecursiveModelFull`.
10
10
 
11
11
  Examples:
12
- >>> from spotforecast2.models import ForecasterRecursiveLGBMFull
13
- >>> model = ForecasterRecursiveLGBMFull(iteration=0)
14
- >>> model.name
15
- 'lgbm'
16
- >>> model.forecaster is not None
17
- True
18
- >>> model.n_trials
19
- 10
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
- :class:`~spotforecast2_safe.forecaster.wrappers.lgbm.ForecasterRecursiveLGBM`
39
- (``spotforecast2-safe``) and adds the real :meth:`tune` and
40
- :meth:`get_global_shap_feature_importance` from
41
- :class:`~spotforecast2.models.ForecasterRecursiveModelFull`.
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 :meth:`tune` and SHAP methods resolve from
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
- >>> from spotforecast2.models import ForecasterRecursiveLGBMFull
55
- >>> model = ForecasterRecursiveLGBMFull(iteration=0)
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
- ```{python}
66
- from spotforecast2.models import ForecasterRecursiveLGBMFull
67
- model = ForecasterRecursiveLGBMFull(iteration=0)
68
- print(f"Model name: {model.name}")
69
- print(f"Trials: {model.n_trials}")
70
- print(f"Has tune: {callable(model.tune)}")
71
- print(f"Has SHAP: {callable(model.get_global_shap_feature_importance)}")
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):