autogluon.timeseries 1.2.1b20250425__py3-none-any.whl → 1.2.1b20250427__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.
Files changed (20) hide show
  1. autogluon/timeseries/learner.py +1 -4
  2. autogluon/timeseries/metrics/__init__.py +36 -8
  3. autogluon/timeseries/metrics/abstract.py +77 -7
  4. autogluon/timeseries/metrics/point.py +136 -47
  5. autogluon/timeseries/metrics/quantile.py +42 -17
  6. autogluon/timeseries/models/abstract/abstract_timeseries_model.py +4 -18
  7. autogluon/timeseries/models/ensemble/greedy.py +8 -7
  8. autogluon/timeseries/models/presets.py +0 -2
  9. autogluon/timeseries/predictor.py +35 -27
  10. autogluon/timeseries/trainer.py +22 -15
  11. autogluon/timeseries/version.py +1 -1
  12. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/METADATA +5 -5
  13. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/RECORD +20 -20
  14. /autogluon.timeseries-1.2.1b20250425-py3.9-nspkg.pth → /autogluon.timeseries-1.2.1b20250427-py3.9-nspkg.pth +0 -0
  15. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/LICENSE +0 -0
  16. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/NOTICE +0 -0
  17. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/WHEEL +0 -0
  18. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/namespace_packages.txt +0 -0
  19. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/top_level.txt +0 -0
  20. {autogluon.timeseries-1.2.1b20250425.dist-info → autogluon.timeseries-1.2.1b20250427.dist-info}/zip-safe +0 -0
@@ -29,15 +29,13 @@ class TimeSeriesLearner(AbstractLearner):
29
29
  known_covariates_names: Optional[List[str]] = None,
30
30
  trainer_type: Type[TimeSeriesTrainer] = TimeSeriesTrainer,
31
31
  eval_metric: Union[str, TimeSeriesScorer, None] = None,
32
- eval_metric_seasonal_period: Optional[int] = None,
33
32
  prediction_length: int = 1,
34
33
  cache_predictions: bool = True,
35
34
  ensemble_model_type: Optional[Type] = None,
36
35
  **kwargs,
37
36
  ):
38
37
  super().__init__(path_context=path_context)
39
- self.eval_metric: TimeSeriesScorer = check_get_evaluation_metric(eval_metric)
40
- self.eval_metric_seasonal_period = eval_metric_seasonal_period
38
+ self.eval_metric = check_get_evaluation_metric(eval_metric, prediction_length=prediction_length)
41
39
  self.trainer_type = trainer_type
42
40
  self.target = target
43
41
  self.known_covariates_names = [] if known_covariates_names is None else known_covariates_names
@@ -82,7 +80,6 @@ class TimeSeriesLearner(AbstractLearner):
82
80
  path=self.model_context,
83
81
  prediction_length=self.prediction_length,
84
82
  eval_metric=self.eval_metric,
85
- eval_metric_seasonal_period=self.eval_metric_seasonal_period,
86
83
  target=self.target,
87
84
  quantile_levels=self.quantile_levels,
88
85
  verbosity=kwargs.get("verbosity", 2),
@@ -1,5 +1,7 @@
1
1
  from pprint import pformat
2
- from typing import Type, Union
2
+ from typing import Any, Dict, Optional, Sequence, Type, Union
3
+
4
+ import numpy as np
3
5
 
4
6
  from .abstract import TimeSeriesScorer
5
7
  from .point import MAE, MAPE, MASE, MSE, RMSE, RMSLE, RMSSE, SMAPE, WAPE, WCD
