scentree 0.0.1__py3-none-any.whl
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.
- scentree/__init__.py +0 -0
- scentree/config/__init__.py +1 -0
- scentree/dim_reduction/__init__.py +0 -0
- scentree/dim_reduction/pca.py +86 -0
- scentree/estimators/__init__.py +0 -0
- scentree/estimators/estimator_orchestrator.py +245 -0
- scentree/estimators/ridge.py +18 -0
- scentree/estimators/scikit_base.py +255 -0
- scentree/estimators/utils.py +50 -0
- scentree/estimators/var.py +264 -0
- scentree/fan_generator/__init__.py +3 -0
- scentree/fan_generator/stage_manager.py +294 -0
- scentree/io/__init__.py +19 -0
- scentree/io/loader.py +267 -0
- scentree/io/writer.py +108 -0
- scentree/metrics/__init__.py +0 -0
- scentree/metrics/rmse.py +19 -0
- scentree/tree_construction/__init__.py +58 -0
- scentree/tree_construction/ftc.py +918 -0
- scentree-0.0.1.dist-info/METADATA +363 -0
- scentree-0.0.1.dist-info/RECORD +24 -0
- scentree-0.0.1.dist-info/WHEEL +5 -0
- scentree-0.0.1.dist-info/licenses/LICENSE +287 -0
- scentree-0.0.1.dist-info/top_level.txt +1 -0
scentree/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
explained_var = 0.8
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.typing import NDArray
|
|
3
|
+
from pydantic import BaseModel, PrivateAttr
|
|
4
|
+
from sklearn.decomposition import PCA
|
|
5
|
+
from typing import cast, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
R = TypeVar("R", bound="BasePCA")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BasePCA(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
PCA wrapper around scikit-learn's PCA implementation.
|
|
13
|
+
|
|
14
|
+
This class encapsulates a scikit-learn PCA reducer and manages
|
|
15
|
+
it as an internal (non-validated, non-serialized) attribute.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_reducer: Optional[PCA] = PrivateAttr(default=None)
|
|
19
|
+
|
|
20
|
+
def transform(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
21
|
+
"""
|
|
22
|
+
Apply dimensionality reduction to a given matrix.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
X (NDArray[np.float64]): Data to be reduced.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
RuntimeError: If the method has not been fitted previously
|
|
29
|
+
(i.e., `self._reducer` is None).
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
NDArray[np.float64]: Data in the low-dimensional space.
|
|
33
|
+
"""
|
|
34
|
+
if self._reducer is None:
|
|
35
|
+
raise RuntimeError("Model has not been fitted yet.")
|
|
36
|
+
return cast(NDArray[np.float64], self._reducer.transform(X))
|
|
37
|
+
|
|
38
|
+
def inverse_transform(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
39
|
+
"""
|
|
40
|
+
Apply inverse transformation to obtain data in the high-dimensional space.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
X (NDArray[np.float64]): Data in the low-dimensional space.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
RuntimeError: If the method has not been fitted previously
|
|
47
|
+
(i.e., `self._reducer` is None).
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
NDArray[np.float64]: Data in the low-dimensional space.
|
|
51
|
+
"""
|
|
52
|
+
if self._reducer is None:
|
|
53
|
+
raise RuntimeError("Model has not been fitted yet.")
|
|
54
|
+
X_rec = cast(NDArray[np.float64], self._reducer.inverse_transform(X))
|
|
55
|
+
return X_rec
|
|
56
|
+
|
|
57
|
+
def fit_auto_components(
|
|
58
|
+
self,
|
|
59
|
+
X: NDArray[np.float64],
|
|
60
|
+
threshold: float,
|
|
61
|
+
) -> NDArray[np.float64]:
|
|
62
|
+
"""
|
|
63
|
+
Fit a PCA reducer automatically selecting the number of components
|
|
64
|
+
based on the explained variance threshold and transform the input data.
|
|
65
|
+
|
|
66
|
+
This method first fits a full PCA to compute the cumulative explained
|
|
67
|
+
variance. It then selects the smallest number of components such that
|
|
68
|
+
the cumulative variance exceeds the specified threshold. Finally, it
|
|
69
|
+
fits a PCA reducer with this number of components and transforms the data.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
X (NDArray[np.float64]): Input feature matrix of shape (n_samples, n_features).
|
|
73
|
+
threshold (float): Desired cumulative explained variance ratio (between 0 and 1)
|
|
74
|
+
used to select the number of principal components.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
NDArray[np.float64]: Transformed input matrix with reduced dimensions based on
|
|
78
|
+
the automatically selected number of components.
|
|
79
|
+
"""
|
|
80
|
+
pca_full = PCA()
|
|
81
|
+
pca_full.fit(X)
|
|
82
|
+
cumulative_variance = np.cumsum(pca_full.explained_variance_ratio_)
|
|
83
|
+
n_components_th = np.argmax(cumulative_variance >= threshold) + 1
|
|
84
|
+
self._reducer = PCA(n_components=n_components_th)
|
|
85
|
+
X_reduced = cast(NDArray[np.float64], self._reducer.fit_transform(X))
|
|
86
|
+
return X_reduced
|
|
File without changes
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.typing import NDArray
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from scentree.estimators.ridge import RidgeEstimator
|
|
5
|
+
from scentree.estimators.var import VarEstimator
|
|
6
|
+
from sklearn.model_selection import BaseCrossValidator
|
|
7
|
+
from tqdm.auto import tqdm
|
|
8
|
+
from typing import Any, cast, ClassVar, List, Optional, Protocol, Self, Type, TypeVar, Union
|
|
9
|
+
|
|
10
|
+
R = TypeVar("R", bound="EstimatorController")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EstimatorProtocol(Protocol):
|
|
14
|
+
"""
|
|
15
|
+
Protocol defining the minimal interface required for an estimator.
|
|
16
|
+
|
|
17
|
+
This protocol standardizes the expected behavior for models that support
|
|
18
|
+
prediction, residual estimation, cross-validation fitting, in-sample and
|
|
19
|
+
out-of-sample estimation, and scoring.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def predict(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
23
|
+
"""Generate predictions.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
X (NDArray[np.float64]): Input feature matrix for prediction.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
NDArray[np.float64]: Predicted target values.
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def estimate_residuals(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
34
|
+
"""
|
|
35
|
+
Estimate residuals.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
X (NDArray[np.float64]): Input feature matrix for prediction. This
|
|
39
|
+
matrix is used as the true values.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
NDArray[np.float64]: Residuals.
|
|
43
|
+
"""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
def fit_cv(
|
|
47
|
+
self,
|
|
48
|
+
X: NDArray[np.float64],
|
|
49
|
+
y: Optional[NDArray[np.float64]],
|
|
50
|
+
cv: Union[int, BaseCrossValidator],
|
|
51
|
+
) -> Self:
|
|
52
|
+
"""
|
|
53
|
+
Fit an estmator using cross-validation.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
X (NDArray[np.float64]): Input feature matrix.
|
|
57
|
+
y (Optional[NDArray[np.float64]]): Target value.
|
|
58
|
+
cv (Union[int, BaseCrossValidator]): Number of folds (if int) or a
|
|
59
|
+
scikit-learn compatible cross-validator instance defining
|
|
60
|
+
the splitting strategy.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Self: The fitted estimator instance.
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def in_sample_estimation(self, steps: int) -> NDArray[np.float64]:
|
|
68
|
+
"""
|
|
69
|
+
Generate in sample estimations.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
steps (int): Number of estimated values to provide.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
NDArray[np.float64]: Matrix containing the estimated values.
|
|
76
|
+
"""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
def out_sample_estimation(self, steps: int) -> NDArray[np.float64]:
|
|
80
|
+
"""
|
|
81
|
+
Generate out sample estimations.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
steps (int): Number of estimated values to provide.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
NDArray[np.float64]: Matrix containing the estimated values.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def get_score(self, X: NDArray[np.float64]) -> float:
|
|
92
|
+
"""
|
|
93
|
+
Compute the score metric using the data provided.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
X (NDArray[np.float64]): Matrix containing the features.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
float: The scoring measure.
|
|
100
|
+
"""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class EstimatorController(BaseModel):
|
|
105
|
+
"""Controller class for managing and selecting estimators.
|
|
106
|
+
|
|
107
|
+
This class provides a mechanism to manage multiple estimator types.
|
|
108
|
+
It trains each estimator using the provided data, evaluates their
|
|
109
|
+
performance based on the scoring measure.
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
estimator_classes (ClassVar[List[Type]]): A list of estimator classes to be
|
|
113
|
+
evaluated.
|
|
114
|
+
best_estimator (Optional[Any]): The best estimator.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
estimator_classes: ClassVar[List[Type]] = [RidgeEstimator, VarEstimator]
|
|
118
|
+
best_estimator: Optional[Any] = None
|
|
119
|
+
|
|
120
|
+
def get_score(self, X: NDArray[np.float64], estimator: EstimatorProtocol) -> float:
|
|
121
|
+
"""
|
|
122
|
+
Compute the score metric using the data provided.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
X (NDArray[np.float64]): Matrix containing the features.
|
|
126
|
+
estimator (EstimatorProtocol): The estimator to be evaluated.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
float: The scoring measure.
|
|
130
|
+
"""
|
|
131
|
+
score = estimator.get_score(X)
|
|
132
|
+
return score
|
|
133
|
+
|
|
134
|
+
def fit(
|
|
135
|
+
self: R,
|
|
136
|
+
X: NDArray[np.float64],
|
|
137
|
+
cv: Union[int, BaseCrossValidator] = 5,
|
|
138
|
+
) -> R:
|
|
139
|
+
"""Train an estimator.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
X (NDArray[np.float64]): Input feature matrix for prediction.
|
|
143
|
+
cv (Union[int, BaseCrossValidator]): Cross-validation configuration.
|
|
144
|
+
Defaults to 5.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
ValueError: If no estimator is selected (i.e., `estimator_chosen` is None).
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
EstimatorController: The fitted estimator.
|
|
151
|
+
"""
|
|
152
|
+
best_score = None
|
|
153
|
+
estimator_chosen = None
|
|
154
|
+
iterator = tqdm(
|
|
155
|
+
self.estimator_classes,
|
|
156
|
+
desc="Evaluating estimators",
|
|
157
|
+
disable=False,
|
|
158
|
+
)
|
|
159
|
+
for EstimatorClass in iterator:
|
|
160
|
+
iterator.set_postfix(estimator=EstimatorClass.__name__)
|
|
161
|
+
# Instantiate the estimator
|
|
162
|
+
estimator = EstimatorClass()
|
|
163
|
+
estimator.fit_cv(X=X, cv=cv)
|
|
164
|
+
# Compute score
|
|
165
|
+
score = self.get_score(X=X, estimator=estimator)
|
|
166
|
+
if (best_score is None) or (best_score is not None and score < best_score):
|
|
167
|
+
estimator_chosen = estimator
|
|
168
|
+
best_score = score
|
|
169
|
+
# Evaluate the estimator in the test set
|
|
170
|
+
if estimator_chosen is None:
|
|
171
|
+
raise ValueError("`estimator_chosen` is None")
|
|
172
|
+
self.best_estimator = estimator_chosen
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
def predict(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
176
|
+
"""Generate predictions.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
X (NDArray[np.float64]): Input feature matrix for prediction.
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValueError: If the estimator has not been previously fitted
|
|
183
|
+
(i.e., `self.best_estimator` is None).
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
NDArray[np.float64]: Predicted target values.
|
|
187
|
+
"""
|
|
188
|
+
if self.best_estimator is None:
|
|
189
|
+
raise ValueError("You must call `fit()` before `predict()`.")
|
|
190
|
+
return cast(NDArray[np.float64], self.best_estimator.predict(X))
|
|
191
|
+
|
|
192
|
+
def estimate_residuals(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
193
|
+
"""
|
|
194
|
+
Estimate residuals.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
X (NDArray[np.float64]): Input feature matrix for prediction. This
|
|
198
|
+
matrix is used as the true values.
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
ValueError: If the estimator has not been previously fitted
|
|
202
|
+
(i.e., `self.best_estimator` is None).
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
NDArray[np.float64]: Residuals.
|
|
206
|
+
"""
|
|
207
|
+
if self.best_estimator is None:
|
|
208
|
+
raise ValueError("You must call `fit()` before `estimate_residuals()`.")
|
|
209
|
+
return cast(NDArray[np.float64], self.best_estimator.estimate_residuals(X))
|
|
210
|
+
|
|
211
|
+
def in_sample_estimation(self, steps: int) -> NDArray[np.float64]:
|
|
212
|
+
"""
|
|
213
|
+
Generate in sample estimations.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
steps (int): Number of estimated values to provide.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ValueError: If the estimator has not been previously fitted
|
|
220
|
+
(i.e., `self.best_estimator` is None).
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
NDArray[np.float64]: Matrix containing the estimated values.
|
|
224
|
+
"""
|
|
225
|
+
if self.best_estimator is None:
|
|
226
|
+
raise ValueError("You must call `fit()` before `in_sample_estimation()`.")
|
|
227
|
+
return cast(NDArray[np.float64], self.best_estimator.in_sample_estimation(steps))
|
|
228
|
+
|
|
229
|
+
def out_sample_estimation(self, steps: int) -> NDArray[np.float64]:
|
|
230
|
+
"""
|
|
231
|
+
Generate out sample estimations.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
steps (int): Number of estimated values to provide.
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
ValueError: If the estimator has not been previously fitted
|
|
238
|
+
(i.e., `self.best_estimator` is None).
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
NDArray[np.float64]: Matrix containing the estimated values.
|
|
242
|
+
"""
|
|
243
|
+
if self.best_estimator is None:
|
|
244
|
+
raise ValueError("You must call `fit()` before `out_sample_estimation()`.")
|
|
245
|
+
return cast(NDArray[np.float64], self.best_estimator.out_sample_estimation(steps))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from scentree.estimators.scikit_base import SklearnEstimator
|
|
2
|
+
from sklearn.base import BaseEstimator
|
|
3
|
+
from sklearn.linear_model import Ridge
|
|
4
|
+
from typing import Optional, Type
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RidgeEstimator(SklearnEstimator):
|
|
8
|
+
"""Wrapper for the scikit-learn `Ridge` regression estimator.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
estimator_class (Optional[Type[BaseEstimator]]): Estimator class to wrap.
|
|
12
|
+
Defaults to `sklearn.linear_model.Ridge`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, estimator_class: Optional[Type[BaseEstimator]] = None):
|
|
16
|
+
if estimator_class is None:
|
|
17
|
+
estimator_class = Ridge
|
|
18
|
+
super().__init__(estimator_class=estimator_class)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.typing import NDArray
|
|
3
|
+
from scentree.estimators.utils import get_default_parameters, get_hyperparameters_space
|
|
4
|
+
from scentree.metrics.rmse import rmse
|
|
5
|
+
from sklearn.base import BaseEstimator
|
|
6
|
+
from sklearn.metrics import make_scorer
|
|
7
|
+
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit
|
|
8
|
+
from sklearn.model_selection._split import _BaseKFold
|
|
9
|
+
from typing import cast, Any, Dict, Optional, Type, TypeVar, Union
|
|
10
|
+
|
|
11
|
+
R = TypeVar("R", bound="SklearnEstimator")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SklearnEstimator(BaseEstimator):
|
|
15
|
+
"""
|
|
16
|
+
Wrapper class for scikit-learn estimators to integrate them into the Scentree framework.
|
|
17
|
+
|
|
18
|
+
This class provides a unified interface for working with scikit-learn estimators.
|
|
19
|
+
It allows dynamic instantiation of the estimator with default or custom hyperparameters,
|
|
20
|
+
and exposes standard scikit-learn methods such as `fit`, `predict`, `get_params`, and
|
|
21
|
+
`set_params`. This makes it easy to use any scikit-learn estimator in a consistent way
|
|
22
|
+
within the framework.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
estimator_class (Type[Any]): The scikit-learn estimator class to wrap, e.g.,
|
|
26
|
+
`sklearn.linear_model.Ridge`.
|
|
27
|
+
estimator (Optional[Any]): Instance of the fitted estimator. This is set after
|
|
28
|
+
calling `fit`.
|
|
29
|
+
name (str): Name of the estimator class, derived from `estimator_class.__name__`.
|
|
30
|
+
hyperparameters (Dict[str, Any]): Dictionary of hyperparameter values used to
|
|
31
|
+
instantiate the estimator.
|
|
32
|
+
hyperparameters_space (Dict[str, List[Any]]): Dictionary defining the search
|
|
33
|
+
space for hyperparameters for tuning or optimization.
|
|
34
|
+
X_train_ (Optional[NDArray[np.float64]]): Optional attribute to store training data,
|
|
35
|
+
if needed for reference or internal operations.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, estimator_class: Type[Any]):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the SklearnEstimator wrapper with a given estimator class.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
estimator_class (Type[Any]): The scikit-learn estimator class to wrap.
|
|
44
|
+
"""
|
|
45
|
+
self.estimator = None
|
|
46
|
+
self.estimator_class = estimator_class
|
|
47
|
+
self.name = self.estimator_class.__name__
|
|
48
|
+
self.hyperparameters = get_default_parameters(estimator_class)
|
|
49
|
+
self.hyperparameters_space = get_hyperparameters_space(self.name)
|
|
50
|
+
self.X_train_: Optional[NDArray[np.float64]] = None
|
|
51
|
+
|
|
52
|
+
def fit(self: R, X: NDArray[np.float64], y: NDArray[np.float64]) -> R:
|
|
53
|
+
"""Fit the wrapped Scikit-learn estimator to the training data.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
X (NDArray[np.float64]): Input feature matrix for training.
|
|
57
|
+
y (NDArray[np.float64]): Target vector.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
R: The fitted wrapper instance.
|
|
61
|
+
"""
|
|
62
|
+
self.estimator = self.estimator_class(**self.hyperparameters)
|
|
63
|
+
assert self.estimator is not None
|
|
64
|
+
self.estimator.fit(X, y)
|
|
65
|
+
self.X_train_ = X
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def predict(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
69
|
+
"""Generate predictions using the fitted Scikit-learn estimator.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
X (NDArray[np.float64]): Input feature matrix for prediction.
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If `fit()` has not been called before prediction.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
NDArray[np.float64]: Predicted target values.
|
|
79
|
+
"""
|
|
80
|
+
if self.estimator is None:
|
|
81
|
+
raise ValueError("You must call `fit()` before `predict()`.")
|
|
82
|
+
return self.estimator.predict(X)
|
|
83
|
+
|
|
84
|
+
def get_params(self, deep: bool = True) -> Dict[str, Any]:
|
|
85
|
+
"""Return the hyperparameters of the estimator.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
deep (bool): Ignored parameter for compatibility with Scikit-learn.
|
|
89
|
+
Included for consistency with the BaseEstimator API.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Dict[str, Any]: Dictionary of current hyperparameter values.
|
|
93
|
+
"""
|
|
94
|
+
return {
|
|
95
|
+
"estimator_class": self.estimator_class,
|
|
96
|
+
"hyperparameters": self.hyperparameters,
|
|
97
|
+
"hyperparameters_space": self.hyperparameters_space,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def set_params(self: R, **params: Any) -> R:
|
|
101
|
+
"""Set hyperparameter values for the estimator.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
**params: Arbitrary keyword arguments of hyperparameters to update.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
R: The updated estimator instance (same type as self).
|
|
108
|
+
"""
|
|
109
|
+
for key, value in params.items():
|
|
110
|
+
if hasattr(self, key):
|
|
111
|
+
setattr(self, key, value)
|
|
112
|
+
elif key in self.hyperparameters:
|
|
113
|
+
self.hyperparameters[key] = value
|
|
114
|
+
else:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"Invalid parameter '{key}' for {self.__class__.__name__}. "
|
|
117
|
+
f"Valid parameters: constructor={list(self.__dict__.keys())}, "
|
|
118
|
+
f"hyperparameters={list(self.hyperparameters.keys())}."
|
|
119
|
+
)
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
def fit_cv(
|
|
123
|
+
self,
|
|
124
|
+
X: NDArray[np.float64],
|
|
125
|
+
cv: Union[int, _BaseKFold] = 5,
|
|
126
|
+
) -> BaseEstimator:
|
|
127
|
+
"""
|
|
128
|
+
Perform a Grid Search over the estimator's hyperparameters
|
|
129
|
+
and return a trained instance of the best model.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
X (NDArray[np.float64]): Training feature matrix.
|
|
133
|
+
cv (int or BaseCrossValidator): Cross-validation strategy. Default is 5-fold.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
BaseEstimator: An instance of the estimator with the best
|
|
137
|
+
hyperparameters, already fitted to the training data.
|
|
138
|
+
"""
|
|
139
|
+
# First of all, split data into two sets: feature and target.
|
|
140
|
+
# To begin with, only one lag is taken into account.
|
|
141
|
+
steps = X.shape[0]
|
|
142
|
+
feature = X[: (steps - 1), :]
|
|
143
|
+
target = X[1:, :]
|
|
144
|
+
|
|
145
|
+
# Define scoring, compatible with multi-output
|
|
146
|
+
scorer = make_scorer(rmse, greater_is_better=False)
|
|
147
|
+
|
|
148
|
+
if isinstance(cv, int):
|
|
149
|
+
cv_splitter = TimeSeriesSplit(n_splits=cv)
|
|
150
|
+
else:
|
|
151
|
+
cv_splitter = cv
|
|
152
|
+
|
|
153
|
+
# Create GridSearchCV
|
|
154
|
+
grid = GridSearchCV(
|
|
155
|
+
estimator=self.estimator_class(),
|
|
156
|
+
param_grid=self.hyperparameters_space,
|
|
157
|
+
scoring=scorer,
|
|
158
|
+
cv=cv_splitter,
|
|
159
|
+
n_jobs=-1,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Fit the grid search
|
|
163
|
+
grid.fit(feature, target)
|
|
164
|
+
|
|
165
|
+
# Instantiate and fit the best estimator
|
|
166
|
+
self.hyperparameters = grid.best_params_
|
|
167
|
+
self.fit(feature, target)
|
|
168
|
+
|
|
169
|
+
return self
|
|
170
|
+
|
|
171
|
+
def in_sample_estimation(self, steps: int) -> NDArray[np.float64]:
|
|
172
|
+
"""
|
|
173
|
+
Generate in sample estimations.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
steps (int): Number of estimated values to provide.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ValueError: If `fit()` has not been called before in_sample_estimation.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
NDArray[np.float64]: Matrix containing the estimated values.
|
|
183
|
+
"""
|
|
184
|
+
if self.X_train_ is None:
|
|
185
|
+
raise ValueError("You must call `fit()` before `in_sample_estimation()`.")
|
|
186
|
+
estimated_values = self.predict(self.X_train_[-steps:, :])
|
|
187
|
+
return estimated_values
|
|
188
|
+
|
|
189
|
+
def out_sample_estimation(self, steps: int) -> NDArray[np.float64]:
|
|
190
|
+
"""
|
|
191
|
+
Generate out sample estimations.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
steps (int): Number of estimated values to provide.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
ValueError: If `fit()` has not been called before out_sample_estimation.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
NDArray[np.float64]: Matrix containing the estimated values.
|
|
201
|
+
"""
|
|
202
|
+
if self.X_train_ is None:
|
|
203
|
+
raise ValueError("You must call `fit()` before `in_sample_estimation()`.")
|
|
204
|
+
estimated_values = np.full((steps, self.X_train_.shape[1]), np.nan)
|
|
205
|
+
current_data = np.reshape(self.X_train_[-1, :], shape=(1, -1))
|
|
206
|
+
for i_pred in range(steps):
|
|
207
|
+
current_estimation = np.reshape(self.predict(current_data), shape=(1, -1))
|
|
208
|
+
estimated_values[i_pred, :] = current_estimation
|
|
209
|
+
current_data = current_estimation
|
|
210
|
+
return estimated_values
|
|
211
|
+
|
|
212
|
+
def get_score(self, X: NDArray[np.float64]) -> float:
|
|
213
|
+
"""
|
|
214
|
+
Compute the score metric using the data provided.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
X (NDArray[np.float64]): Matrix containing the features.
|
|
218
|
+
estimator (EstimatorProtocol): The estimator to be evaluated.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
ValueError: If `fit()` has not been called before computing the score.
|
|
222
|
+
ValueError: If `X` does not have the correct shape (at least 2 samples).
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
float: The scoring measure.
|
|
226
|
+
"""
|
|
227
|
+
if self.X_train_ is None:
|
|
228
|
+
raise ValueError("You must call `fit()` before `get_score()`.")
|
|
229
|
+
if len(X.shape) != 2 or (len(X.shape) == 2 and X.shape[0] < 2):
|
|
230
|
+
raise ValueError("X must be of appropriate shape")
|
|
231
|
+
estimated_values = self.predict(X)
|
|
232
|
+
X_aligned = X[1:, :]
|
|
233
|
+
estimated_aligned = estimated_values[:-1, :]
|
|
234
|
+
return rmse(X_aligned, estimated_aligned)
|
|
235
|
+
|
|
236
|
+
def estimate_residuals(self, X: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
237
|
+
"""
|
|
238
|
+
Estimate residuals.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
X (NDArray[np.float64]): Input feature matrix for prediction. This
|
|
242
|
+
matrix is used as the true values.
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
ValueError: If `fit()` has not been called before computing the residuals.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
NDArray[np.float64]: Residuals.
|
|
249
|
+
"""
|
|
250
|
+
if self.X_train_ is None:
|
|
251
|
+
raise ValueError("You must call `fit()` before `estimate_residuals()`.")
|
|
252
|
+
estimated_values = self.predict(X)
|
|
253
|
+
X_true = X[1:, :]
|
|
254
|
+
X_estimated = estimated_values[:-1, :]
|
|
255
|
+
return cast(NDArray[np.float64], X_true - X_estimated)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from inspect import signature
|
|
2
|
+
from typing import Any, Dict, List, Type
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
HYPERPARAMETERS_SPACE: Dict[str, Dict[str, List[Any]]] = {
|
|
6
|
+
"Ridge": {
|
|
7
|
+
"alpha": [0.05, 0.06, 0.08, 0.1, 0.2, 0.25, 0.5, 0.7, 1, 2],
|
|
8
|
+
"max_iter": [100, 200, 300, 500, 1000],
|
|
9
|
+
},
|
|
10
|
+
"VAR": {"maxlags": [2, 5, 7, 10], "trend": ["n"]},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_hyperparameters_space(estimator_name: str) -> Dict[str, List[Any]]:
|
|
15
|
+
"""Obtain the hyperparameters space for a given estimator.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
estimator_name (str): Name of the estimator.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
dict: Dictionary with the range for each hyperparameter.
|
|
22
|
+
"""
|
|
23
|
+
return HYPERPARAMETERS_SPACE.get(estimator_name, {})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_default_parameters(class_chosen: Type, from_fit: bool = False) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Retrieve the default hyperparameters for a given estimator class.
|
|
29
|
+
|
|
30
|
+
This function inspects the constructor (`__init__`) or the `fit` method
|
|
31
|
+
of the specified estimator class and returns a dictionary containing
|
|
32
|
+
all parameters that have default values, excluding `self`.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
class_chosen (Type): The estimator class to retrieve default parameters from.
|
|
36
|
+
from_fit (bool, optional): If True, retrieve defaults from the `fit` method
|
|
37
|
+
instead of the constructor. Defaults to False.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict[str, Any]: A dictionary mapping parameter names to their default values.
|
|
41
|
+
Only parameters with default values are included.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
target = class_chosen.fit if from_fit else class_chosen.__init__
|
|
46
|
+
sig = signature(target)
|
|
47
|
+
defaults = {
|
|
48
|
+
k: v.default for k, v in sig.parameters.items() if v.default is not v.empty and k != "self"
|
|
49
|
+
}
|
|
50
|
+
return defaults
|