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.
Files changed (48) hide show
  1. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/PKG-INFO +1 -1
  2. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/pyproject.toml +1 -1
  3. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/random_search.py +38 -44
  4. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/spotoptim_search.py +87 -66
  5. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/utils_metrics.py +34 -28
  6. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/models/forecaster_recursive_lgbm_full.py +27 -33
  7. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/models/forecaster_recursive_model_full.py +86 -33
  8. spotforecast2-2.2.3/src/spotforecast2/models/forecaster_recursive_xgb_full.py +111 -0
  9. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/strategies.py +1 -0
  10. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/outlier_plots.py +59 -52
  11. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/plotter.py +97 -122
  12. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/time_series_visualization.py +165 -125
  13. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/stats/autocorrelation.py +62 -47
  14. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_demo.py +34 -16
  15. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1_with_covariates.py +81 -105
  16. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1_with_covariates_and_dataframe.py +96 -103
  17. spotforecast2-2.2.2/src/spotforecast2/models/forecaster_recursive_xgb_full.py +0 -74
  18. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/README.md +0 -0
  19. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/__init__.py +0 -0
  20. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/__init__.py +0 -0
  21. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/bayesian_search.py +0 -0
  22. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/model_selection/grid_search.py +0 -0
  23. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/models/__init__.py +0 -0
  24. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/__init__.py +0 -0
  25. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/base.py +0 -0
  26. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/clean.py +0 -0
  27. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/defaults.py +0 -0
  28. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/factories.py +0 -0
  29. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/lazy.py +0 -0
  30. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/multi.py +0 -0
  31. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/optuna.py +0 -0
  32. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/predict.py +0 -0
  33. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/runner.py +0 -0
  34. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/search_spaces.py +0 -0
  35. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/multitask/spotoptim.py +0 -0
  36. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/__init__.py +0 -0
  37. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/assets/__init__.py +0 -0
  38. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/assets/prediction_report.html.j2 +0 -0
  39. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/distribution.py +0 -0
  40. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/plots/spectral.py +0 -0
  41. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/py.typed +0 -0
  42. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/stats/__init__.py +0 -0
  43. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/__init__.py +0 -0
  44. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_entsoe.py +0 -0
  45. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1.py +0 -0
  46. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/tasks/task_n_to_1_dataframe.py +0 -0
  47. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/src/spotforecast2/warnings/__init__.py +0 -0
  48. {spotforecast2-2.2.2 → spotforecast2-2.2.3}/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.3
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.3"
4
4
  description = "Forecasting with spot"
5
5
  readme = "README.md"
6
6
  license = { text = "AGPL-3.0-or-later" }
@@ -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
@@ -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
- import matplotlib.pyplot as plt
83
- import numpy as np
71
+ from spotforecast2.model_selection.spotoptim_search import (
72
+ parse_lags_from_strings,
73
+ )
84
74
 
85
- def check_safety_threshold(val, threshold):
86
- return 1 if val >= threshold else 0
75
+ result_int = parse_lags_from_strings(24)
76
+ print(result_int)
77
+ assert result_int == 24
87
78
 
88
- threshold = 0.95
89
- x = np.linspace(0.8, 1.0, 50)
90
- y = [check_safety_threshold(val, threshold) for val in x]
79
+ result_list = parse_lags_from_strings("[1, 2, 3]")
80
+ print(result_list)
81
+ assert result_list == [1, 2, 3]
91
82
 
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
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
- :func:`~spotforecast2.model_selection.bayesian_search_forecaster`
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
- :class:`~spotoptim.hyperparameters.ParameterSet` or a plain
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
- return spotoptim_objective(
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=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
- optimizer.optimize()
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
- 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:
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
- 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:
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
@@ -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):