@@ -22,7 +24,7 @@ __all__ = [
22
24
 
23
25
  DEFAULT_METRIC_NAME = "WQL"
24
26
 
25
- AVAILABLE_METRICS = {
27
+ AVAILABLE_METRICS: Dict[str, Type[TimeSeriesScorer]] = {
26
28
  "MASE": MASE,
27
29
  "MAPE": MAPE,
28
30
  "SMAPE": SMAPE,
@@ -42,33 +44,59 @@ DEPRECATED_METRICS = {
42
44
  }
43
45
 
44
46
  # Experimental metrics that are not yet user facing
45
- EXPERIMENTAL_METRICS = {
47
+ EXPERIMENTAL_METRICS: Dict[str, Type[TimeSeriesScorer]] = {
46
48
  "WCD": WCD,
47
49
  }
48
50
 
49
51
 
50
52
  def check_get_evaluation_metric(
51
- eval_metric: Union[str, TimeSeriesScorer, Type[TimeSeriesScorer], None] = None
53
+ eval_metric: Union[str, TimeSeriesScorer, Type[TimeSeriesScorer], None],
54
+ prediction_length: int,
55
+ seasonal_period: Optional[int] = None,
56
+ horizon_weight: Optional[Sequence[float] | np.ndarray] = None,
52
57
  ) -> TimeSeriesScorer:
58
+ """Factory method for TimeSeriesScorer objects.
59
+
60
+ Returns
61
+ -------
62
+ scorer :
63
+ A `TimeSeriesScorer` object based on the provided `eval_metric`.
64
+
65
+ `scorer.prediction_length` is always set to the `prediction_length` provided to this method.
66
+
67
+ If `seasonal_period` is not `None`, then `scorer.seasonal_period` is set to this value. Otherwise the original
68
+ value of `seasonal_period` is kept.
69
+
70
+ If `horizon_weight` is not `None`, then `scorer.horizon_weight` is set to this value. Otherwise the original
71
+ value of `horizon_weight` is kept.
72
+ """
53
73
  scorer: TimeSeriesScorer
74
+ metric_kwargs: Dict[str, Any] = dict(
75
+ prediction_length=prediction_length, seasonal_period=seasonal_period, horizon_weight=horizon_weight
76
+ )
54
77
  if isinstance(eval_metric, TimeSeriesScorer):
55
78
  scorer = eval_metric
79
+ scorer.prediction_length = prediction_length
80
+ if seasonal_period is not None:
81
+ scorer.seasonal_period = seasonal_period
82
+ if horizon_weight is not None:
83
+ scorer.horizon_weight = scorer.check_get_horizon_weight(horizon_weight, prediction_length=prediction_length)
56
84
  elif isinstance(eval_metric, type) and issubclass(eval_metric, TimeSeriesScorer):
57
85
  # e.g., user passed `eval_metric=CustomMetric` instead of `eval_metric=CustomMetric()`
58
- scorer = eval_metric()
86
+ scorer = eval_metric(**metric_kwargs)
59
87
  elif isinstance(eval_metric, str):
60
88
  metric_name = DEPRECATED_METRICS.get(eval_metric, eval_metric).upper()
61
89
  if metric_name in AVAILABLE_METRICS:
62
- scorer = AVAILABLE_METRICS[metric_name]()
90
+ scorer = AVAILABLE_METRICS[metric_name](**metric_kwargs)
63
91
  elif metric_name in EXPERIMENTAL_METRICS:
64
- scorer = EXPERIMENTAL_METRICS[metric_name]()
92
+ scorer = EXPERIMENTAL_METRICS[metric_name](**metric_kwargs)
65
93
  else:
66
94
  raise ValueError(
67
95
  f"Time series metric {eval_metric} not supported. Available metrics are:\n"
68
96
  f"{pformat(sorted(AVAILABLE_METRICS.keys()))}"
69
97
  )
70
98
  elif eval_metric is None:
71
- scorer = AVAILABLE_METRICS[DEFAULT_METRIC_NAME]()
99
+ scorer = AVAILABLE_METRICS[DEFAULT_METRIC_NAME](**metric_kwargs)
72
100
  else:
73
101
  raise ValueError(
74
102
  f"eval_metric must be of type str, TimeSeriesScorer or None "
@@ -1,6 +1,8 @@
1
- from typing import Optional, Tuple, Union
1
+ import warnings
2
+ from typing import Optional, Sequence, Tuple, Union, overload
2
3
 
3
4
  import numpy as np
5
+ import numpy.typing as npt
4
6
  import pandas as pd
5
7
 
6
8
  from autogluon.timeseries import TimeSeriesDataFrame
@@ -15,6 +17,18 @@ class TimeSeriesScorer:
15
17
 
16
18
  Follows the design of ``autogluon.core.metrics.Scorer``.
17
19
 
20
+ Parameters
21
+ ----------
22
+ prediction_length : int, default = 1
23
+ The length of the forecast horizon. The predictions provided to the `TimeSeriesScorer` are expected to contain
24
+ a forecast for this many time steps for each time series.
25
+ seasonal_period : int or None, default = None
26
+ Seasonal period used to compute some evaluation metrics such as mean absolute scaled error (MASE). Defaults to
27
+ `None`, in which case the seasonal period is computed based on the data frequency.
28
+ horizon_weight : Sequence[float], np.ndarray or None, default = None
29
+ Weight assigned to each time step in the forecast horizon when computing the metric. If provided, the
30
+ `horizon_weight` will be stored as a numpy array of shape `[1, prediction_length]`.
31
+
18
32
  Attributes
19
33
  ----------
20
34
  greater_is_better_internal : bool, default = False
@@ -40,6 +54,18 @@ class TimeSeriesScorer:
40
54
  needs_quantile: bool = False
41
55
  equivalent_tabular_regression_metric: Optional[str] = None
42
56
 
57
+ def __init__(
58
+ self,
59
+ prediction_length: int = 1,
60
+ seasonal_period: Optional[int] = None,
61
+ horizon_weight: Optional[Sequence[float]] = None,
62
+ ):
63
+ self.prediction_length = int(prediction_length)
64
+ if self.prediction_length < 1:
65
+ raise ValueError(f"prediction_length must be >= 1 (received {prediction_length})")
66
+ self.seasonal_period = seasonal_period
67
+ self.horizon_weight = self.check_get_horizon_weight(horizon_weight, prediction_length=prediction_length)
68
+
43
69
  @property
44
70
  def sign(self) -> int:
45
71
  return 1 if self.greater_is_better_internal else -1
@@ -66,18 +92,25 @@ class TimeSeriesScorer:
66
92
  self,
67
93
  data: TimeSeriesDataFrame,
68
94
  predictions: TimeSeriesDataFrame,
69
- prediction_length: int = 1,
70
95
  target: str = "target",
71
- seasonal_period: Optional[int] = None,
72
96
  **kwargs,
73
97
  ) -> float:
74
- seasonal_period = get_seasonality(data.freq) if seasonal_period is None else seasonal_period
98
+ seasonal_period = get_seasonality(data.freq) if self.seasonal_period is None else self.seasonal_period
99
+
100
+ if "prediction_length" in kwargs:
101
+ warnings.warn(
102
+ "Passing `prediction_length` to `TimeSeriesScorer.__call__` is deprecated and will be removed in v2.0. "
103
+ "Please set the `eval_metric.prediction_length` attribute instead.",
104
+ category=FutureWarning,
105
+ )
106
+ self.prediction_length = kwargs["prediction_length"]
107
+ self.horizon_weight = self.check_get_horizon_weight(self.horizon_weight, self.prediction_length)
75
108
 
76
- data_past = data.slice_by_timestep(None, -prediction_length)
77
- data_future = data.slice_by_timestep(-prediction_length, None)
109
+ data_past = data.slice_by_timestep(None, -self.prediction_length)
110
+ data_future = data.slice_by_timestep(-self.prediction_length, None)
78
111
 
79
112
  assert not predictions.isna().any().any(), "Predictions contain NaN values."
80
- assert (predictions.num_timesteps_per_item() == prediction_length).all()
113
+ assert (predictions.num_timesteps_per_item() == self.prediction_length).all()
81
114
  assert data_future.index.equals(predictions.index), "Prediction and data indices do not match."
82
115
 
83
116
  try:
@@ -200,3 +233,40 @@ class TimeSeriesScorer:
200
233
  q_pred = pd.DataFrame(predictions[quantile_columns])
201
234
  quantile_levels = np.array(quantile_columns, dtype=float)
202
235
  return y_true, q_pred, quantile_levels
236
+
237
+ @overload
238
+ @staticmethod
239
+ def check_get_horizon_weight(horizon_weight: None, prediction_length: int) -> None: ...
240
+ @overload
241
+ @staticmethod
242
+ def check_get_horizon_weight(
243
+ horizon_weight: Sequence[float] | np.ndarray, prediction_length: int
244
+ ) -> npt.NDArray[np.float64]: ...
245
+
246
+ @staticmethod
247
+ def check_get_horizon_weight(
248
+ horizon_weight: Sequence[float] | np.ndarray | None, prediction_length: int
249
+ ) -> Optional[npt.NDArray[np.float64]]:
250
+ """Convert horizon_weight to a non-negative numpy array that sums up to prediction_length.
251
+ Raises an exception if horizon_weight has an invalid shape or contains invalid values.
252
+
253
+ Returns
254
+ -------
255
+ horizon_weight:
256
+ None if the input is None, otherwise a numpy array of shape [1, prediction_length].
257
+ """
258
+ if horizon_weight is None:
259
+ return None
260
+ horizon_weight_np = np.ravel(horizon_weight).astype(np.float64)
261
+ if horizon_weight_np.shape != (prediction_length,):
262
+ raise ValueError(
263
+ f"horizon_weight must have length equal to {prediction_length=} (got {len(horizon_weight)=})"
264
+ )
265
+ if not (horizon_weight_np >= 0).all():
266
+ raise ValueError(f"All values in horizon_weight must be >= 0 (got {horizon_weight})")
267
+ if not horizon_weight_np.sum() > 0:
268
+ raise ValueError(f"At least some values in horizon_weight must be > 0 (got {horizon_weight})")
269
+ if not np.isfinite(horizon_weight_np).all():
270
+ raise ValueError(f"All horizon_weight values must be finite (got {horizon_weight})")
271
+ horizon_weight_np = horizon_weight_np * prediction_length / horizon_weight_np.sum()
272
+ return horizon_weight_np.reshape([1, prediction_length])
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import warnings
3
- from typing import Optional
3
+ from typing import Optional, Sequence
4
4
 
5
5
  import numpy as np
6
6
  import pandas as pd
@@ -38,10 +38,18 @@ class RMSE(TimeSeriesScorer):
38
38
  equivalent_tabular_regression_metric = "root_mean_squared_error"
39
39
 
40
40
  def compute_metric(
41
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
41
+ self,
42
+ data_future: TimeSeriesDataFrame,
43
+ predictions: TimeSeriesDataFrame,
44
+ target: str = "target",
45
+ **kwargs,
42
46
  ) -> float:
43
47
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
44
- return np.sqrt(self._safemean((y_true - y_pred) ** 2))
48
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
49
+ errors = ((y_true - y_pred) ** 2).reshape([-1, self.prediction_length])
50
+ if self.horizon_weight is not None:
51
+ errors *= self.horizon_weight
52
+ return np.sqrt(self._safemean(errors))
45
53
 
46
54
 
47
55
  class MSE(TimeSeriesScorer):
@@ -69,10 +77,18 @@ class MSE(TimeSeriesScorer):
69
77
  equivalent_tabular_regression_metric = "mean_squared_error"
70
78
 
71
79
  def compute_metric(
72
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
80
+ self,
81
+ data_future: TimeSeriesDataFrame,
82
+ predictions: TimeSeriesDataFrame,
83
+ target: str = "target",
84
+ **kwargs,
73
85
  ) -> float:
74
86
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
75
- return self._safemean((y_true - y_pred) ** 2)
87
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
88
+ errors = ((y_true - y_pred) ** 2).reshape([-1, self.prediction_length])
89
+ if self.horizon_weight is not None:
90
+ errors *= self.horizon_weight
91
+ return self._safemean(errors)
76
92
 
77
93
 
78
94
  class MAE(TimeSeriesScorer):
@@ -98,10 +114,18 @@ class MAE(TimeSeriesScorer):
98
114
  equivalent_tabular_regression_metric = "mean_absolute_error"
99
115
 
100
116
  def compute_metric(
101
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
117
+ self,
118
+ data_future: TimeSeriesDataFrame,
119
+ predictions: TimeSeriesDataFrame,
120
+ target: str = "target",
121
+ **kwargs,
102
122
  ) -> float:
103
123
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
104
- return self._safemean((y_true - y_pred).abs())
124
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
125
+ errors = np.abs(y_true - y_pred).reshape([-1, self.prediction_length])
126
+ if self.horizon_weight is not None:
127
+ errors *= self.horizon_weight
128
+ return self._safemean(errors)
105
129
 
106
130
 
107
131
  class WAPE(TimeSeriesScorer):
@@ -119,6 +143,7 @@ class WAPE(TimeSeriesScorer):
119
143
  - not sensitive to outliers
120
144
  - prefers models that accurately estimate the median
121
145
 
146
+ If `self.horizon_weight` is provided, both the errors and the target time series in the denominator will be re-weighted.
122
147
 
123
148
  References
124
149
  ----------
@@ -129,10 +154,19 @@ class WAPE(TimeSeriesScorer):
129
154
  equivalent_tabular_regression_metric = "mean_absolute_error"
130
155
 
131
156
  def compute_metric(
132
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
157
+ self,
158
+ data_future: TimeSeriesDataFrame,
159
+ predictions: TimeSeriesDataFrame,
160
+ target: str = "target",
161
+ **kwargs,
133
162
  ) -> float:
134
163
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
135
- return (y_true - y_pred).abs().sum() / y_true.abs().sum()
164
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
165
+ errors = np.abs(y_true - y_pred).reshape([-1, self.prediction_length])
166
+ if self.horizon_weight is not None:
167
+ errors *= self.horizon_weight
168
+ y_true = y_true.reshape([-1, self.prediction_length]) * self.horizon_weight
169
+ return np.nansum(errors) / np.nansum(np.abs(y_true))
136
170
 
137
171
 
138
172
  class SMAPE(TimeSeriesScorer):
@@ -158,10 +192,18 @@ class SMAPE(TimeSeriesScorer):
158
192
  equivalent_tabular_regression_metric = "symmetric_mean_absolute_percentage_error"
159
193
 
160
194
  def compute_metric(
161
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
195
+ self,
196
+ data_future: TimeSeriesDataFrame,
197
+ predictions: TimeSeriesDataFrame,
198
+ target: str = "target",
199
+ **kwargs,
162
200
  ) -> float:
163
201
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
164
- return self._safemean(2 * ((y_true - y_pred).abs() / (y_true.abs() + y_pred.abs())))
202
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
203
+ errors = (np.abs(y_true - y_pred) / (np.abs(y_true) + np.abs(y_pred))).reshape([-1, self.prediction_length])
204
+ if self.horizon_weight is not None:
205
+ errors *= self.horizon_weight
206
+ return 2 * self._safemean(errors)
165
207
 
166
208
 
167
209
  class MAPE(TimeSeriesScorer):
@@ -187,10 +229,18 @@ class MAPE(TimeSeriesScorer):
187
229
  equivalent_tabular_regression_metric = "mean_absolute_percentage_error"
188
230
 
189
231
  def compute_metric(
190
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
232
+ self,
233
+ data_future: TimeSeriesDataFrame,
234
+ predictions: TimeSeriesDataFrame,
235
+ target: str = "target",
236
+ **kwargs,
191
237
  ) -> float:
192
238
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
193
- return self._safemean((y_true - y_pred).abs() / y_true.abs())
239
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
240
+ errors = (np.abs(y_true - y_pred) / np.abs(y_true)).reshape([-1, self.prediction_length])
241
+ if self.horizon_weight is not None:
242
+ errors *= self.horizon_weight
243
+ return self._safemean(errors)
194
244
 
195
245
 
196
246
  class MASE(TimeSeriesScorer):
@@ -226,7 +276,15 @@ class MASE(TimeSeriesScorer):
226
276
  optimized_by_median = True
227
277
  equivalent_tabular_regression_metric = "mean_absolute_error"
228
278
 
229
- def __init__(self):
279
+ def __init__(
280
+ self,
281
+ prediction_length: int = 1,
282
+ seasonal_period: Optional[int] = None,
283
+ horizon_weight: Optional[Sequence[float]] = None,
284
+ ):
285
+ super().__init__(
286
+ prediction_length=prediction_length, seasonal_period=seasonal_period, horizon_weight=horizon_weight
287
+ )
230
288
  self._past_abs_seasonal_error: Optional[pd.Series] = None
231
289
 
232
290
  def save_past_metrics(
@@ -240,16 +298,22 @@ class MASE(TimeSeriesScorer):
240
298
  self._past_abs_seasonal_error = None
241
299
 
242
300
  def compute_metric(
243
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
301
+ self,
302
+ data_future: TimeSeriesDataFrame,
303
+ predictions: TimeSeriesDataFrame,
304
+ target: str = "target",
305
+ **kwargs,
244
306
  ) -> float:
245
- y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
246
307
  if self._past_abs_seasonal_error is None:
247
308
  raise AssertionError("Call `save_past_metrics` before `compute_metric`")
248
309
 
249
- num_items = len(self._past_abs_seasonal_error)
250
- # Reshape abs errors into [num_items, prediction_length] to normalize per item without groupby
251
- abs_errors = np.abs(y_true.to_numpy() - y_pred.to_numpy()).reshape([num_items, -1])
252
- return self._safemean(abs_errors / self._past_abs_seasonal_error.values[:, None])
310
+ y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
311
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
312
+
313
+ errors = np.abs(y_true - y_pred).reshape([-1, self.prediction_length])
314
+ if self.horizon_weight is not None:
315
+ errors *= self.horizon_weight
316
+ return self._safemean(errors / self._past_abs_seasonal_error.to_numpy()[:, None])
253
317
 
254
318
 
255
319
  class RMSSE(TimeSeriesScorer):
@@ -286,7 +350,15 @@ class RMSSE(TimeSeriesScorer):
286
350
 
287
351
  equivalent_tabular_regression_metric = "root_mean_squared_error"
288
352
 
289
- def __init__(self):
353
+ def __init__(
354
+ self,
355
+ prediction_length: int = 1,
356
+ seasonal_period: Optional[int] = None,
357
+ horizon_weight: Optional[Sequence[float]] = None,
358
+ ):
359
+ super().__init__(
360
+ prediction_length=prediction_length, seasonal_period=seasonal_period, horizon_weight=horizon_weight
361
+ )
290
362
  self._past_squared_seasonal_error: Optional[pd.Series] = None
291
363
 
292
364
  def save_past_metrics(
@@ -300,16 +372,21 @@ class RMSSE(TimeSeriesScorer):
300
372
  self._past_squared_seasonal_error = None
301
373
 
302
374
  def compute_metric(
303
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
375
+ self,
376
+ data_future: TimeSeriesDataFrame,
377
+ predictions: TimeSeriesDataFrame,
378
+ target: str = "target",
379
+ **kwargs,
304
380
  ) -> float:
305
- y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
306
381
  if self._past_squared_seasonal_error is None:
307
382
  raise AssertionError("Call `save_past_metrics` before `compute_metric`")
308
383
 
309
- num_items = len(self._past_squared_seasonal_error)
310
- # Reshape squared errors into [num_items, prediction_length] to normalize per item without groupby
311
- squared_errors = ((y_true.to_numpy() - y_pred.to_numpy()) ** 2.0).reshape([num_items, -1])
312
- return np.sqrt(self._safemean(squared_errors / self._past_squared_seasonal_error.values[:, None]))
384
+ y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
385
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
386
+ errors = ((y_true - y_pred) ** 2).reshape([-1, self.prediction_length])
387
+ if self.horizon_weight is not None:
388
+ errors *= self.horizon_weight
389
+ return np.sqrt(self._safemean(errors / self._past_squared_seasonal_error.to_numpy()[:, None]))
313
390
 
314
391
 
315
392
  class RMSLE(TimeSeriesScorer):
@@ -336,20 +413,26 @@ class RMSLE(TimeSeriesScorer):
336
413
  """
337
414
 
338
415
  def compute_metric(
339
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
416
+ self,
417
+ data_future: TimeSeriesDataFrame,
418
+ predictions: TimeSeriesDataFrame,
419
+ target: str = "target",
420
+ **kwargs,
340
421
  ) -> float:
341
422
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
423
+ y_true, y_pred = y_true.to_numpy(), y_pred.to_numpy()
342
424
  y_pred = np.clip(y_pred, a_min=0.0, a_max=None)
343
425
 
344
- return np.sqrt(np.power(np.log1p(y_pred) - np.log1p(y_true), 2).mean())
426
+ errors = np.power(np.log1p(y_pred) - np.log1p(y_true), 2).reshape([-1, self.prediction_length])
427
+ if self.horizon_weight is not None:
428
+ errors *= self.horizon_weight
429
+ return np.sqrt(self._safemean(errors))
345
430
 
346
431
  def __call__(
347
432
  self,
348
433
  data: TimeSeriesDataFrame,
349
434
  predictions: TimeSeriesDataFrame,
350
- prediction_length: int = 1,
351
435
  target: str = "target",
352
- seasonal_period: Optional[int] = None,
353
436
  **kwargs,
354
437
  ) -> float:
355
438
  if (data[target] < 0).any():
@@ -357,9 +440,7 @@ class RMSLE(TimeSeriesScorer):
357
440
  return super().__call__(
358
441
  data=data,
359
442
  predictions=predictions,
360
- prediction_length=prediction_length,
361
443
  target=target,
362
- seasonal_period=seasonal_period,
363
444
  **kwargs,
364
445
  )
365
446
 
@@ -382,35 +463,43 @@ class WCD(TimeSeriesScorer):
382
463
  Parameters
383
464
  ----------
384
465
  alpha : float, default = 0.5
385
- Values > 0.5 correspond put a stronger penalty on underpredictions (when cumulative forecast is below the
466
+ Values > 0.5 put a stronger penalty on underpredictions (when cumulative forecast is below the
386
467
  cumulative actual value). Values < 0.5 put a stronger penalty on overpredictions.
387
468
  """
388
469
 
389
- def __init__(self, alpha: float = 0.5):
470
+ def __init__(
471
+ self,
472
+ alpha: float = 0.5,
473
+ prediction_length: int = 1,
474
+ seasonal_period: Optional[int] = None,
475
+ horizon_weight: Optional[Sequence[float]] = None,
476
+ ):
477
+ super().__init__(
478
+ prediction_length=prediction_length, seasonal_period=seasonal_period, horizon_weight=horizon_weight
479
+ )
390
480
  assert 0 < alpha < 1, "alpha must be in (0, 1)"
391
481
  self.alpha = alpha
392
- self.num_items: Optional[int] = None
393
482
  warnings.warn(
394
483
  f"{self.name} is an experimental metric. Its behavior may change in the future version of AutoGluon."
395
484
  )
396
485
 
397
- def save_past_metrics(
398
- self, data_past: TimeSeriesDataFrame, target: str = "target", seasonal_period: int = 1, **kwargs
399
- ) -> None:
400
- self.num_items = data_past.num_items
401
-
402
486
  def _fast_cumsum(self, y: np.ndarray) -> np.ndarray:
403
- """Compute the cumulative sum for each consecutive `prediction_length` items in the array."""
404
- assert self.num_items is not None, "Make sure to call `save_past_metrics` before `compute_metric`"
405
- y = y.reshape(self.num_items, -1)
487
+ """Compute the cumulative sum for each consecutive `self.prediction_length` items in the array."""
488
+ y = y.reshape(-1, self.prediction_length)
406
489
  return np.nancumsum(y, axis=1).ravel()
407
490
 
408
491
  def compute_metric(
409
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
492
+ self,
493
+ data_future: TimeSeriesDataFrame,
494
+ predictions: TimeSeriesDataFrame,
495
+ target: str = "target",
496
+ **kwargs,
410
497
  ) -> float:
411
498
  y_true, y_pred = self._get_point_forecast_score_inputs(data_future, predictions, target=target)
412
499
  cumsum_true = self._fast_cumsum(y_true.to_numpy())
413
500
  cumsum_pred = self._fast_cumsum(y_pred.to_numpy())
414
501
  diffs = cumsum_pred - cumsum_true
415
- error = diffs * np.where(diffs < 0, -self.alpha, (1 - self.alpha))
416
- return 2 * self._safemean(error)
502
+ errors = (diffs * np.where(diffs < 0, -self.alpha, (1 - self.alpha))).reshape([-1, self.prediction_length])
503
+ if self.horizon_weight is not None:
504
+ errors *= self.horizon_weight
505
+ return 2 * self._safemean(errors)
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import Optional, Sequence
2
2
 
3
3
  import numpy as np
4
4
  import pandas as pd
@@ -25,6 +25,7 @@ class WQL(TimeSeriesScorer):
25
25
  - scale-dependent (time series with large absolute value contribute more to the loss)
26
26
  - equivalent to WAPE if ``quantile_levels = [0.5]``
27
27
 
28
+ If `horizon_weight` is provided, both the errors and the target time series in the denominator will be re-weighted.
28
29
 
29
30
  References
30
31
  ----------
@@ -34,16 +35,25 @@ class WQL(TimeSeriesScorer):
34
35
  needs_quantile = True
35
36
 
36
37
  def compute_metric(
37
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
38
+ self,
39
+ data_future: TimeSeriesDataFrame,
40
+ predictions: TimeSeriesDataFrame,
41
+ target: str = "target",
42
+ **kwargs,
38
43
  ) -> float:
39
44
  y_true, q_pred, quantile_levels = self._get_quantile_forecast_score_inputs(data_future, predictions, target)
40
- values_true = y_true.values[:, None] # shape [N, 1]
41
- values_pred = q_pred.values # shape [N, len(quantile_levels)]
45
+ y_true = y_true.to_numpy()[:, None] # shape [N, 1]
46
+ q_pred = q_pred.to_numpy() # shape [N, len(quantile_levels)]
42
47
 
43
- return 2 * np.mean(
44
- np.nansum(np.abs((values_true - values_pred) * ((values_true <= values_pred) - quantile_levels)), axis=0)
45
- / np.nansum(np.abs(values_true))
48
+ errors = (
49
+ np.abs((q_pred - y_true) * ((y_true <= q_pred) - quantile_levels))
50
+ .mean(axis=1)
51
+ .reshape([-1, self.prediction_length])
46
52
  )
53
+ if self.horizon_weight is not None:
54
+ errors *= self.horizon_weight
55
+ y_true = y_true.reshape([-1, self.prediction_length]) * self.horizon_weight
56
+ return 2 * np.nansum(errors) / np.nansum(np.abs(y_true))
47
57
 
48
58
 
49
59
  class SQL(TimeSeriesScorer):
@@ -79,7 +89,15 @@ class SQL(TimeSeriesScorer):
79
89
 
80
90
  needs_quantile = True
81
91
 
82
- def __init__(self):
92
+ def __init__(
93
+ self,
94
+ prediction_length: int = 1,
95
+ seasonal_period: Optional[int] = None,
96
+ horizon_weight: Optional[Sequence[float]] = None,
97
+ ):
98
+ super().__init__(
99
+ prediction_length=prediction_length, seasonal_period=seasonal_period, horizon_weight=horizon_weight
100
+ )
83
101
  self._past_abs_seasonal_error: Optional[pd.Series] = None
84
102
 
85
103
  def save_past_metrics(
@@ -93,17 +111,24 @@ class SQL(TimeSeriesScorer):
93
111
  self._past_abs_seasonal_error = None
94
112
 
95
113
  def compute_metric(
96
- self, data_future: TimeSeriesDataFrame, predictions: TimeSeriesDataFrame, target: str = "target", **kwargs
114
+ self,
115
+ data_future: TimeSeriesDataFrame,
116
+ predictions: TimeSeriesDataFrame,
117
+ target: str = "target",
118
+ **kwargs,
97
119
  ) -> float:
98
120
  if self._past_abs_seasonal_error is None:
99
121
  raise AssertionError("Call `save_past_metrics` before `compute_metric`")
100
122
 
101
123
  y_true, q_pred, quantile_levels = self._get_quantile_forecast_score_inputs(data_future, predictions, target)
102
- q_pred = q_pred.values
103
- values_true = y_true.values[:, None] # shape [N, 1]
104
-
105
- ql = np.abs((q_pred - values_true) * ((values_true <= q_pred) - quantile_levels)).mean(axis=1)
106
- num_items = len(self._past_abs_seasonal_error)
107
- # Reshape quantile losses values into [num_items, prediction_length] to normalize per item without groupby
108
- quantile_losses = ql.reshape([num_items, -1])
109
- return 2 * self._safemean(quantile_losses / self._past_abs_seasonal_error.values[:, None])
124
+ q_pred = q_pred.to_numpy()
125
+ y_true = y_true.to_numpy()[:, None] # shape [N, 1]
126
+
127
+ errors = (
128
+ np.abs((q_pred - y_true) * ((y_true <= q_pred) - quantile_levels))
129
+ .mean(axis=1)
130
+ .reshape([-1, self.prediction_length])
131
+ )
132
+ if self.horizon_weight is not None:
133
+ errors *= self.horizon_weight
134
+ return 2 * self._safemean(errors / self._past_abs_seasonal_error.to_numpy()[:, None])
@@ -57,9 +57,6 @@ class TimeSeriesModelBase(ModelBase, ABC):
57
57
  Metric by which predictions will be ultimately evaluated on future test data. This only impacts
58
58
  ``model.score()``, as eval_metric is not used during training. Available metrics can be found in
59
59
  ``autogluon.timeseries.metrics``.
60
- eval_metric_seasonal_period : int, optional
61
- Seasonal period used to compute some evaluation metrics such as mean absolute scaled error (MASE). Defaults to
62
- ``None``, in which case the seasonal period is computed based on the data frequency.
63
60
  hyperparameters : dict, default = None
64
61
  Hyperparameters that will be used by the model (can be search spaces instead of fixed values).
65
62
  If None, model defaults are used. This is identical to passing an empty dictionary.
@@ -88,7 +85,6 @@ class TimeSeriesModelBase(ModelBase, ABC):
88
85
  target: str = "target",
89
86
  quantile_levels: Sequence[float] = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
90
87
  eval_metric: Union[str, TimeSeriesScorer, None] = None,
91
- eval_metric_seasonal_period: Optional[int] = None,
92
88
  ):
93
89
  self.name = name or re.sub(r"Model$", "", self.__class__.__name__)
94
90
 
@@ -103,8 +99,7 @@ class TimeSeriesModelBase(ModelBase, ABC):
103
99
 
104
100
  self.path = os.path.join(self.path_root, self.name)
105
101
 
106
- self.eval_metric: TimeSeriesScorer = check_get_evaluation_metric(eval_metric)
107
- self.eval_metric_seasonal_period = eval_metric_seasonal_period
102
+ self.eval_metric = check_get_evaluation_metric(eval_metric, prediction_length=prediction_length)
108
103
  self.target: str = target
109
104
  self.covariate_metadata = covariate_metadata or CovariateMetadata()
110
105
 
@@ -393,7 +388,6 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
393
388
  target: str = "target",
394
389
  quantile_levels: Sequence[float] = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
395
390
  eval_metric: Union[str, TimeSeriesScorer, None] = None,
396
- eval_metric_seasonal_period: Optional[int] = None,
397
391
  ):
398
392
  # TODO: make freq a required argument in AbstractTimeSeriesModel
399
393
  super().__init__(
@@ -406,7 +400,6 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
406
400
  target=target,
407
401
  quantile_levels=quantile_levels,
408
402
  eval_metric=eval_metric,
409
- eval_metric_seasonal_period=eval_metric_seasonal_period,
410
403
  )
411
404
  self.target_scaler: Optional[TargetScaler]
412
405
  self.covariate_scaler: Optional[CovariateScaler]
@@ -700,19 +693,15 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
700
693
  self,
701
694
  data: TimeSeriesDataFrame,
702
695
  predictions: TimeSeriesDataFrame,
703
- metric: Optional[str] = None,
704
696
  ) -> float:
705
697
  """Compute the score measuring how well the predictions align with the data."""
706
- eval_metric = self.eval_metric if metric is None else check_get_evaluation_metric(metric)
707
- return eval_metric.score(
698
+ return self.eval_metric.score(
708
699
  data=data,
709
700
  predictions=predictions,
710
- prediction_length=self.prediction_length,
711
701
  target=self.target,
712
- seasonal_period=self.eval_metric_seasonal_period,
713
702
  )
714
703
 
715
- def score(self, data: TimeSeriesDataFrame, metric: Optional[str] = None) -> float:
704
+ def score(self, data: TimeSeriesDataFrame) -> float:
716
705
  """Return the evaluation scores for given metric and dataset. The last
717
706
  `self.prediction_length` time steps of each time series in the input data set
718
707
  will be held out and used for computing the evaluation score. Time series
@@ -722,9 +711,6 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
722
711
  ----------
723
712
  data: TimeSeriesDataFrame
724
713
  Dataset used for scoring.
725
- metric: str
726
- String identifier of evaluation metric to use, from one of
727
- `autogluon.timeseries.utils.metric_utils.AVAILABLE_METRICS`.
728
714
 
729
715
  Returns
730
716
  -------
@@ -736,7 +722,7 @@ class AbstractTimeSeriesModel(TimeSeriesModelBase, TimeSeriesTunable, ABC):
736
722
  prediction_length=self.prediction_length, known_covariates_names=self.covariate_metadata.known_covariates
737
723
  )
738
724
  predictions = self.predict(past_data, known_covariates=known_covariates)
739
- return self._score_with_predictions(data=data, predictions=predictions, metric=metric)
725
+ return self._score_with_predictions(data=data, predictions=predictions)
740
726
 
741
727
  def score_and_cache_oof(
742
728
  self,
@@ -28,7 +28,6 @@ class TimeSeriesEnsembleSelection(EnsembleSelection):
28
28
  random_state: Optional[np.random.RandomState] = None,
29
29
  prediction_length: int = 1,
30
30
  target: str = "target",
31
- eval_metric_seasonal_period: int = 1,
32
31
  **kwargs,
33
32
  ):
34
33
  super().__init__(
@@ -43,7 +42,6 @@ class TimeSeriesEnsembleSelection(EnsembleSelection):
43
42
  )
44
43
  self.prediction_length = prediction_length
45
44
  self.target = target
46
- self.eval_metric_seasonal_period = eval_metric_seasonal_period
47
45
  self.metric: TimeSeriesScorer
48
46
 
49
47
  self.dummy_pred_per_window = []
@@ -79,6 +77,10 @@ class TimeSeriesEnsembleSelection(EnsembleSelection):
79
77
  self.scorer_per_window = []
80
78
  self.data_future_per_window = []
81
79
 
80
+ seasonal_period = self.metric.seasonal_period
81
+ if seasonal_period is None:
82
+ seasonal_period = get_seasonality(labels[0].freq)
83
+
82
84
  for window_idx, data in enumerate(labels):
83
85
  dummy_pred = copy.deepcopy(predictions[0][window_idx])
84
86
  # This should never happen; sanity check to make sure that all predictions have the same index
@@ -90,7 +92,7 @@ class TimeSeriesEnsembleSelection(EnsembleSelection):
90
92
  # Split the observed time series once to avoid repeated computations inside the evaluator
91
93
  data_past = data.slice_by_timestep(None, -self.prediction_length)
92
94
  data_future = data.slice_by_timestep(-self.prediction_length, None)
93
- scorer.save_past_metrics(data_past, target=self.target, seasonal_period=self.eval_metric_seasonal_period)
95
+ scorer.save_past_metrics(data_past, target=self.target, seasonal_period=seasonal_period)
94
96
  self.scorer_per_window.append(scorer)
95
97
  self.data_future_per_window.append(data_future)
96
98
 
@@ -122,7 +124,9 @@ class TimeSeriesEnsembleSelection(EnsembleSelection):
122
124
  dummy_pred[list(dummy_pred.columns)] = y_pred_proba[window_idx]
123
125
  # We use scorer.compute_metric instead of scorer.score to avoid repeated calls to scorer.save_past_metrics
124
126
  metric_value = self.scorer_per_window[window_idx].compute_metric(
125
- data_future, dummy_pred, target=self.target
127
+ data_future,
128
+ dummy_pred,
129
+ target=self.target,
126
130
  )
127
131
  total_score += metric.sign * metric_value
128
132
  avg_score = total_score / len(self.data_future_per_window)
@@ -162,14 +166,11 @@ class GreedyEnsemble(AbstractWeightedTimeSeriesEnsembleModel):
162
166
  model_scores: Optional[Dict[str, float]] = None,
163
167
  time_limit: Optional[float] = None,
164
168
  ):
165
- if self.eval_metric_seasonal_period is None:
166
- self.eval_metric_seasonal_period = get_seasonality(self.freq)
167
169
  ensemble_selection = TimeSeriesEnsembleSelection(
168
170
  ensemble_size=self.get_hyperparameters()["ensemble_size"],
169
171
  metric=self.eval_metric,
170
172
  prediction_length=self.prediction_length,
171
173
  target=self.target,
172
- eval_metric_seasonal_period=self.eval_metric_seasonal_period,
173
174
  )
174
175
  ensemble_selection.fit(
175
176
  predictions=list(predictions_per_window.values()),
@@ -183,7 +183,6 @@ def get_preset_models(
183
183
  prediction_length: int,
184
184
  path: str,
185
185
  eval_metric: Union[str, TimeSeriesScorer],
186
- eval_metric_seasonal_period: Optional[int],
187
186
  hyperparameters: Union[str, Dict, None],
188
187
  hyperparameter_tune: bool,
189
188
  covariate_metadata: CovariateMetadata,
@@ -260,7 +259,6 @@ def get_preset_models(
260
259
  freq=freq,
261
260
  prediction_length=prediction_length,
262
261
  eval_metric=eval_metric,
263
- eval_metric_seasonal_period=eval_metric_seasonal_period,
264
262
  covariate_metadata=covariate_metadata,
265
263
  hyperparameters=model_hps,
266
264
  **kwargs,
@@ -93,6 +93,14 @@ class TimeSeriesPredictor:
93
93
  eval_metric_seasonal_period : int, optional
94
94
  Seasonal period used to compute some evaluation metrics such as mean absolute scaled error (MASE). Defaults to
95
95
  ``None``, in which case the seasonal period is computed based on the data frequency.
96
+ horizon_weight : List[float], optional
97
+ Weight assigned to each time step in the forecast horizon when computing the `eval_metric`. If provided, this
98
+ must be a list with `prediction_length` non-negative values, where at least some values are greater than zero.
99
+ AutoGluon will automatically normalize the weights so that they sum up to `prediction_length`. By default, all
100
+ time steps in the forecast horizon have the same weight, which is equivalent to setting `horizon_weight = [1] * prediction_length`.
101
+
102
+ This parameter only affects model selection and ensemble construction; it has no effect on the loss function of
103
+ the individual forecasting models.
96
104
  known_covariates_names: List[str], optional
97
105
  Names of the covariates that are known in advance for all time steps in the forecast horizon. These are also
98
106
  known as dynamic features, exogenous variables, additional regressors or related time series. Examples of such
@@ -107,7 +115,7 @@ class TimeSeriesPredictor:
107
115
  List of increasing decimals that specifies which quantiles should be estimated when making distributional
108
116
  forecasts. Defaults to ``[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]``.
109
117
  path : str or pathlib.Path, optional
110
- Path to the directory where models and intermediate outputs will be saved. Defaults to a timestamped folder
118
+ Path to the local directory where models and intermediate outputs will be saved. Defaults to a timestamped folder
111
119
  ``AutogluonModels/ag-[TIMESTAMP]`` that will be created in the working directory.
112
120
  verbosity : int, default = 2
113
121
  Verbosity levels range from 0 to 4 and control how much information is printed to stdout. Higher levels
@@ -144,6 +152,7 @@ class TimeSeriesPredictor:
144
152
  freq: Optional[str] = None,
145
153
  eval_metric: Union[str, TimeSeriesScorer, None] = None,
146
154
  eval_metric_seasonal_period: Optional[int] = None,
155
+ horizon_weight: Optional[List[float]] = None,
147
156
  path: Optional[Union[str, Path]] = None,
148
157
  verbosity: int = 2,
149
158
  log_to_file: bool = True,
@@ -156,6 +165,11 @@ class TimeSeriesPredictor:
156
165
  self.verbosity = verbosity
157
166
  set_logger_verbosity(self.verbosity, logger=logger)
158
167
  self.path = setup_outputdir(path)
168
+ if self.path.lower().startswith("s3://"):
169
+ logger.warning(
170
+ "Warning: S3 paths are not supported for the `path` argument in TimeSeriesPredictor. "
171
+ "Use a local path and upload the trained predictor to S3 manually if needed"
172
+ )
159
173
  self._setup_log_to_file(log_to_file=log_to_file, log_file_path=log_file_path)
160
174
 
161
175
  self.cache_predictions = cache_predictions
@@ -187,15 +201,18 @@ class TimeSeriesPredictor:
187
201
  if std_freq != str(self.freq):
188
202
  logger.info(f"Frequency '{self.freq}' stored as '{std_freq}'")
189
203
  self.freq = std_freq
190
- self.eval_metric = check_get_evaluation_metric(eval_metric)
191
- self.eval_metric_seasonal_period = eval_metric_seasonal_period
204
+ self.eval_metric: TimeSeriesScorer = check_get_evaluation_metric(
205
+ eval_metric,
206
+ prediction_length=prediction_length,
207
+ seasonal_period=eval_metric_seasonal_period,
208
+ horizon_weight=horizon_weight,
209
+ )
192
210
  if quantile_levels is None:
193
211
  quantile_levels = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
194
212
  self.quantile_levels = sorted(quantile_levels)
195
213
  self._learner: TimeSeriesLearner = self._learner_type(
196
214
  path_context=self.path,
197
- eval_metric=eval_metric,
198
- eval_metric_seasonal_period=eval_metric_seasonal_period,
215
+ eval_metric=self.eval_metric,
199
216
  target=self.target,
200
217
  known_covariates_names=self.known_covariates_names,
201
218
  prediction_length=self.prediction_length,
@@ -494,33 +511,22 @@ class TimeSeriesPredictor:
494
511
 
495
512
  Available presets:
496
513
 
497
- - ``"fast_training"``: fit simple statistical models (``ETS``, ``Theta``, ``Naive``, ``SeasonalNaive``) + fast tree-based models ``RecursiveTabular``
498
- and ``DirectTabular``. These models are fast to train but may not be very accurate.
499
- - ``"medium_quality"``: all models mentioned above + deep learning model ``TemporalFusionTransformer`` + Chronos-Bolt (small). Produces good forecasts with reasonable training time.
500
- - ``"high_quality"``: All ML models available in AutoGluon + additional statistical models (``NPTS``, ``AutoETS``,
501
- ``DynamicOptimizedTheta``). Much more accurate than ``medium_quality``, but takes longer to train.
514
+ - ``"fast_training"``: Simple statistical and tree-based ML models. These models are fast to train but may not be very accurate.
515
+ - ``"medium_quality"``: Same models as above, plus deep learning models ``TemporalFusionTransformer`` and Chronos-Bolt (small). Produces good forecasts with reasonable training time.
516
+ - ``"high_quality"``: A mix of multiple DL, ML and statistical forecasting models available in AutoGluon that offers the best forecast accuracy. Much more accurate than ``medium_quality``, but takes longer to train.
502
517
  - ``"best_quality"``: Same models as in ``"high_quality"``, but performs validation with multiple backtests. Usually better than ``high_quality``, but takes even longer to train.
503
518
 
504
- Available presets with the new, faster `Chronos-Bolt <https://github.com/amazon-science/chronos-forecasting>`_ model:
519
+ Available presets with the `Chronos-Bolt <https://github.com/amazon-science/chronos-forecasting>`_ model:
505
520
 
506
521
  - ``"bolt_{model_size}"``: where model size is one of ``tiny,mini,small,base``. Uses the Chronos-Bolt pretrained model for zero-shot forecasting.
507
522
  See the documentation for ``ChronosModel`` or see `Hugging Face <https://huggingface.co/collections/amazon/chronos-models-65f1791d630a8d57cb718444>`_ for more information.
508
523
 
509
- Available presets with the original `Chronos <https://github.com/amazon-science/chronos-forecasting>`_ model.
510
- Note that as of v1.2 we recommend using the new, faster Chronos-Bolt models instead of the original Chronos models.
524
+ Exact definitions of these presets can be found in the source code
525
+ [`1 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/configs/presets_configs.py>`_,
526
+ `2 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/models/presets.py>`_].
511
527
 
512
- - ``"chronos_{model_size}"``: where model size is one of ``tiny,mini,small,base,large``. Uses the Chronos pretrained model for zero-shot forecasting.
513
- See the documentation for ``ChronosModel`` or see `Hugging Face <https://huggingface.co/collections/amazon/chronos-models-65f1791d630a8d57cb718444>`_ for more information.
514
- Note that a GPU is required for model sizes ``small``, ``base`` and ``large``.
515
- - ``"chronos"``: alias for ``"chronos_small"``.
516
- - ``"chronos_ensemble"``: builds an ensemble of seasonal naive, tree-based and deep learning models with fast inference
517
- and ``"chronos_small"``.
518
- - ``"chronos_large_ensemble"``: builds an ensemble of seasonal naive, tree-based and deep learning models
519
- with fast inference and ``"chronos_large"``.
520
-
521
- Details for these presets can be found in ``autogluon/timeseries/configs/presets_configs.py``. If not
522
- provided, user-provided values for ``hyperparameters`` and ``hyperparameter_tune_kwargs`` will be used
523
- (defaulting to their default values specified below).
528
+ If no `presets` are selected, user-provided values for `hyperparameters` will be used (defaulting to their
529
+ default values specified below).
524
530
  hyperparameters : str or dict, optional
525
531
  Determines what models are trained and what hyperparameters are used by each model.
526
532
 
@@ -684,7 +690,8 @@ class TimeSeriesPredictor:
684
690
  target=self.target,
685
691
  known_covariates_names=self.known_covariates_names,
686
692
  eval_metric=self.eval_metric,
687
- eval_metric_seasonal_period=self.eval_metric_seasonal_period,
693
+ eval_metric_seasonal_period=self.eval_metric.seasonal_period,
694
+ horizon_weight=self.eval_metric.horizon_weight,
688
695
  quantile_levels=self.quantile_levels,
689
696
  freq=self.freq,
690
697
  time_limit=time_limit,
@@ -1500,7 +1507,8 @@ class TimeSeriesPredictor:
1500
1507
  target=self.target,
1501
1508
  prediction_length=self.prediction_length,
1502
1509
  eval_metric=self.eval_metric.name,
1503
- eval_metric_seasonal_period=self.eval_metric_seasonal_period,
1510
+ eval_metric_seasonal_period=self.eval_metric.seasonal_period,
1511
+ horizon_weight=self.eval_metric.horizon_weight,
1504
1512
  quantile_levels=self.quantile_levels,
1505
1513
  )
1506
1514
  return simulation_dict
@@ -46,7 +46,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
46
46
  path: str,
47
47
  prediction_length: int = 1,
48
48
  eval_metric: Union[str, TimeSeriesScorer, None] = None,
49
- eval_metric_seasonal_period: Optional[int] = None,
50
49
  save_data: bool = True,
51
50
  skip_model_selection: bool = False,
52
51
  enable_ensemble: bool = True,
@@ -86,8 +85,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
86
85
  #: self.refit_single_full() and self.refit_full().
87
86
  self.model_refit_map = {}
88
87
 
89
- self.eval_metric: TimeSeriesScorer = check_get_evaluation_metric(eval_metric)
90
- self.eval_metric_seasonal_period = eval_metric_seasonal_period
88
+ self.eval_metric = check_get_evaluation_metric(eval_metric, prediction_length=prediction_length)
91
89
  if val_splitter is None:
92
90
  val_splitter = ExpandingWindowSplitter(prediction_length=self.prediction_length)
93
91
  assert isinstance(val_splitter, AbstractWindowSplitter), "val_splitter must be of type AbstractWindowSplitter"
@@ -577,7 +575,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
577
575
  ensemble = self.ensemble_model_type(
578
576
  name=self._get_ensemble_model_name(),
579
577
  eval_metric=self.eval_metric,
580
- eval_metric_seasonal_period=self.eval_metric_seasonal_period,
581
578
  target=self.target,
582
579
  prediction_length=self.prediction_length,
583
580
  path=self.path,
@@ -791,6 +788,17 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
791
788
  raise ValueError(f"Model {model_name} failed to predict. Please check the model's logs.")
792
789
  return predictions
793
790
 
791
+ def _get_eval_metric(self, metric: Union[str, TimeSeriesScorer, None]) -> TimeSeriesScorer:
792
+ if metric is None:
793
+ return self.eval_metric
794
+ else:
795
+ return check_get_evaluation_metric(
796
+ metric,
797
+ prediction_length=self.prediction_length,
798
+ seasonal_period=self.eval_metric.seasonal_period,
799
+ horizon_weight=self.eval_metric.horizon_weight,
800
+ )
801
+
794
802
  def _score_with_predictions(
795
803
  self,
796
804
  data: TimeSeriesDataFrame,
@@ -798,13 +806,11 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
798
806
  metric: Union[str, TimeSeriesScorer, None] = None,
799
807
  ) -> float:
800
808
  """Compute the score measuring how well the predictions align with the data."""
801
- eval_metric = self.eval_metric if metric is None else check_get_evaluation_metric(metric)
802
- return eval_metric.score(
809
+ return self._get_eval_metric(metric).score(
803
810
  data=data,
804
811
  predictions=predictions,
805
812
  prediction_length=self.prediction_length,
806
813
  target=self.target,
807
- seasonal_period=self.eval_metric_seasonal_period,
808
814
  )
809
815
 
810
816
  def score(
@@ -814,7 +820,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
814
820
  metric: Union[str, TimeSeriesScorer, None] = None,
815
821
  use_cache: bool = True,
816
822
  ) -> float:
817
- eval_metric = self.eval_metric if metric is None else check_get_evaluation_metric(metric)
823
+ eval_metric = self._get_eval_metric(metric)
818
824
  scores_dict = self.evaluate(data=data, model=model, metrics=[eval_metric], use_cache=use_cache)
819
825
  return scores_dict[eval_metric.name]
820
826
 
@@ -833,7 +839,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
833
839
  metrics_ = [metrics] if not isinstance(metrics, list) else metrics
834
840
  scores_dict = {}
835
841
  for metric in metrics_:
836
- eval_metric = self.eval_metric if metric is None else check_get_evaluation_metric(metric)
842
+ eval_metric = self._get_eval_metric(metric)
837
843
  scores_dict[eval_metric.name] = self._score_with_predictions(
838
844
  data=data, predictions=predictions, metric=eval_metric
839
845
  )
@@ -855,7 +861,7 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
855
861
  confidence_level: float = 0.99,
856
862
  ) -> pd.DataFrame:
857
863
  assert method in ["naive", "permutation"], f"Invalid feature importance method {method}."
858
- metric = check_get_evaluation_metric(metric) if metric is not None else self.eval_metric
864
+ eval_metric = self._get_eval_metric(metric)
859
865
 
860
866
  logger.info("Computing feature importance")
861
867
 
@@ -906,7 +912,9 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
906
912
  else:
907
913
  data_sample = data
908
914
 
909
- base_score = self.evaluate(data=data_sample, model=model, metrics=metric, use_cache=False)[metric.name]
915
+ base_score = self.evaluate(data=data_sample, model=model, metrics=eval_metric, use_cache=False)[
916
+ eval_metric.name
917
+ ]
910
918
 
911
919
  for feature in features:
912
920
  # override importance for unused features
@@ -914,9 +922,9 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
914
922
  continue
915
923
  else:
916
924
  data_sample_replaced = importance_transform.transform(data_sample, feature_name=feature)
917
- score = self.evaluate(data=data_sample_replaced, model=model, metrics=metric, use_cache=False)[
918
- metric.name
919
- ]
925
+ score = self.evaluate(
926
+ data=data_sample_replaced, model=model, metrics=eval_metric, use_cache=False
927
+ )[eval_metric.name]
920
928
 
921
929
  importance = base_score - score
922
930
  if relative_scores:
@@ -1266,7 +1274,6 @@ class TimeSeriesTrainer(AbstractTrainer[TimeSeriesModelBase]):
1266
1274
  return get_preset_models(
1267
1275
  path=self.path,
1268
1276
  eval_metric=self.eval_metric,
1269
- eval_metric_seasonal_period=self.eval_metric_seasonal_period,
1270
1277
  prediction_length=self.prediction_length,
1271
1278
  freq=freq,
1272
1279
  hyperparameters=hyperparameters,
@@ -1,4 +1,4 @@
1
1
  """This is the autogluon version file."""
2
2
 
3
- __version__ = "1.2.1b20250425"
3
+ __version__ = "1.2.1b20250427"
4
4
  __lite__ = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: autogluon.timeseries
3
- Version: 1.2.1b20250425
3
+ Version: 1.2.1b20250427
4
4
  Summary: Fast and Accurate ML in 3 Lines of Code
5
5
  Home-page: https://github.com/autogluon/autogluon
6
6
  Author: AutoGluon Community
@@ -55,10 +55,10 @@ Requires-Dist: fugue>=0.9.0
55
55
  Requires-Dist: tqdm<5,>=4.38
56
56
  Requires-Dist: orjson~=3.9
57
57
  Requires-Dist: tensorboard<3,>=2.9
58
- Requires-Dist: autogluon.core[raytune]==1.2.1b20250425
59
- Requires-Dist: autogluon.common==1.2.1b20250425
60
- Requires-Dist: autogluon.features==1.2.1b20250425
61
- Requires-Dist: autogluon.tabular[catboost,lightgbm,xgboost]==1.2.1b20250425
58
+ Requires-Dist: autogluon.core[raytune]==1.2.1b20250427
59
+ Requires-Dist: autogluon.common==1.2.1b20250427
60
+ Requires-Dist: autogluon.features==1.2.1b20250427
61
+ Requires-Dist: autogluon.tabular[catboost,lightgbm,xgboost]==1.2.1b20250427
62
62
  Provides-Extra: all
63
63
  Provides-Extra: chronos-onnx
64
64
  Requires-Dist: optimum[onnxruntime]<1.23,>=1.17; extra == "chronos-onnx"
@@ -1,25 +1,25 @@
1
- autogluon.timeseries-1.2.1b20250425-py3.9-nspkg.pth,sha256=cQGwpuGPqg1GXscIwt-7PmME1OnSpD-7ixkikJ31WAY,554
1
+ autogluon.timeseries-1.2.1b20250427-py3.9-nspkg.pth,sha256=cQGwpuGPqg1GXscIwt-7PmME1OnSpD-7ixkikJ31WAY,554
2
2
  autogluon/timeseries/__init__.py,sha256=_CrLLc1fkjen7UzWoO0Os8WZoHOgvZbHKy46I8v_4k4,304
3
3
  autogluon/timeseries/evaluator.py,sha256=l642tYfTHsl8WVIq_vV6qhgAFVFr9UuZD7gLra3A_Kc,250
4
- autogluon/timeseries/learner.py,sha256=7dqSHKCIX2osjv9cmWWLwaGvdrPvla0HTnsR75bdenY,14112
5
- autogluon/timeseries/predictor.py,sha256=Ur5TV0idIssnUzVdZcVsnrNWAmX6JGxbLZZnpT8EGmQ,87980
4
+ autogluon/timeseries/learner.py,sha256=pIn4YSOk0aqCWyBpIlwnAsFnG4h7PLXk8guFH3wFS-w,13923
5
+ autogluon/timeseries/predictor.py,sha256=Dz-LJVU5sjlFCOqHTeYPt77DuGavdAXB0DkclpM55rY,88173
6
6
  autogluon/timeseries/regressor.py,sha256=xw5VPrXS-NQ_Ts4ppDjoNV0TdqUYjW4VINUtb_BZdiI,11868
7
7
  autogluon/timeseries/splitter.py,sha256=yzPca9p2bWV-_VJAptUyyzQsxu-uixAdpMoGQtDzMD4,3205
8
- autogluon/timeseries/trainer.py,sha256=zrO3ARI_h14pYy7_GEGO7nqd9rONiDmx5hhCRMyzwls,58115
9
- autogluon/timeseries/version.py,sha256=zTyiK3ClhRyp8BZjIMLB2n9YsA7GaR9qnPDLREdyRQs,91
8
+ autogluon/timeseries/trainer.py,sha256=57OyqlTAVahDPxF5GmdDljIr1RbjnIUL_d5TbrkTJ2c,58075
9
+ autogluon/timeseries/version.py,sha256=MYp1RtN1gV71F2mBvyr6Hhxxc5fa6p7SICDe0s2XoUY,91
10
10
  autogluon/timeseries/configs/__init__.py,sha256=BTtHIPCYeGjqgOcvqb8qPD4VNX-ICKOg6wnkew1cPOE,98
11
11
  autogluon/timeseries/configs/presets_configs.py,sha256=cLat8ecLlWrI-SC5KLBDCX2SbVXaucemy2pjxJAtSY0,2543
12
12
  autogluon/timeseries/dataset/__init__.py,sha256=UvnhAN5tjgxXTHoZMQDy64YMDj4Xxa68yY7NP4vAw0o,81
13
13
  autogluon/timeseries/dataset/ts_dataframe.py,sha256=W3VE65lFyWmqMQ3XHN4Jhrqf_dO1EOLneNL2QDvVxeY,48120
14
- autogluon/timeseries/metrics/__init__.py,sha256=dJCrZ2cHwqhqNctwQjwG-FHgGUmzIFT-D0z72f4RAVM,2104
15
- autogluon/timeseries/metrics/abstract.py,sha256=CHUZB6xt9oF9yijSOjgGtjLuKo2X0mT6dQDuwg4ZzpU,8192
16
- autogluon/timeseries/metrics/point.py,sha256=2nlieQcPBCI9hXMT3v0Oe802ykZDuzvEtDpunzt0IVA,15785
17
- autogluon/timeseries/metrics/quantile.py,sha256=wvFeDMvRf1mFurhvVr_7g13Kg-hKIRoW4y9t2no_e7A,3969
14
+ autogluon/timeseries/metrics/__init__.py,sha256=6v6aFjC_zbSTcSjU2hr0_LQf3MhCArMgKow3IfU2g-M,3477
15
+ autogluon/timeseries/metrics/abstract.py,sha256=Z4ThftPBgLl9AgPQoJWcjHpVaOWWr6X3s3WwpvpAUOg,11818
16
+ autogluon/timeseries/metrics/point.py,sha256=xllyGh11otbmUVHyIaceROPR3qyllWPQ9xlSmIGI3EI,18306
17
+ autogluon/timeseries/metrics/quantile.py,sha256=vhmETtjPsIfVlvtILNAT6F2PtIDNPrOroy-U1FQbgw8,4632
18
18
  autogluon/timeseries/metrics/utils.py,sha256=HuDe1BNe8yJU4f_DKM913nNrUueoRaw6zhxm1-S20s0,910
19
19
  autogluon/timeseries/models/__init__.py,sha256=MYD9JJ-wUDE5B6jW6E6LU2eXQ6vflfQBvqQJkdzJa3A,1189
20
- autogluon/timeseries/models/presets.py,sha256=BdSTW91-flgqhVNuZIvqEf7wUj1iB6BPger4tJaoAZQ,12322
20
+ autogluon/timeseries/models/presets.py,sha256=HEACiRpnY6dcff7W44gnM0x1KRgr2bNf5D6zcaHgHxo,12201
21
21
  autogluon/timeseries/models/abstract/__init__.py,sha256=Htfkjjc3vo92RvyM8rIlQ0PLWt3jcrCKZES07UvCMV0,146
22
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py,sha256=Mj0bx45A2zy9Vzhcd7xjct3KUJGnMKLTozTMvtdsViw,33059
22
+ autogluon/timeseries/models/abstract/abstract_timeseries_model.py,sha256=94TG7tsdfENP41QATr4IeMofaFt8ySjrrrH4MxZZ3Xc,32104
23
23
  autogluon/timeseries/models/abstract/model_trial.py,sha256=ENPg_7nsdxIvaNM0o0UShZ3x8jFlRmwRc5m0fGPC0TM,3720
24
24
  autogluon/timeseries/models/abstract/tunable.py,sha256=SFl4vjkb6BfFFaRPVdftnnLYlIyCThutLHxiiAlV6tY,7168
25
25
  autogluon/timeseries/models/autogluon_tabular/__init__.py,sha256=r9i6jWcyeLHYClkcMSKRVsfrkBUMxpDrTATNTBc_qgQ,136
@@ -36,7 +36,7 @@ autogluon/timeseries/models/chronos/pipeline/utils.py,sha256=dtDX5Pyu95bGv7qmqgf
36
36
  autogluon/timeseries/models/ensemble/__init__.py,sha256=_BivnZaOWJiIvu93IQy0mrLdCZKT2NHHSqkf31hwF2s,158
37
37
  autogluon/timeseries/models/ensemble/abstract.py,sha256=ie-BKD4JIkQQoKqtf6sYI5Aix7dSgywFsSdeGPxoElk,5821
38
38
  autogluon/timeseries/models/ensemble/basic.py,sha256=BRPWg_Wgfb87iInFSoTRE75BRHaovRR5HFRvzxET_wU,3423
39
- autogluon/timeseries/models/ensemble/greedy.py,sha256=2MVLTPvJ9Khuqri1gwQlo0RmKFeWK4qFkEcLH1Dh41E,7362
39
+ autogluon/timeseries/models/ensemble/greedy.py,sha256=oW2d3-cce1Xck3NOtTh_8uHnjmc-2hGntPGoJQHUibE,7213
40
40
  autogluon/timeseries/models/gluonts/__init__.py,sha256=asC1PTj4j9xMbilvk1IT1julnpeoKbv5ZNuAR6-DFgA,361
41
41
  autogluon/timeseries/models/gluonts/abstract_gluonts.py,sha256=35T8rty6sPGiaSFNpiVNmeseo1_qpn664UcWo92W5eI,32906
42
42
  autogluon/timeseries/models/gluonts/torch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -60,11 +60,11 @@ autogluon/timeseries/utils/datetime/base.py,sha256=3NdsH3NDq4cVAOSoy3XpaNixyNlbj
60
60
  autogluon/timeseries/utils/datetime/lags.py,sha256=gQDk5_zmsY5DUWDUpSaCKYkQ9nHKKY-LsywJQRAoYSk,5988
61
61
  autogluon/timeseries/utils/datetime/seasonality.py,sha256=YK_2k8hvYIMW-sJPnjGWRtCnvIOthwA2hATB3nwVoD4,834
62
62
  autogluon/timeseries/utils/datetime/time_features.py,sha256=MjLi3zQ00uWWJtXH9oGX2GJkTbvjdSiuabSa4kcVuxE,2672
63
- autogluon.timeseries-1.2.1b20250425.dist-info/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
64
- autogluon.timeseries-1.2.1b20250425.dist-info/METADATA,sha256=FQJvTO-OW1GKRFf4RMRuyNUgzGJscd3nXb7it95OrGM,12737
65
- autogluon.timeseries-1.2.1b20250425.dist-info/NOTICE,sha256=7nPQuj8Kp-uXsU0S5so3-2dNU5EctS5hDXvvzzehd7E,114
66
- autogluon.timeseries-1.2.1b20250425.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
67
- autogluon.timeseries-1.2.1b20250425.dist-info/namespace_packages.txt,sha256=giERA4R78OkJf2ijn5slgjURlhRPzfLr7waIcGkzYAo,10
68
- autogluon.timeseries-1.2.1b20250425.dist-info/top_level.txt,sha256=giERA4R78OkJf2ijn5slgjURlhRPzfLr7waIcGkzYAo,10
69
- autogluon.timeseries-1.2.1b20250425.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
70
- autogluon.timeseries-1.2.1b20250425.dist-info/RECORD,,
63
+ autogluon.timeseries-1.2.1b20250427.dist-info/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
64
+ autogluon.timeseries-1.2.1b20250427.dist-info/METADATA,sha256=YhebfebGJqsOMuBmB134Tg3ULwwLv-aIAEJmwIwBQxE,12737
65
+ autogluon.timeseries-1.2.1b20250427.dist-info/NOTICE,sha256=7nPQuj8Kp-uXsU0S5so3-2dNU5EctS5hDXvvzzehd7E,114
66
+ autogluon.timeseries-1.2.1b20250427.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
67
+ autogluon.timeseries-1.2.1b20250427.dist-info/namespace_packages.txt,sha256=giERA4R78OkJf2ijn5slgjURlhRPzfLr7waIcGkzYAo,10
68
+ autogluon.timeseries-1.2.1b20250427.dist-info/top_level.txt,sha256=giERA4R78OkJf2ijn5slgjURlhRPzfLr7waIcGkzYAo,10
69
+ autogluon.timeseries-1.2.1b20250427.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
70
+ autogluon.timeseries-1.2.1b20250427.dist-info/RECORD,,