spotforecast2 0.0.4__tar.gz → 0.1.0__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-0.0.4 → spotforecast2-0.1.0}/PKG-INFO +6 -2
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/README.md +5 -1
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/pyproject.toml +1 -1
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/exceptions.py +3 -3
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/utils.py +46 -46
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/processing/n2n_predict_with_covariates.py +283 -26
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/utils/validation.py +15 -15
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/data/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/data/data.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/data/fetch_data.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/base.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/metrics.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/recursive/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/recursive/_forecaster_equivalent_date.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/recursive/_forecaster_recursive.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/recursive/_warnings.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/bayesian_search.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/grid_search.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/random_search.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/split_base.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/split_one_step.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/split_ts_cv.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/utils_common.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/utils_metrics.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/validation.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/_binner.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/_common.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/_differentiator.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/_rolling.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/curate_data.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/imputation.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/outlier.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/split.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/processing/agg_predict.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/processing/n2n_predict.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/py.typed +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/utils/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/utils/convert_to_utc.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/utils/data_transform.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/utils/forecaster_config.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/utils/generate_holiday.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/weather/__init__.py +0 -0
- {spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/weather/weather_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: spotforecast2
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Forecasting with spot
|
|
5
5
|
Author: bartzbeielstein
|
|
6
6
|
Author-email: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com>
|
|
@@ -35,7 +35,11 @@ Requires-Python: >=3.13
|
|
|
35
35
|
Provides-Extra: dev
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
<div align="left">
|
|
39
|
+
<img src="logo/spotlogo.png" alt="spotforecast2 Logo" width="300">
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
# spotforecast2
|
|
39
43
|
|
|
40
44
|
[](https://www.python.org/downloads/)
|
|
41
45
|
[](https://pypi.org/project/spotforecast2/)
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="left">
|
|
2
|
+
<img src="logo/spotlogo.png" alt="spotforecast2 Logo" width="300">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
# spotforecast2
|
|
2
6
|
|
|
3
7
|
[](https://www.python.org/downloads/)
|
|
4
8
|
[](https://pypi.org/project/spotforecast2/)
|
|
@@ -671,10 +671,10 @@ def set_skforecast_warnings(suppress_warnings: bool, action: str = "ignore") ->
|
|
|
671
671
|
Suppress spotforecast warnings.
|
|
672
672
|
|
|
673
673
|
Args:
|
|
674
|
-
suppress_warnings
|
|
674
|
+
suppress_warnings: bool
|
|
675
675
|
If True, spotforecast warnings will be suppressed.
|
|
676
|
-
action
|
|
677
|
-
|
|
676
|
+
action: str, default 'ignore'
|
|
677
|
+
Action to take regarding the warnings.
|
|
678
678
|
"""
|
|
679
679
|
if suppress_warnings:
|
|
680
680
|
for category in warn_skforecast_categories:
|
|
@@ -42,16 +42,16 @@ def exog_to_direct(
|
|
|
42
42
|
forecasting.
|
|
43
43
|
|
|
44
44
|
Args:
|
|
45
|
-
exog
|
|
45
|
+
exog: pandas Series, pandas DataFrame
|
|
46
46
|
Exogenous variables.
|
|
47
|
-
steps
|
|
47
|
+
steps: int
|
|
48
48
|
Number of steps that will be predicted using exog.
|
|
49
49
|
|
|
50
50
|
Returns:
|
|
51
51
|
tuple[pd.DataFrame, list[str]]:
|
|
52
|
-
exog_direct
|
|
52
|
+
exog_direct: pandas DataFrame
|
|
53
53
|
Exogenous variables transformed.
|
|
54
|
-
exog_direct_names
|
|
54
|
+
exog_direct_names: list
|
|
55
55
|
Names of the columns of the exogenous variables transformed. Only
|
|
56
56
|
created if `exog` is a pandas Series or DataFrame.
|
|
57
57
|
"""
|
|
@@ -90,17 +90,17 @@ def exog_to_direct_numpy(
|
|
|
90
90
|
forecasting.
|
|
91
91
|
|
|
92
92
|
Args:
|
|
93
|
-
exog
|
|
93
|
+
exog: numpy ndarray, pandas Series, pandas DataFrame
|
|
94
94
|
Exogenous variables, shape(samples,). If exog is a pandas format, the
|
|
95
95
|
direct exog names are created.
|
|
96
|
-
steps
|
|
96
|
+
steps: int
|
|
97
97
|
Number of steps that will be predicted using exog.
|
|
98
98
|
|
|
99
99
|
Returns:
|
|
100
100
|
tuple[np.ndarray, list[str] | None]:
|
|
101
|
-
exog_direct
|
|
101
|
+
exog_direct: numpy ndarray
|
|
102
102
|
Exogenous variables transformed.
|
|
103
|
-
exog_direct_names
|
|
103
|
+
exog_direct_names: list, None
|
|
104
104
|
Names of the columns of the exogenous variables transformed. Only
|
|
105
105
|
created if `exog` is a pandas Series or DataFrame.
|
|
106
106
|
"""
|
|
@@ -136,18 +136,18 @@ def prepare_steps_direct(
|
|
|
136
136
|
Prepare list of steps to be predicted in Direct Forecasters.
|
|
137
137
|
|
|
138
138
|
Args:
|
|
139
|
-
max_step
|
|
139
|
+
max_step: int, list, numpy ndarray
|
|
140
140
|
Maximum number of future steps the forecaster will predict
|
|
141
141
|
when using predict methods.
|
|
142
|
-
steps
|
|
142
|
+
steps: int, list, None, default None
|
|
143
143
|
Predict n steps. The value of `steps` must be less than or equal to the
|
|
144
|
-
|
|
144
|
+
value of steps defined when initializing the forecaster. Starts at 1.
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
- If `int`: Only steps within the range of 1 to int are predicted.
|
|
147
|
+
- If `list`: List of ints. Only the steps contained in the list
|
|
148
|
+
are predicted.
|
|
149
|
+
- If `None`: As many steps are predicted as were defined at
|
|
150
|
+
initialization.
|
|
151
151
|
|
|
152
152
|
Returns:
|
|
153
153
|
list[int]:
|
|
@@ -188,14 +188,14 @@ def transform_numpy(
|
|
|
188
188
|
have inverse_transform method.
|
|
189
189
|
|
|
190
190
|
Args:
|
|
191
|
-
array
|
|
191
|
+
array: numpy ndarray
|
|
192
192
|
Array to be transformed.
|
|
193
|
-
transformer
|
|
193
|
+
transformer: scikit-learn alike transformer, preprocessor, or ColumnTransformer.
|
|
194
194
|
Scikit-learn alike transformer (preprocessor) with methods: fit, transform,
|
|
195
195
|
fit_transform and inverse_transform.
|
|
196
|
-
fit
|
|
196
|
+
fit: bool, default False
|
|
197
197
|
Train the transformer before applying it.
|
|
198
|
-
inverse_transform
|
|
198
|
+
inverse_transform: bool, default False
|
|
199
199
|
Transform back the data to the original representation. This is not available
|
|
200
200
|
when using transformers of class scikit-learn ColumnTransformers.
|
|
201
201
|
|
|
@@ -565,25 +565,25 @@ def check_residuals_input(
|
|
|
565
565
|
Check residuals input arguments in Forecasters.
|
|
566
566
|
|
|
567
567
|
Args:
|
|
568
|
-
forecaster_name
|
|
568
|
+
forecaster_name: str
|
|
569
569
|
Forecaster name.
|
|
570
|
-
use_in_sample_residuals
|
|
570
|
+
use_in_sample_residuals: bool
|
|
571
571
|
Indicates if in sample or out sample residuals are used.
|
|
572
|
-
in_sample_residuals_
|
|
572
|
+
in_sample_residuals_: numpy ndarray, dict
|
|
573
573
|
Residuals of the model when predicting training data.
|
|
574
|
-
out_sample_residuals_
|
|
574
|
+
out_sample_residuals_: numpy ndarray, dict
|
|
575
575
|
Residuals of the model when predicting non training data.
|
|
576
|
-
use_binned_residuals
|
|
576
|
+
use_binned_residuals: bool
|
|
577
577
|
Indicates if residuals are binned.
|
|
578
|
-
in_sample_residuals_by_bin_
|
|
578
|
+
in_sample_residuals_by_bin_: dict
|
|
579
579
|
In sample residuals binned according to the predicted value each residual
|
|
580
580
|
is associated with.
|
|
581
|
-
out_sample_residuals_by_bin_
|
|
581
|
+
out_sample_residuals_by_bin_: dict
|
|
582
582
|
Out of sample residuals binned according to the predicted value each residual
|
|
583
583
|
is associated with.
|
|
584
|
-
levels
|
|
584
|
+
levels: list, default None
|
|
585
585
|
Names of the series (levels) to be predicted (Forecasters multiseries).
|
|
586
|
-
encoding
|
|
586
|
+
encoding: str, default None
|
|
587
587
|
Encoding used to identify the different series (ForecasterRecursiveMultiSeries).
|
|
588
588
|
|
|
589
589
|
Returns:
|
|
@@ -673,23 +673,23 @@ def date_to_index_position(
|
|
|
673
673
|
represents the position of the datetime in the index.
|
|
674
674
|
|
|
675
675
|
Args:
|
|
676
|
-
index
|
|
676
|
+
index: pandas Index
|
|
677
677
|
Original datetime index (must be a pandas DatetimeIndex if `date_input`
|
|
678
678
|
is not an int).
|
|
679
|
-
|
|
680
|
-
|
|
679
|
+
date_input: int, str, pandas Timestamp
|
|
680
|
+
Datetime to transform to integer.
|
|
681
681
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
682
|
+
- If int, returns the same integer.
|
|
683
|
+
- If str or pandas Timestamp, it is converted and expanded into the index.
|
|
684
|
+
method: str, default 'prediction'
|
|
685
|
+
Can be 'prediction' or 'validation'.
|
|
686
686
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
687
|
+
- If 'prediction', the date must be later than the last date in the index.
|
|
688
|
+
- If 'validation', the date must be within the index range.
|
|
689
|
+
date_literal: str, default 'steps'
|
|
690
|
+
Variable name used in error messages.
|
|
691
|
+
kwargs_pd_to_datetime: dict, default {}
|
|
692
|
+
Additional keyword arguments to pass to `pd.to_datetime()`.
|
|
693
693
|
|
|
694
694
|
Returns:
|
|
695
695
|
int:
|
|
@@ -758,11 +758,11 @@ def initialize_estimator(
|
|
|
758
758
|
Returns the valid estimator object.
|
|
759
759
|
|
|
760
760
|
Args:
|
|
761
|
-
estimator
|
|
761
|
+
estimator: estimator or pipeline compatible with the scikit-learn API, default None
|
|
762
762
|
An instance of a estimator or pipeline compatible with the scikit-learn API.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
763
|
+
regressor: estimator or pipeline compatible with the scikit-learn API, default None
|
|
764
|
+
Deprecated. An instance of a estimator or pipeline compatible with the
|
|
765
|
+
scikit-learn API.
|
|
766
766
|
|
|
767
767
|
Returns:
|
|
768
768
|
estimator or pipeline compatible with the scikit-learn API
|
|
@@ -6,6 +6,9 @@ recursive forecasters with exogenous variables (weather, holidays, calendar feat
|
|
|
6
6
|
It handles data preparation, feature engineering, model training, and prediction
|
|
7
7
|
in a single integrated function.
|
|
8
8
|
|
|
9
|
+
Model persistence follows scikit-learn conventions using joblib for efficient
|
|
10
|
+
serialization and deserialization of trained forecasters.
|
|
11
|
+
|
|
9
12
|
Examples:
|
|
10
13
|
Basic usage with default parameters:
|
|
11
14
|
|
|
@@ -27,8 +30,28 @@ Examples:
|
|
|
27
30
|
... train_ratio=0.75,
|
|
28
31
|
... verbose=True
|
|
29
32
|
... )
|
|
33
|
+
|
|
34
|
+
Using cached models:
|
|
35
|
+
|
|
36
|
+
>>> # Load existing models if available, or train new ones
|
|
37
|
+
>>> predictions, metadata, forecasters = n2n_predict_with_covariates(
|
|
38
|
+
... forecast_horizon=24,
|
|
39
|
+
... force_train=False,
|
|
40
|
+
... model_dir="./models",
|
|
41
|
+
... verbose=True
|
|
42
|
+
... )
|
|
43
|
+
|
|
44
|
+
Force retraining and update cache:
|
|
45
|
+
|
|
46
|
+
>>> predictions, metadata, forecasters = n2n_predict_with_covariates(
|
|
47
|
+
... forecast_horizon=24,
|
|
48
|
+
... force_train=True,
|
|
49
|
+
... model_dir="./models",
|
|
50
|
+
... verbose=True
|
|
51
|
+
... )
|
|
30
52
|
"""
|
|
31
53
|
|
|
54
|
+
from pathlib import Path
|
|
32
55
|
from typing import Dict, List, Optional, Tuple, Union
|
|
33
56
|
|
|
34
57
|
import numpy as np
|
|
@@ -37,6 +60,11 @@ from astral import LocationInfo
|
|
|
37
60
|
from lightgbm import LGBMRegressor
|
|
38
61
|
from sklearn.preprocessing import PolynomialFeatures
|
|
39
62
|
|
|
63
|
+
try:
|
|
64
|
+
from joblib import dump, load
|
|
65
|
+
except ImportError:
|
|
66
|
+
raise ImportError("joblib is required. Install with: pip install joblib")
|
|
67
|
+
|
|
40
68
|
try:
|
|
41
69
|
from tqdm.auto import tqdm
|
|
42
70
|
except ImportError: # pragma: no cover - fallback when tqdm is not installed
|
|
@@ -547,6 +575,152 @@ def _merge_data_and_covariates(
|
|
|
547
575
|
return data_with_exog, exo_tmp, exo_pred
|
|
548
576
|
|
|
549
577
|
|
|
578
|
+
# ============================================================================
|
|
579
|
+
# Model Persistence Functions
|
|
580
|
+
# ============================================================================
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _ensure_model_dir(model_dir: Union[str, Path]) -> Path:
|
|
584
|
+
"""Ensure model directory exists.
|
|
585
|
+
|
|
586
|
+
Args:
|
|
587
|
+
model_dir: Directory path for model storage.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
Path: Validated Path object.
|
|
591
|
+
|
|
592
|
+
Raises:
|
|
593
|
+
OSError: If directory cannot be created.
|
|
594
|
+
"""
|
|
595
|
+
model_path = Path(model_dir)
|
|
596
|
+
model_path.mkdir(parents=True, exist_ok=True)
|
|
597
|
+
return model_path
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _get_model_filepath(model_dir: Path, target: str) -> Path:
|
|
601
|
+
"""Get filepath for a single model.
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
model_dir: Directory containing models.
|
|
605
|
+
target: Target variable name.
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
Path: Full filepath for the model.
|
|
609
|
+
|
|
610
|
+
Examples:
|
|
611
|
+
>>> path = _get_model_filepath(Path("./models"), "power")
|
|
612
|
+
>>> str(path)
|
|
613
|
+
'./models/forecaster_power.joblib'
|
|
614
|
+
"""
|
|
615
|
+
return model_dir / f"forecaster_{target}.joblib"
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _save_forecasters(
|
|
619
|
+
forecasters: Dict[str, object],
|
|
620
|
+
model_dir: Union[str, Path],
|
|
621
|
+
verbose: bool = False,
|
|
622
|
+
) -> Dict[str, Path]:
|
|
623
|
+
"""Save trained forecasters to disk using joblib.
|
|
624
|
+
|
|
625
|
+
Follows scikit-learn persistence conventions using joblib for efficient
|
|
626
|
+
serialization of sklearn-compatible estimators.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
forecasters: Dictionary mapping target names to trained ForecasterRecursive objects.
|
|
630
|
+
model_dir: Directory to save models. Created if it doesn't exist.
|
|
631
|
+
verbose: Print progress messages. Default: False.
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
Dict[str, Path]: Dictionary mapping target names to saved model filepaths.
|
|
635
|
+
|
|
636
|
+
Raises:
|
|
637
|
+
OSError: If models cannot be written to disk.
|
|
638
|
+
TypeError: If forecasters contain non-serializable objects.
|
|
639
|
+
|
|
640
|
+
Examples:
|
|
641
|
+
>>> forecasters = {"power": forecaster_obj}
|
|
642
|
+
>>> paths = _save_forecasters(forecasters, "./models", verbose=True)
|
|
643
|
+
>>> print(paths["power"])
|
|
644
|
+
models/forecaster_power.joblib
|
|
645
|
+
"""
|
|
646
|
+
model_path = _ensure_model_dir(model_dir)
|
|
647
|
+
saved_paths = {}
|
|
648
|
+
|
|
649
|
+
for target, forecaster in forecasters.items():
|
|
650
|
+
filepath = _get_model_filepath(model_path, target)
|
|
651
|
+
try:
|
|
652
|
+
dump(forecaster, filepath, compress=3)
|
|
653
|
+
saved_paths[target] = filepath
|
|
654
|
+
if verbose:
|
|
655
|
+
print(f" ✓ Saved forecaster for {target} to {filepath}")
|
|
656
|
+
except Exception as e:
|
|
657
|
+
raise OSError(f"Failed to save model for {target}: {e}")
|
|
658
|
+
|
|
659
|
+
return saved_paths
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _load_forecasters(
|
|
663
|
+
target_columns: List[str],
|
|
664
|
+
model_dir: Union[str, Path],
|
|
665
|
+
verbose: bool = False,
|
|
666
|
+
) -> Tuple[Dict[str, object], List[str]]:
|
|
667
|
+
"""Load trained forecasters from disk using joblib.
|
|
668
|
+
|
|
669
|
+
Attempts to load all forecasters for given targets. Missing models
|
|
670
|
+
are indicated in the return value for selective retraining.
|
|
671
|
+
|
|
672
|
+
Args:
|
|
673
|
+
target_columns: List of target variable names to load.
|
|
674
|
+
model_dir: Directory containing saved models.
|
|
675
|
+
verbose: Print progress messages. Default: False.
|
|
676
|
+
|
|
677
|
+
Returns:
|
|
678
|
+
Tuple[Dict[str, object], List[str]]:
|
|
679
|
+
- forecasters: Dictionary of successfully loaded ForecasterRecursive objects.
|
|
680
|
+
- missing_targets: List of target names without saved models.
|
|
681
|
+
|
|
682
|
+
Examples:
|
|
683
|
+
>>> forecasters, missing = _load_forecasters(
|
|
684
|
+
... ["power", "energy"],
|
|
685
|
+
... "./models",
|
|
686
|
+
... verbose=True
|
|
687
|
+
... )
|
|
688
|
+
>>> print(missing)
|
|
689
|
+
['energy']
|
|
690
|
+
"""
|
|
691
|
+
model_path = Path(model_dir)
|
|
692
|
+
forecasters = {}
|
|
693
|
+
missing_targets = []
|
|
694
|
+
|
|
695
|
+
for target in target_columns:
|
|
696
|
+
filepath = _get_model_filepath(model_path, target)
|
|
697
|
+
if filepath.exists():
|
|
698
|
+
try:
|
|
699
|
+
forecasters[target] = load(filepath)
|
|
700
|
+
if verbose:
|
|
701
|
+
print(f" ✓ Loaded forecaster for {target} from {filepath}")
|
|
702
|
+
except Exception as e:
|
|
703
|
+
if verbose:
|
|
704
|
+
print(f" ✗ Failed to load {target}: {e}")
|
|
705
|
+
missing_targets.append(target)
|
|
706
|
+
else:
|
|
707
|
+
missing_targets.append(target)
|
|
708
|
+
|
|
709
|
+
return forecasters, missing_targets
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _model_directory_exists(model_dir: Union[str, Path]) -> bool:
|
|
713
|
+
"""Check if model directory exists.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
model_dir: Directory path to check.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
bool: True if directory exists, False otherwise.
|
|
720
|
+
"""
|
|
721
|
+
return Path(model_dir).exists()
|
|
722
|
+
|
|
723
|
+
|
|
550
724
|
# ============================================================================
|
|
551
725
|
# Main Function
|
|
552
726
|
# ============================================================================
|
|
@@ -567,6 +741,8 @@ def n2n_predict_with_covariates(
|
|
|
567
741
|
include_weather_windows: bool = False,
|
|
568
742
|
include_holiday_features: bool = False,
|
|
569
743
|
include_poly_features: bool = False,
|
|
744
|
+
force_train: bool = False,
|
|
745
|
+
model_dir: Union[str, Path] = "./forecaster_models",
|
|
570
746
|
verbose: bool = True,
|
|
571
747
|
show_progress: bool = True,
|
|
572
748
|
) -> Tuple[pd.DataFrame, Dict, Dict]:
|
|
@@ -580,9 +756,12 @@ def n2n_predict_with_covariates(
|
|
|
580
756
|
5. Performs feature engineering (cyclical encoding, interactions)
|
|
581
757
|
6. Merges target and exogenous data
|
|
582
758
|
7. Splits into train/validation/test sets
|
|
583
|
-
8. Trains recursive forecasters with sample weighting
|
|
759
|
+
8. Trains or loads recursive forecasters with sample weighting
|
|
584
760
|
9. Generates multi-step ahead predictions
|
|
585
761
|
|
|
762
|
+
Models are persisted to disk following scikit-learn conventions using joblib.
|
|
763
|
+
Existing models are reused for prediction unless force_train=True.
|
|
764
|
+
|
|
586
765
|
Args:
|
|
587
766
|
forecast_horizon: Number of time steps to forecast ahead. Default: 24.
|
|
588
767
|
contamination: Contamination parameter for outlier detection. Default: 0.01.
|
|
@@ -599,6 +778,10 @@ def n2n_predict_with_covariates(
|
|
|
599
778
|
include_weather_windows: Include weather window features. Default: False.
|
|
600
779
|
include_holiday_features: Include holiday features. Default: False.
|
|
601
780
|
include_poly_features: Include polynomial interaction features. Default: False.
|
|
781
|
+
force_train: Force retraining of all models, ignoring cached models.
|
|
782
|
+
Default: False.
|
|
783
|
+
model_dir: Directory for saving/loading trained models.
|
|
784
|
+
Default: "./forecaster_models".
|
|
602
785
|
verbose: Print progress messages. Default: True.
|
|
603
786
|
show_progress: Show progress bar during training. Default: True.
|
|
604
787
|
|
|
@@ -611,9 +794,10 @@ def n2n_predict_with_covariates(
|
|
|
611
794
|
Raises:
|
|
612
795
|
ValueError: If data validation fails or required data cannot be retrieved.
|
|
613
796
|
ImportError: If required dependencies are not installed.
|
|
797
|
+
OSError: If models cannot be saved to disk.
|
|
614
798
|
|
|
615
799
|
Examples:
|
|
616
|
-
Basic usage:
|
|
800
|
+
Basic usage with automatic model caching:
|
|
617
801
|
|
|
618
802
|
>>> predictions, metadata, forecasters = n2n_predict_with_covariates(
|
|
619
803
|
... forecast_horizon=24,
|
|
@@ -622,6 +806,22 @@ def n2n_predict_with_covariates(
|
|
|
622
806
|
>>> print(predictions.shape)
|
|
623
807
|
(24, 11)
|
|
624
808
|
|
|
809
|
+
Load cached models (if available):
|
|
810
|
+
|
|
811
|
+
>>> predictions, metadata, forecasters = n2n_predict_with_covariates(
|
|
812
|
+
... forecast_horizon=24,
|
|
813
|
+
... force_train=False,
|
|
814
|
+
... model_dir="./saved_models"
|
|
815
|
+
... )
|
|
816
|
+
|
|
817
|
+
Force retraining and update cache:
|
|
818
|
+
|
|
819
|
+
>>> predictions, metadata, forecasters = n2n_predict_with_covariates(
|
|
820
|
+
... forecast_horizon=24,
|
|
821
|
+
... force_train=True,
|
|
822
|
+
... model_dir="./saved_models"
|
|
823
|
+
... )
|
|
824
|
+
|
|
625
825
|
Custom location and features:
|
|
626
826
|
|
|
627
827
|
>>> predictions, metadata, forecasters = n2n_predict_with_covariates(
|
|
@@ -630,6 +830,7 @@ def n2n_predict_with_covariates(
|
|
|
630
830
|
... longitude=13.4050,
|
|
631
831
|
... lags=48,
|
|
632
832
|
... include_poly_features=True,
|
|
833
|
+
... force_train=False,
|
|
633
834
|
... verbose=True
|
|
634
835
|
... )
|
|
635
836
|
|
|
@@ -641,6 +842,16 @@ def n2n_predict_with_covariates(
|
|
|
641
842
|
near missing data.
|
|
642
843
|
- Train/validation splits are temporal (80/20 by default).
|
|
643
844
|
- All features are cast to float32 for memory efficiency.
|
|
845
|
+
- Trained models are saved to disk using joblib for fast reuse.
|
|
846
|
+
- When force_train=False, existing models are loaded and prediction
|
|
847
|
+
proceeds without retraining. This significantly speeds up prediction
|
|
848
|
+
for repeated calls with the same configuration.
|
|
849
|
+
- The model_dir directory is created automatically if it doesn't exist.
|
|
850
|
+
|
|
851
|
+
Performance Notes:
|
|
852
|
+
- First run: Full training (~5-10 minutes depending on data size)
|
|
853
|
+
- Subsequent runs (force_train=False): Model loading only (~1-2 seconds)
|
|
854
|
+
- Force retrain (force_train=True): Full training again (~5-10 minutes)
|
|
644
855
|
"""
|
|
645
856
|
if verbose:
|
|
646
857
|
print("=" * 80)
|
|
@@ -845,11 +1056,13 @@ def n2n_predict_with_covariates(
|
|
|
845
1056
|
)
|
|
846
1057
|
|
|
847
1058
|
# ========================================================================
|
|
848
|
-
# 9. MODEL TRAINING
|
|
1059
|
+
# 9. MODEL TRAINING OR LOADING
|
|
849
1060
|
# ========================================================================
|
|
850
1061
|
|
|
851
1062
|
if verbose:
|
|
852
|
-
print(
|
|
1063
|
+
print(
|
|
1064
|
+
"\n[8/9] Loading or training recursive forecasters with exogenous variables..."
|
|
1065
|
+
)
|
|
853
1066
|
|
|
854
1067
|
if estimator is None:
|
|
855
1068
|
estimator = LGBMRegressor(random_state=1234, verbose=-1)
|
|
@@ -857,35 +1070,79 @@ def n2n_predict_with_covariates(
|
|
|
857
1070
|
window_features = RollingFeatures(stats=["mean"], window_sizes=window_size)
|
|
858
1071
|
end_validation = pd.concat([data_train, data_val]).index[-1]
|
|
859
1072
|
|
|
1073
|
+
# Attempt to load cached models if force_train=False
|
|
860
1074
|
recursive_forecasters = {}
|
|
1075
|
+
targets_to_train = target_columns
|
|
861
1076
|
|
|
862
|
-
|
|
863
|
-
if show_progress and tqdm is not None:
|
|
864
|
-
target_iter = tqdm(target_columns, desc="Training forecasters", unit="model")
|
|
865
|
-
|
|
866
|
-
for target in target_iter:
|
|
1077
|
+
if not force_train and _model_directory_exists(model_dir):
|
|
867
1078
|
if verbose:
|
|
868
|
-
print(
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
window_features=window_features,
|
|
874
|
-
weight_func=weight_func,
|
|
1079
|
+
print(" Attempting to load cached models...")
|
|
1080
|
+
cached_forecasters, missing_targets = _load_forecasters(
|
|
1081
|
+
target_columns=target_columns,
|
|
1082
|
+
model_dir=model_dir,
|
|
1083
|
+
verbose=verbose,
|
|
875
1084
|
)
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1085
|
+
recursive_forecasters.update(cached_forecasters)
|
|
1086
|
+
targets_to_train = missing_targets
|
|
1087
|
+
|
|
1088
|
+
if len(cached_forecasters) == len(target_columns):
|
|
1089
|
+
if verbose:
|
|
1090
|
+
print(f" ✓ All {len(target_columns)} forecasters loaded from cache")
|
|
1091
|
+
elif len(cached_forecasters) > 0:
|
|
1092
|
+
if verbose:
|
|
1093
|
+
print(
|
|
1094
|
+
f" ✓ Loaded {len(cached_forecasters)} forecasters, "
|
|
1095
|
+
f"will train {len(targets_to_train)} new ones"
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
# Train missing or forced models
|
|
1099
|
+
if len(targets_to_train) > 0:
|
|
1100
|
+
if force_train and len(recursive_forecasters) > 0:
|
|
1101
|
+
if verbose:
|
|
1102
|
+
print(f" Force retraining all {len(target_columns)} forecasters...")
|
|
1103
|
+
targets_to_train = target_columns
|
|
1104
|
+
recursive_forecasters.clear()
|
|
1105
|
+
|
|
1106
|
+
target_iter = targets_to_train
|
|
1107
|
+
if show_progress and tqdm is not None:
|
|
1108
|
+
target_iter = tqdm(
|
|
1109
|
+
targets_to_train,
|
|
1110
|
+
desc="Training forecasters",
|
|
1111
|
+
unit="model",
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
for target in target_iter:
|
|
1115
|
+
if verbose:
|
|
1116
|
+
print(f" Training forecaster for {target}...")
|
|
1117
|
+
|
|
1118
|
+
forecaster = ForecasterRecursive(
|
|
1119
|
+
estimator=estimator,
|
|
1120
|
+
lags=lags,
|
|
1121
|
+
window_features=window_features,
|
|
1122
|
+
weight_func=weight_func,
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
forecaster.fit(
|
|
1126
|
+
y=data_with_exog[target].loc[:end_validation].squeeze(),
|
|
1127
|
+
exog=data_with_exog[exog_features].loc[:end_validation],
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
recursive_forecasters[target] = forecaster
|
|
1131
|
+
|
|
1132
|
+
if verbose:
|
|
1133
|
+
print(f" ✓ Forecaster trained for {target}")
|
|
1134
|
+
|
|
1135
|
+
# Save newly trained models to disk
|
|
884
1136
|
if verbose:
|
|
885
|
-
print(f"
|
|
1137
|
+
print(f" Saving {len(targets_to_train)} trained forecasters to disk...")
|
|
1138
|
+
_save_forecasters(
|
|
1139
|
+
forecasters={t: recursive_forecasters[t] for t in targets_to_train},
|
|
1140
|
+
model_dir=model_dir,
|
|
1141
|
+
verbose=verbose,
|
|
1142
|
+
)
|
|
886
1143
|
|
|
887
1144
|
if verbose:
|
|
888
|
-
print(f" ✓ Total forecasters
|
|
1145
|
+
print(f" ✓ Total forecasters available: {len(recursive_forecasters)}")
|
|
889
1146
|
|
|
890
1147
|
# ========================================================================
|
|
891
1148
|
# 10. PREDICTION
|
|
@@ -407,50 +407,50 @@ def check_predict_input(
|
|
|
407
407
|
trained.
|
|
408
408
|
|
|
409
409
|
Args:
|
|
410
|
-
forecaster_name
|
|
410
|
+
forecaster_name: str
|
|
411
411
|
Forecaster name.
|
|
412
|
-
steps
|
|
412
|
+
steps: int, list
|
|
413
413
|
Number of future steps predicted.
|
|
414
414
|
is_fitted: bool
|
|
415
415
|
Tag to identify if the estimator has been fitted (trained).
|
|
416
|
-
exog_in_
|
|
416
|
+
exog_in_: bool
|
|
417
417
|
If the forecaster has been trained using exogenous variable/s.
|
|
418
|
-
index_type_
|
|
418
|
+
index_type_: type
|
|
419
419
|
Type of index of the input used in training.
|
|
420
|
-
index_freq_
|
|
420
|
+
index_freq_: str
|
|
421
421
|
Frequency of Index of the input used in training.
|
|
422
422
|
window_size: int
|
|
423
423
|
Size of the window needed to create the predictors. It is equal to
|
|
424
424
|
`max_lag`.
|
|
425
|
-
last_window
|
|
425
|
+
last_window: pandas Series, pandas DataFrame, None
|
|
426
426
|
Values of the series used to create the predictors (lags) need in the
|
|
427
427
|
first iteration of prediction (t + 1).
|
|
428
|
-
last_window_exog
|
|
428
|
+
last_window_exog: pandas Series, pandas DataFrame, default None
|
|
429
429
|
Values of the exogenous variables aligned with `last_window` in
|
|
430
430
|
ForecasterStats predictions.
|
|
431
|
-
exog
|
|
431
|
+
exog: pandas Series, pandas DataFrame, dict, default None
|
|
432
432
|
Exogenous variable/s included as predictor/s.
|
|
433
|
-
exog_names_in_
|
|
433
|
+
exog_names_in_: list, default None
|
|
434
434
|
Names of the exogenous variables used during training.
|
|
435
|
-
interval
|
|
435
|
+
interval: list, tuple, default None
|
|
436
436
|
Confidence of the prediction interval estimated. Sequence of percentiles
|
|
437
437
|
to compute, which must be between 0 and 100 inclusive. For example,
|
|
438
438
|
interval of 95% should be as `interval = [2.5, 97.5]`.
|
|
439
|
-
alpha
|
|
439
|
+
alpha: float, default None
|
|
440
440
|
The confidence intervals used in ForecasterStats are (1 - alpha) %.
|
|
441
441
|
max_step: int, default None
|
|
442
442
|
Maximum number of steps allowed (`ForecasterDirect` and
|
|
443
443
|
`ForecasterDirectMultiVariate`).
|
|
444
|
-
levels
|
|
444
|
+
levels: str, list, default None
|
|
445
445
|
Time series to be predicted (`ForecasterRecursiveMultiSeries`
|
|
446
446
|
and `ForecasterRnn).
|
|
447
|
-
levels_forecaster
|
|
447
|
+
levels_forecaster: str, list, default None
|
|
448
448
|
Time series used as output data of a multiseries problem in a RNN problem
|
|
449
449
|
(`ForecasterRnn`).
|
|
450
|
-
series_names_in_
|
|
450
|
+
series_names_in_: list, default None
|
|
451
451
|
Names of the columns used during fit (`ForecasterRecursiveMultiSeries`,
|
|
452
452
|
`ForecasterDirectMultiVariate` and `ForecasterRnn`).
|
|
453
|
-
encoding
|
|
453
|
+
encoding: str, default None
|
|
454
454
|
Encoding used to identify the different series (`ForecasterRecursiveMultiSeries`).
|
|
455
455
|
|
|
456
456
|
Returns:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/recursive/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/forecaster/recursive/_warnings.py
RENAMED
|
File without changes
|
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/bayesian_search.py
RENAMED
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/grid_search.py
RENAMED
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/random_search.py
RENAMED
|
File without changes
|
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/split_one_step.py
RENAMED
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/split_ts_cv.py
RENAMED
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/utils_common.py
RENAMED
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/model_selection/utils_metrics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{spotforecast2-0.0.4 → spotforecast2-0.1.0}/src/spotforecast2/preprocessing/_differentiator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|