autogluon.timeseries 1.4.1b20251115__py3-none-any.whl → 1.5.0b20251221__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.

Potentially problematic release.


This version of autogluon.timeseries might be problematic. Click here for more details.

Files changed (82) hide show
  1. autogluon/timeseries/configs/hyperparameter_presets.py +13 -28
  2. autogluon/timeseries/configs/predictor_presets.py +23 -39
  3. autogluon/timeseries/dataset/ts_dataframe.py +32 -34
  4. autogluon/timeseries/learner.py +67 -33
  5. autogluon/timeseries/metrics/__init__.py +4 -4
  6. autogluon/timeseries/metrics/abstract.py +8 -8
  7. autogluon/timeseries/metrics/point.py +9 -9
  8. autogluon/timeseries/metrics/quantile.py +4 -4
  9. autogluon/timeseries/models/__init__.py +2 -1
  10. autogluon/timeseries/models/abstract/abstract_timeseries_model.py +52 -50
  11. autogluon/timeseries/models/abstract/model_trial.py +2 -1
  12. autogluon/timeseries/models/abstract/tunable.py +8 -8
  13. autogluon/timeseries/models/autogluon_tabular/mlforecast.py +30 -26
  14. autogluon/timeseries/models/autogluon_tabular/per_step.py +13 -11
  15. autogluon/timeseries/models/autogluon_tabular/transforms.py +2 -2
  16. autogluon/timeseries/models/chronos/__init__.py +2 -1
  17. autogluon/timeseries/models/chronos/chronos2.py +395 -0
  18. autogluon/timeseries/models/chronos/model.py +30 -25
  19. autogluon/timeseries/models/chronos/utils.py +5 -5
  20. autogluon/timeseries/models/ensemble/__init__.py +17 -10
  21. autogluon/timeseries/models/ensemble/abstract.py +13 -9
  22. autogluon/timeseries/models/ensemble/array_based/__init__.py +2 -2
  23. autogluon/timeseries/models/ensemble/array_based/abstract.py +24 -31
  24. autogluon/timeseries/models/ensemble/array_based/models.py +146 -11
  25. autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +2 -0
  26. autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +6 -5
  27. autogluon/timeseries/models/ensemble/array_based/regressor/linear_stacker.py +186 -0
  28. autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +44 -83
  29. autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +21 -55
  30. autogluon/timeseries/models/ensemble/ensemble_selection.py +167 -0
  31. autogluon/timeseries/models/ensemble/per_item_greedy.py +172 -0
  32. autogluon/timeseries/models/ensemble/weighted/abstract.py +7 -3
  33. autogluon/timeseries/models/ensemble/weighted/basic.py +26 -13
  34. autogluon/timeseries/models/ensemble/weighted/greedy.py +21 -144
  35. autogluon/timeseries/models/gluonts/abstract.py +30 -29
  36. autogluon/timeseries/models/gluonts/dataset.py +9 -9
  37. autogluon/timeseries/models/gluonts/models.py +0 -7
  38. autogluon/timeseries/models/local/__init__.py +0 -7
  39. autogluon/timeseries/models/local/abstract_local_model.py +13 -16
  40. autogluon/timeseries/models/local/naive.py +2 -2
  41. autogluon/timeseries/models/local/npts.py +7 -1
  42. autogluon/timeseries/models/local/statsforecast.py +13 -13
  43. autogluon/timeseries/models/multi_window/multi_window_model.py +38 -23
  44. autogluon/timeseries/models/registry.py +3 -4
  45. autogluon/timeseries/models/toto/_internal/backbone/attention.py +3 -4
  46. autogluon/timeseries/models/toto/_internal/backbone/backbone.py +6 -6
  47. autogluon/timeseries/models/toto/_internal/backbone/rope.py +4 -9
  48. autogluon/timeseries/models/toto/_internal/backbone/rotary_embedding_torch.py +342 -0
  49. autogluon/timeseries/models/toto/_internal/backbone/scaler.py +2 -3
  50. autogluon/timeseries/models/toto/_internal/backbone/transformer.py +10 -10
  51. autogluon/timeseries/models/toto/_internal/dataset.py +2 -2
  52. autogluon/timeseries/models/toto/_internal/forecaster.py +8 -8
  53. autogluon/timeseries/models/toto/dataloader.py +4 -4
  54. autogluon/timeseries/models/toto/hf_pretrained_model.py +97 -16
  55. autogluon/timeseries/models/toto/model.py +30 -17
  56. autogluon/timeseries/predictor.py +531 -136
  57. autogluon/timeseries/regressor.py +18 -23
  58. autogluon/timeseries/splitter.py +2 -2
  59. autogluon/timeseries/trainer/ensemble_composer.py +323 -129
  60. autogluon/timeseries/trainer/model_set_builder.py +9 -9
  61. autogluon/timeseries/trainer/prediction_cache.py +16 -16
  62. autogluon/timeseries/trainer/trainer.py +235 -145
  63. autogluon/timeseries/trainer/utils.py +3 -4
  64. autogluon/timeseries/transforms/covariate_scaler.py +7 -7
  65. autogluon/timeseries/transforms/target_scaler.py +8 -8
  66. autogluon/timeseries/utils/constants.py +10 -0
  67. autogluon/timeseries/utils/datetime/lags.py +1 -3
  68. autogluon/timeseries/utils/datetime/seasonality.py +1 -3
  69. autogluon/timeseries/utils/features.py +22 -9
  70. autogluon/timeseries/utils/forecast.py +1 -2
  71. autogluon/timeseries/utils/timer.py +173 -0
  72. autogluon/timeseries/version.py +1 -1
  73. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/METADATA +23 -21
  74. autogluon_timeseries-1.5.0b20251221.dist-info/RECORD +103 -0
  75. autogluon_timeseries-1.4.1b20251115.dist-info/RECORD +0 -96
  76. /autogluon.timeseries-1.4.1b20251115-py3.9-nspkg.pth → /autogluon.timeseries-1.5.0b20251221-py3.11-nspkg.pth +0 -0
  77. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/WHEEL +0 -0
  78. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/licenses/LICENSE +0 -0
  79. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/licenses/NOTICE +0 -0
  80. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/namespace_packages.txt +0 -0
  81. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/top_level.txt +0 -0
  82. {autogluon_timeseries-1.4.1b20251115.dist-info → autogluon_timeseries-1.5.0b20251221.dist-info}/zip-safe +0 -0
@@ -5,11 +5,12 @@ import os
5
5
  import pprint
6
6
  import time
7
7
  from pathlib import Path
8
- from typing import Any, Literal, Optional, Type, Union, cast
8
+ from typing import Any, Literal, Type, cast, overload
9
9
 
10
10
  import numpy as np
11
11
  import pandas as pd
12
12
 
13
+ from autogluon.common.utils.decorators import apply_presets
13
14
  from autogluon.common.utils.log_utils import (
14
15
  add_log_to_file,
15
16
  set_logger_verbosity,
@@ -17,7 +18,6 @@ from autogluon.common.utils.log_utils import (
17
18
  )
18
19
  from autogluon.common.utils.system_info import get_ag_system_info
19
20
  from autogluon.common.utils.utils import check_saved_predictor_version, setup_outputdir
20
- from autogluon.core.utils.decorators import apply_presets
21
21
  from autogluon.core.utils.loaders import load_pkl, load_str
22
22
  from autogluon.core.utils.savers import save_pkl, save_str
23
23
  from autogluon.timeseries import __version__ as current_ag_version
@@ -66,7 +66,7 @@ class TimeSeriesPredictor:
66
66
 
67
67
  If ``freq`` is provided when creating the predictor, all data passed to the predictor will be automatically
68
68
  resampled at this frequency.
69
- eval_metric : Union[str, TimeSeriesScorer], default = "WQL"
69
+ eval_metric : str | TimeSeriesScorer, default = "WQL"
70
70
  Metric by which predictions will be ultimately evaluated on future test data. AutoGluon tunes hyperparameters
71
71
  in order to improve this metric on validation data, and ranks models (on validation data) according to this
72
72
  metric.
@@ -124,7 +124,7 @@ class TimeSeriesPredictor:
124
124
  debug messages from AutoGluon and all logging in dependencies (GluonTS, PyTorch Lightning, AutoGluon-Tabular, etc.)
125
125
  log_to_file: bool, default = True
126
126
  Whether to save the logs into a file for later reference
127
- log_file_path: Union[str, Path], default = "auto"
127
+ log_file_path: str | Path, default = "auto"
128
128
  File path to save the logs.
129
129
  If auto, logs will be saved under ``predictor_path/logs/predictor_log.txt``.
130
130
  Will be ignored if ``log_to_file`` is set to False
@@ -145,20 +145,20 @@ class TimeSeriesPredictor:
145
145
 
146
146
  def __init__(
147
147
  self,
148
- target: Optional[str] = None,
149
- known_covariates_names: Optional[list[str]] = None,
148
+ target: str | None = None,
149
+ known_covariates_names: list[str] | None = None,
150
150
  prediction_length: int = 1,
151
- freq: Optional[str] = None,
152
- eval_metric: Union[str, TimeSeriesScorer, None] = None,
153
- eval_metric_seasonal_period: Optional[int] = None,
154
- horizon_weight: Optional[list[float]] = None,
155
- path: Optional[Union[str, Path]] = None,
151
+ freq: str | None = None,
152
+ eval_metric: str | TimeSeriesScorer | None = None,
153
+ eval_metric_seasonal_period: int | None = None,
154
+ horizon_weight: list[float] | None = None,
155
+ path: str | Path | None = None,
156
156
  verbosity: int = 2,
157
157
  log_to_file: bool = True,
158
- log_file_path: Union[str, Path] = "auto",
159
- quantile_levels: Optional[list[float]] = None,
158
+ log_file_path: str | Path = "auto",
159
+ quantile_levels: list[float] | None = None,
160
160
  cache_predictions: bool = True,
161
- label: Optional[str] = None,
161
+ label: str | None = None,
162
162
  **kwargs,
163
163
  ):
164
164
  self.verbosity = verbosity
@@ -228,7 +228,16 @@ class TimeSeriesPredictor:
228
228
  def _trainer(self) -> TimeSeriesTrainer:
229
229
  return self._learner.load_trainer() # noqa
230
230
 
231
- def _setup_log_to_file(self, log_to_file: bool, log_file_path: Union[str, Path]) -> None:
231
+ @property
232
+ def is_fit(self) -> bool:
233
+ return self._learner.is_fit
234
+
235
+ def _assert_is_fit(self, method_name: str) -> None:
236
+ """Check if predictor is fit and raise AssertionError with informative message if not."""
237
+ if not self.is_fit:
238
+ raise AssertionError(f"Predictor is not fit. Call `.fit` before calling `.{method_name}`. ")
239
+
240
+ def _setup_log_to_file(self, log_to_file: bool, log_file_path: str | Path) -> None:
232
241
  if log_to_file:
233
242
  if log_file_path == "auto":
234
243
  log_file_path = os.path.join(self.path, "logs", self._predictor_log_file_name)
@@ -238,7 +247,7 @@ class TimeSeriesPredictor:
238
247
 
239
248
  def _to_data_frame(
240
249
  self,
241
- data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
250
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
242
251
  name: str = "data",
243
252
  ) -> TimeSeriesDataFrame:
244
253
  if isinstance(data, TimeSeriesDataFrame):
@@ -259,7 +268,7 @@ class TimeSeriesPredictor:
259
268
 
260
269
  def _check_and_prepare_data_frame(
261
270
  self,
262
- data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
271
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
263
272
  name: str = "data",
264
273
  ) -> TimeSeriesDataFrame:
265
274
  """Ensure that TimeSeriesDataFrame has a sorted index and a valid frequency.
@@ -268,7 +277,7 @@ class TimeSeriesPredictor:
268
277
 
269
278
  Parameters
270
279
  ----------
271
- data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]
280
+ data : TimeSeriesDataFrame | pd.DataFrame | Path | str
272
281
  Data as a dataframe or path to file storing the data.
273
282
  name : str
274
283
  Name of the data that will be used in log messages (e.g., 'train_data', 'tuning_data', or 'data').
@@ -311,7 +320,7 @@ class TimeSeriesPredictor:
311
320
  return df
312
321
 
313
322
  def _check_and_prepare_data_frame_for_evaluation(
314
- self, data: TimeSeriesDataFrame, cutoff: Optional[int] = None, name: str = "data"
323
+ self, data: TimeSeriesDataFrame, cutoff: int | None = None, name: str = "data"
315
324
  ) -> TimeSeriesDataFrame:
316
325
  """
317
326
  Make sure that provided evaluation data includes both historical and future time series values.
@@ -351,36 +360,10 @@ class TimeSeriesPredictor:
351
360
  f"Median time series length is {median_length:.0f} (min={min_length}, max={max_length}). "
352
361
  )
353
362
 
354
- def _reduce_num_val_windows_if_necessary(
355
- self,
356
- train_data: TimeSeriesDataFrame,
357
- original_num_val_windows: int,
358
- val_step_size: int,
359
- ) -> int:
360
- """Adjust num_val_windows based on the length of time series in train_data.
361
-
362
- Chooses num_val_windows such that TS with median length is long enough to perform num_val_windows validations
363
- (at least 1, at most `original_num_val_windows`).
364
-
365
- In other words, find largest `num_val_windows` that satisfies
366
- median_length >= min_train_length + prediction_length + (num_val_windows - 1) * val_step_size
367
- """
368
- median_length = train_data.num_timesteps_per_item().median()
369
- num_val_windows_for_median_ts = int(
370
- (median_length - self._min_train_length - self.prediction_length) // val_step_size + 1
371
- )
372
- new_num_val_windows = min(original_num_val_windows, max(1, num_val_windows_for_median_ts))
373
- if new_num_val_windows < original_num_val_windows:
374
- logger.warning(
375
- f"Time series in train_data are too short for chosen num_val_windows={original_num_val_windows}. "
376
- f"Reducing num_val_windows to {new_num_val_windows}."
377
- )
378
- return new_num_val_windows
379
-
380
363
  def _filter_useless_train_data(
381
364
  self,
382
365
  train_data: TimeSeriesDataFrame,
383
- num_val_windows: int,
366
+ num_val_windows: tuple[int, ...],
384
367
  val_step_size: int,
385
368
  ) -> TimeSeriesDataFrame:
386
369
  """Remove time series from train_data that either contain all NaNs or are too short for chosen settings.
@@ -391,7 +374,8 @@ class TimeSeriesPredictor:
391
374
  In other words, this method removes from train_data all time series with only NaN values or length less than
392
375
  min_train_length + prediction_length + (num_val_windows - 1) * val_step_size
393
376
  """
394
- min_length = self._min_train_length + self.prediction_length + (num_val_windows - 1) * val_step_size
377
+ total_num_val_windows = sum(num_val_windows)
378
+ min_length = self._min_train_length + self.prediction_length + (total_num_val_windows - 1) * val_step_size
395
379
  train_lengths = train_data.num_timesteps_per_item()
396
380
  too_short_items = train_lengths.index[train_lengths < min_length]
397
381
 
@@ -422,27 +406,28 @@ class TimeSeriesPredictor:
422
406
  @apply_presets(get_predictor_presets())
423
407
  def fit(
424
408
  self,
425
- train_data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
426
- tuning_data: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
427
- time_limit: Optional[int] = None,
428
- presets: Optional[str] = None,
429
- hyperparameters: Optional[Union[str, dict[Union[str, Type], Any]]] = None,
430
- hyperparameter_tune_kwargs: Optional[Union[str, dict]] = None,
431
- excluded_model_types: Optional[list[str]] = None,
432
- num_val_windows: int = 1,
433
- val_step_size: Optional[int] = None,
434
- refit_every_n_windows: Optional[int] = 1,
409
+ train_data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
410
+ tuning_data: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
411
+ time_limit: int | None = None,
412
+ presets: str | None = None,
413
+ hyperparameters: str | dict[str | Type, Any] | None = None,
414
+ hyperparameter_tune_kwargs: str | dict | None = None,
415
+ excluded_model_types: list[str] | None = None,
416
+ ensemble_hyperparameters: dict[str, Any] | list[dict[str, Any]] | None = None,
417
+ num_val_windows: int | tuple[int, ...] | Literal["auto"] = 1,
418
+ val_step_size: int | None = None,
419
+ refit_every_n_windows: int | None | Literal["auto"] = 1,
435
420
  refit_full: bool = False,
436
421
  enable_ensemble: bool = True,
437
422
  skip_model_selection: bool = False,
438
- random_seed: Optional[int] = 123,
439
- verbosity: Optional[int] = None,
423
+ random_seed: int | None = 123,
424
+ verbosity: int | None = None,
440
425
  ) -> "TimeSeriesPredictor":
441
426
  """Fit probabilistic forecasting models to the given time series dataset.
442
427
 
443
428
  Parameters
444
429
  ----------
445
- train_data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]
430
+ train_data : TimeSeriesDataFrame | pd.DataFrame | Path | str
446
431
  Training data in the :class:`~autogluon.timeseries.TimeSeriesDataFrame` format.
447
432
 
448
433
  Time series with length ``<= (num_val_windows + 1) * prediction_length`` will be ignored during training.
@@ -468,7 +453,7 @@ class TimeSeriesPredictor:
468
453
 
469
454
  If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
470
455
  If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
471
- tuning_data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str], optional
456
+ tuning_data : TimeSeriesDataFrame | pd.DataFrame | Path | str, optional
472
457
  Data reserved for model selection and hyperparameter tuning, rather than training individual models. Also
473
458
  used to compute the validation scores. Note that only the last ``prediction_length`` time steps of each
474
459
  time series are used for computing the validation score.
@@ -502,18 +487,23 @@ class TimeSeriesPredictor:
502
487
  Available presets:
503
488
 
504
489
  - ``"fast_training"``: Simple statistical and tree-based ML models. These models are fast to train but may not be very accurate.
505
- - ``"medium_quality"``: Same models as above, plus deep learning models ``TemporalFusionTransformer`` and Chronos-Bolt (small). Produces good forecasts with reasonable training time.
490
+ - ``"medium_quality"``: Same models as above, plus deep learning models ``TemporalFusionTransformer`` and Chronos-2 (small). Produces good forecasts with reasonable training time.
506
491
  - ``"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.
507
492
  - ``"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.
508
493
 
509
- Available presets with the `Chronos-Bolt <https://github.com/amazon-science/chronos-forecasting>`_ model:
494
+ Available presets with the `Chronos-2 and Chronos-Bolt <https://github.com/amazon-science/chronos-forecasting>`_ models:
510
495
 
496
+ - ``"chronos2"``: `Chronos-2 <https://huggingface.co/amazon/chronos-2>`_ base model for zero-shot forecasting.
497
+ - ``"chronos2_small"``: Smaller Chronos-2 model for faster zero-shot forecasting with lower memory footprint.
498
+ - ``"chronos2_ensemble"``: Ensemble combining zero-shot Chronos-2 base model with fine-tuned Chronos-2 small model for improved accuracy.
511
499
  - ``"bolt_{model_size}"``: where model size is one of ``tiny,mini,small,base``. Uses the Chronos-Bolt pretrained model for zero-shot forecasting.
512
- See the documentation for ``ChronosModel`` or see `Hugging Face <https://huggingface.co/collections/amazon/chronos-models-65f1791d630a8d57cb718444>`_ for more information.
513
500
 
514
- Exact definitions of these presets can be found in the source code
515
- [`1 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/configs/presets_configs.py>`_,
516
- `2 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/models/presets.py>`_].
501
+ See the documentation for ``Chronos2`` and ``Chronos`` models in :ref:`Forecasting Time Series - Model Zoo <forecasting_model_zoo>`
502
+ or see `Hugging Face <https://huggingface.co/collections/amazon/chronos-models-65f1791d630a8d57cb718444>`_ for more information.
503
+
504
+ Exact definitions of all presets can be found in the source code
505
+ [`1 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/configs/predictor_presets.py>`_,
506
+ `2 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/configs/hyperparameter_presets.py>`_].
517
507
 
518
508
  If no ``presets`` are selected, user-provided values for ``hyperparameters`` will be used (defaulting to their
519
509
  default values specified below).
@@ -589,6 +579,8 @@ class TimeSeriesPredictor:
589
579
  * "bayes": Perform HPO with HyperOpt on GluonTS-backed models via Ray tune. Perform random search on other models.
590
580
  * "auto": alias for "bayes"
591
581
 
582
+ To enable HyperOpt, install the corresponding extra with ``pip install "autogluon.timeseries[ray]"``.
583
+
592
584
  The "scheduler" and "searcher" key are required when providing a dict.
593
585
 
594
586
  Example::
@@ -610,13 +602,39 @@ class TimeSeriesPredictor:
610
602
  presets="high_quality",
611
603
  excluded_model_types=["DeepAR"],
612
604
  )
613
- num_val_windows : int, default = 1
605
+ ensemble_hyperparameters : dict or list of dict, optional
606
+ Hyperparameters for ensemble models. Can be a single dict for one ensemble layer, or a list of dicts
607
+ for multiple ensemble layers (multi-layer stacking).
608
+
609
+ For single-layer ensembling (default)::
610
+
611
+ predictor.fit(
612
+ ...,
613
+ ensemble_hyperparameters={"WeightedEnsemble": {"ensemble_size": 10}},
614
+ )
615
+
616
+ For multi-layer ensembling, provide a list where each element configures one ensemble layer::
617
+
618
+ predictor.fit(
619
+ ...,
620
+ num_val_windows=(2, 3),
621
+ ensemble_hyperparameters=[
622
+ {"WeightedEnsemble": {"ensemble_size": 5}, "SimpleAverageEnsemble": {}}, # Layer 1
623
+ {"PerformanceWeightedEnsemble": {}}, # Layer 2
624
+ ],
625
+ )
626
+
627
+ When using multi-layer ensembling, ``num_val_windows`` must be a tuple of integers, and ``len(ensemble_hyperparameters)`` must match ``len(num_val_windows)``.
628
+ num_val_windows : int | tuple[int, ...] | "auto", default = 1
614
629
  Number of backtests done on ``train_data`` for each trained model to estimate the validation performance.
615
- If ``num_val_windows > 1`` is provided, this value may be automatically reduced to ensure that the majority
616
- of time series in ``train_data`` are long enough for the chosen number of backtests.
630
+ This parameter is also used to control multi-layer ensembling.
617
631
 
618
- Increasing this parameter increases the training time roughly by a factor of ``num_val_windows // refit_every_n_windows``.
619
- See ``refit_every_n_windows`` and ``val_step_size`` for details.
632
+ If set to ``"auto"``, the value will be determined automatically based on dataset properties (number of
633
+ time series and median time series length).
634
+
635
+ Increasing this parameter increases the training time roughly by a factor of
636
+ ``num_val_windows // refit_every_n_windows``. See ``refit_every_n_windows`` and ``val_step_size`` for
637
+ details.
620
638
 
621
639
  For example, for ``prediction_length=2``, ``num_val_windows=3`` and ``val_step_size=1`` the folds are::
622
640
 
@@ -627,17 +645,41 @@ class TimeSeriesPredictor:
627
645
 
628
646
  where ``x`` are the train time steps and ``y`` are the validation time steps.
629
647
 
630
- This argument has no effect if ``tuning_data`` is provided.
648
+ This parameter can also be used to control how many of the backtesting windows are reserved for training
649
+ multiple layers of ensemble models. By default, AutoGluon-TimeSeries uses only a single layer of ensembles
650
+ trained on the backtest windows specified by the ``num_val_windows`` parameter. However, the
651
+ ``ensemble_hyperparameters`` argument can be used to specify multiple layers of ensembles. In this case,
652
+ a tuple of integers can be provided in ``num_val_windows`` to control how many of the backtesting windows
653
+ will be used to train which ensemble layers.
654
+
655
+ For example, if ``len(ensemble_hyperparameters) == 2``, a 2-tuple ``num_val_windows=(2, 3)`` is analogous
656
+ to ``num_val_windows=5``, except the first layer of ensemble models will be trained on the first two
657
+ backtest windows, and the second layer will be trained on the latter three. Validation scores of all models
658
+ will be computed on the last three windows.
659
+
660
+ If ``len(ensemble_hyperparameters) == 1``, then ``num_val_windows=(5,)`` has the same effect as
661
+ ``num_val_windows=5``.
662
+
663
+ If ``tuning_data`` is provided and ``len(ensemble_hyperparameters) == 1``, then this parameter is ignored.
664
+ Validation and ensemble training will be performed on ``tuning_data``.
665
+
666
+ If ``tuning_data`` is provided and ``len(ensemble_hyperparameters) > 1``, then this method expects that
667
+ ``len(num_val_windows) > 1``. In this case, the last element of ``num_val_windows`` will be ignored. The
668
+ last layer of ensemble training will be performed on ``tuning_data``. Validation scores will likewise be
669
+ computed on ``tuning_data``.
670
+
631
671
  val_step_size : int or None, default = None
632
672
  Step size between consecutive validation windows. If set to ``None``, defaults to ``prediction_length``
633
673
  provided when creating the predictor.
634
674
 
635
- This argument has no effect if ``tuning_data`` is provided.
636
- refit_every_n_windows: int or None, default = 1
675
+ If ``tuning_data`` is provided and ``len(ensemble_hyperparameters) == 1``, then this parameter is ignored.
676
+ refit_every_n_windows: int | None | "auto", default = 1
637
677
  When performing cross validation, each model will be retrained every ``refit_every_n_windows`` validation
638
678
  windows, where the number of validation windows is specified by ``num_val_windows``. Note that in the
639
679
  default setting where ``num_val_windows=1``, this argument has no effect.
640
680
 
681
+ If set to ``"auto"``, the value will be determined automatically based on ``num_val_windows``.
682
+
641
683
  If set to ``None``, models will only be fit once for the first (oldest) validation window. By default,
642
684
  ``refit_every_n_windows=1``, i.e., all models will be refit for each validation window.
643
685
  refit_full : bool, default = False
@@ -660,8 +702,10 @@ class TimeSeriesPredictor:
660
702
 
661
703
  """
662
704
  time_start = time.time()
663
- if self._learner.is_fit:
664
- raise AssertionError("Predictor is already fit! To fit additional models create a new `Predictor`.")
705
+ if self.is_fit:
706
+ raise AssertionError(
707
+ "Predictor is already fit! To fit additional models create a new `TimeSeriesPredictor`."
708
+ )
665
709
 
666
710
  if verbosity is None:
667
711
  verbosity = self.verbosity
@@ -707,36 +751,57 @@ class TimeSeriesPredictor:
707
751
 
708
752
  if val_step_size is None:
709
753
  val_step_size = self.prediction_length
754
+ median_timeseries_length = int(train_data.num_timesteps_per_item().median())
755
+
756
+ # Early validation: check length mismatch when num_val_windows is explicitly provided
757
+ if num_val_windows != "auto" and ensemble_hyperparameters is not None:
758
+ num_layers = len(ensemble_hyperparameters) if isinstance(ensemble_hyperparameters, list) else 1
759
+ num_windows_tuple = num_val_windows if isinstance(num_val_windows, tuple) else (num_val_windows,)
760
+ if len(num_windows_tuple) != num_layers:
761
+ raise ValueError(
762
+ f"Length mismatch: num_val_windows has {len(num_windows_tuple)} element(s) but "
763
+ f"ensemble_hyperparameters has {num_layers} layer(s). These must match when num_val_windows "
764
+ f"is explicitly provided. Use num_val_windows='auto' to automatically determine the number of windows."
765
+ )
710
766
 
711
- if num_val_windows > 0:
712
- num_val_windows = self._reduce_num_val_windows_if_necessary(
713
- train_data, original_num_val_windows=num_val_windows, val_step_size=val_step_size
767
+ if num_val_windows == "auto":
768
+ num_val_windows = self._recommend_num_val_windows_auto(
769
+ median_timeseries_length=median_timeseries_length,
770
+ val_step_size=val_step_size,
771
+ num_items=train_data.num_items,
772
+ ensemble_hyperparameters=ensemble_hyperparameters,
714
773
  )
774
+ logger.info(f"Automatically setting num_val_windows={num_val_windows} based on dataset properties")
775
+
776
+ num_val_windows, ensemble_hyperparameters = self._validate_and_normalize_validation_and_ensemble_inputs(
777
+ num_val_windows=num_val_windows,
778
+ ensemble_hyperparameters=ensemble_hyperparameters,
779
+ val_step_size=val_step_size,
780
+ median_timeseries_length=median_timeseries_length,
781
+ tuning_data_provided=tuning_data is not None,
782
+ )
715
783
 
716
784
  if tuning_data is not None:
717
785
  tuning_data = self._check_and_prepare_data_frame(tuning_data, name="tuning_data")
718
786
  tuning_data = self._check_and_prepare_data_frame_for_evaluation(tuning_data, name="tuning_data")
719
787
  logger.info(f"Provided tuning_data has {self._get_dataset_stats(tuning_data)}")
720
- # TODO: Use num_val_windows to perform multi-window backtests on tuning_data
721
- if num_val_windows > 0:
722
- logger.warning(
723
- "\tSetting num_val_windows = 0 (disabling backtesting on train_data) because tuning_data is provided."
724
- )
725
- num_val_windows = 0
726
788
 
727
- if num_val_windows == 0 and tuning_data is None:
728
- raise ValueError("Please set num_val_windows >= 1 or provide custom tuning_data")
789
+ if refit_every_n_windows == "auto":
790
+ refit_every_n_windows = self._recommend_refit_every_n_windows_auto(num_val_windows)
791
+ logger.info(
792
+ f"Automatically setting refit_every_n_windows={refit_every_n_windows} based on num_val_windows"
793
+ )
729
794
 
730
- if num_val_windows <= 1 and refit_every_n_windows is not None and refit_every_n_windows > 1:
795
+ if sum(num_val_windows) <= 1 and refit_every_n_windows is not None and refit_every_n_windows > 1:
731
796
  logger.warning(
732
- f"\trefit_every_n_windows provided as {refit_every_n_windows} but num_val_windows is set to {num_val_windows}."
733
- " Refit_every_n_windows will have no effect."
797
+ f"\trefit_every_n_windows provided as {refit_every_n_windows} but num_val_windows is set to "
798
+ f"{num_val_windows}. refit_every_n_windows will have no effect."
734
799
  )
735
800
 
736
801
  if not skip_model_selection:
737
- train_data = self._filter_useless_train_data(
738
- train_data, num_val_windows=num_val_windows, val_step_size=val_step_size
739
- )
802
+ # When tuning_data is provided, ignore the last element of num_val_windows for filtering purposes
803
+ filter_num_val_windows = num_val_windows[:-1] if tuning_data is not None else num_val_windows
804
+ train_data = self._filter_useless_train_data(train_data, filter_num_val_windows, val_step_size)
740
805
 
741
806
  time_left = None if time_limit is None else time_limit - (time.time() - time_start)
742
807
  self._learner.fit(
@@ -745,6 +810,7 @@ class TimeSeriesPredictor:
745
810
  val_data=tuning_data,
746
811
  hyperparameter_tune_kwargs=hyperparameter_tune_kwargs,
747
812
  excluded_model_types=excluded_model_types,
813
+ ensemble_hyperparameters=ensemble_hyperparameters,
748
814
  time_limit=time_left,
749
815
  verbosity=verbosity,
750
816
  num_val_windows=num_val_windows,
@@ -763,23 +829,152 @@ class TimeSeriesPredictor:
763
829
  self.save()
764
830
  return self
765
831
 
832
+ def _recommend_num_val_windows_auto(
833
+ self,
834
+ num_items: int,
835
+ median_timeseries_length: int,
836
+ val_step_size: int,
837
+ ensemble_hyperparameters: dict[str, Any] | list[dict[str, Any]] | None = None,
838
+ ) -> tuple[int, ...]:
839
+ if num_items < 20:
840
+ recommended_windows = 5
841
+ elif num_items < 100:
842
+ recommended_windows = 3
843
+ else:
844
+ recommended_windows = 2
845
+
846
+ min_train_length = max(2 * self.prediction_length + 1, 10)
847
+ max_windows = int((median_timeseries_length - min_train_length - self.prediction_length) // val_step_size + 1)
848
+ total_windows = min(recommended_windows, max(1, max_windows))
849
+
850
+ num_layers = len(ensemble_hyperparameters) if isinstance(ensemble_hyperparameters, list) else 1
851
+ if total_windows >= num_layers:
852
+ # Distribute windows: most to first layer, 1 to each remaining layer
853
+ return (total_windows - num_layers + 1,) + (1,) * (num_layers - 1)
854
+ else:
855
+ # Insufficient windows: return tuple matching num_layers, will be reduced downstream
856
+ return (1,) * num_layers
857
+
858
+ def _recommend_refit_every_n_windows_auto(self, num_val_windows: tuple[int, ...]) -> int:
859
+ # Simple mapping for total_windows -> refit_ever_n_windows: 1 -> 1, 2 -> 1, 3 -> 2, 4 -> 2, 5 -> 2
860
+ total_windows = sum(num_val_windows)
861
+ return int(round(total_windows**0.5))
862
+
863
+ def _validate_and_normalize_validation_and_ensemble_inputs(
864
+ self,
865
+ num_val_windows: int | tuple[int, ...],
866
+ ensemble_hyperparameters: dict[str, Any] | list[dict[str, Any]] | None,
867
+ val_step_size: int,
868
+ median_timeseries_length: float,
869
+ tuning_data_provided: bool,
870
+ ) -> tuple[tuple[int, ...], list[dict[str, Any]] | None]:
871
+ """Validate and normalize num_val_windows and ensemble_hyperparameters for multi-layer ensembling."""
872
+ if ensemble_hyperparameters is not None and isinstance(ensemble_hyperparameters, dict):
873
+ ensemble_hyperparameters = [ensemble_hyperparameters]
874
+
875
+ num_val_windows = self._normalize_num_val_windows_input(num_val_windows, tuning_data_provided)
876
+ num_val_windows = self._reduce_num_val_windows_if_necessary(
877
+ num_val_windows, val_step_size, median_timeseries_length, tuning_data_provided
878
+ )
879
+
880
+ if ensemble_hyperparameters is not None and len(num_val_windows) < len(ensemble_hyperparameters):
881
+ logger.warning(
882
+ f"Time series too short: reducing ensemble layers from {len(ensemble_hyperparameters)} to "
883
+ f"{len(num_val_windows)}. Only the first {len(num_val_windows)} ensemble layer(s) will be trained."
884
+ )
885
+ ensemble_hyperparameters = ensemble_hyperparameters[: len(num_val_windows)]
886
+
887
+ return num_val_windows, ensemble_hyperparameters
888
+
889
+ def _normalize_num_val_windows_input(
890
+ self,
891
+ num_val_windows: int | tuple[int, ...],
892
+ tuning_data_provided: bool,
893
+ ) -> tuple[int, ...]:
894
+ if isinstance(num_val_windows, int):
895
+ num_val_windows = (num_val_windows,)
896
+ if not isinstance(num_val_windows, tuple):
897
+ raise TypeError(f"num_val_windows must be int or tuple[int, ...], got {type(num_val_windows)}")
898
+ if len(num_val_windows) == 0:
899
+ raise ValueError("num_val_windows tuple cannot be empty")
900
+ if tuning_data_provided:
901
+ num_val_windows = num_val_windows[:-1] + (1,)
902
+ logger.warning(
903
+ f"\tTuning data is provided. Setting num_val_windows = {num_val_windows}. Validation scores will"
904
+ " be computed on a single window of tuning_data."
905
+ )
906
+ if not all(isinstance(n, int) and n > 0 for n in num_val_windows):
907
+ raise ValueError("All elements of num_val_windows must be positive integers.")
908
+ return num_val_windows
909
+
910
+ def _reduce_num_val_windows_if_necessary(
911
+ self,
912
+ num_val_windows: tuple[int, ...],
913
+ val_step_size: int,
914
+ median_time_series_length: float,
915
+ tuning_data_provided: bool,
916
+ ) -> tuple[int, ...]:
917
+ """Adjust num_val_windows based on the length of time series in train_data.
918
+
919
+ Chooses num_val_windows such that TS with median length is long enough to perform num_val_windows validations
920
+ (at least 1, at most `original_num_val_windows`).
921
+
922
+ In other words, find largest `num_val_windows` that satisfies
923
+ median_length >= min_train_length + prediction_length + (num_val_windows - 1) * val_step_size
924
+
925
+ If tuning_data is provided, the last element of `num_val_windows` is ignored when computing the number of
926
+ requested validation windows.
927
+ """
928
+ num_val_windows_for_median_ts = int(
929
+ (median_time_series_length - self._min_train_length - self.prediction_length) // val_step_size + 1
930
+ )
931
+ max_allowed = max(1, num_val_windows_for_median_ts)
932
+ total_requested = sum(num_val_windows) if not tuning_data_provided else sum(num_val_windows[:-1])
933
+
934
+ if max_allowed >= total_requested:
935
+ return num_val_windows
936
+
937
+ logger.warning(
938
+ f"Time series in train_data are too short for chosen num_val_windows={num_val_windows}. "
939
+ f"Reducing num_val_windows to {max_allowed} total windows."
940
+ )
941
+
942
+ result = list(num_val_windows)
943
+
944
+ # Starting from the last group of windows, reduce number of windows in each group by 1,
945
+ # until sum(num_val_windows) <= max_allowed is satisfied.
946
+ for i in range(len(result) - 1, -1, -1):
947
+ while result[i] > 1 and sum(result) > max_allowed:
948
+ result[i] -= 1
949
+ if sum(result) <= max_allowed:
950
+ break
951
+
952
+ # It is possible that the above for loop reduced the number of windows in each group to 1
953
+ # (i.e. result = [1] * len(num_val_windows)), but still sum(result) > max_allowed. In this
954
+ # case we set result = [1] * max_allowed
955
+ if sum(result) > max_allowed:
956
+ result = [1] * max_allowed
957
+
958
+ return tuple(result)
959
+
766
960
  def model_names(self) -> list[str]:
767
961
  """Returns the list of model names trained by this predictor object."""
962
+ self._assert_is_fit("model_names")
768
963
  return self._trainer.get_model_names()
769
964
 
770
965
  def predict(
771
966
  self,
772
- data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
773
- known_covariates: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
774
- model: Optional[str] = None,
967
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
968
+ known_covariates: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
969
+ model: str | None = None,
775
970
  use_cache: bool = True,
776
- random_seed: Optional[int] = 123,
971
+ random_seed: int | None = 123,
777
972
  ) -> TimeSeriesDataFrame:
778
973
  """Return quantile and mean forecasts for the given dataset, starting from the end of each time series.
779
974
 
780
975
  Parameters
781
976
  ----------
782
- data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]
977
+ data : TimeSeriesDataFrame | pd.DataFrame | Path | str
783
978
  Historical time series data for which the forecast needs to be made.
784
979
 
785
980
  The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
@@ -787,7 +982,7 @@ class TimeSeriesPredictor:
787
982
 
788
983
  If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
789
984
  If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
790
- known_covariates : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str], optional
985
+ known_covariates : TimeSeriesDataFrame | pd.DataFrame | Path | str, optional
791
986
  If ``known_covariates_names`` were specified when creating the predictor, it is necessary to provide the
792
987
  values of the known covariates for each time series during the forecast horizon. Specifically:
793
988
 
@@ -837,6 +1032,7 @@ class TimeSeriesPredictor:
837
1032
  B 2020-03-04 17.1
838
1033
  2020-03-05 8.3
839
1034
  """
1035
+ self._assert_is_fit("predict")
840
1036
  # Save original item_id order to return predictions in the same order as input data
841
1037
  data = self._to_data_frame(data)
842
1038
  original_item_id_order = data.item_ids
@@ -852,12 +1048,207 @@ class TimeSeriesPredictor:
852
1048
  )
853
1049
  return cast(TimeSeriesDataFrame, predictions.reindex(original_item_id_order, level=TimeSeriesDataFrame.ITEMID))
854
1050
 
1051
+ @overload
1052
+ def backtest_predictions(
1053
+ self,
1054
+ data: TimeSeriesDataFrame | None = None,
1055
+ *,
1056
+ model: str | None = None,
1057
+ num_val_windows: int | None = None,
1058
+ val_step_size: int | None = None,
1059
+ use_cache: bool = True,
1060
+ ) -> list[TimeSeriesDataFrame]: ...
1061
+
1062
+ @overload
1063
+ def backtest_predictions(
1064
+ self,
1065
+ data: TimeSeriesDataFrame | None = None,
1066
+ *,
1067
+ model: list[str],
1068
+ num_val_windows: int | None = None,
1069
+ val_step_size: int | None = None,
1070
+ use_cache: bool = True,
1071
+ ) -> dict[str, list[TimeSeriesDataFrame]]: ...
1072
+
1073
+ def backtest_predictions(
1074
+ self,
1075
+ data: TimeSeriesDataFrame | None = None,
1076
+ *,
1077
+ model: str | list[str] | None = None,
1078
+ num_val_windows: int | None = None,
1079
+ val_step_size: int | None = None,
1080
+ use_cache: bool = True,
1081
+ ) -> list[TimeSeriesDataFrame] | dict[str, list[TimeSeriesDataFrame]]:
1082
+ """Return predictions for multiple validation windows.
1083
+
1084
+ When ``data=None``, returns the predictions that were saved during training. Otherwise, generates new
1085
+ predictions by splitting ``data`` into multiple windows using an expanding window strategy.
1086
+
1087
+ The corresponding target values for each window can be obtained using
1088
+ :meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_targets`.
1089
+
1090
+ Parameters
1091
+ ----------
1092
+ data : TimeSeriesDataFrame, optional
1093
+ Time series data to generate predictions for. If ``None``, returns the predictions that were saved
1094
+ during training on ``train_data``.
1095
+
1096
+ If provided, all time series in ``data`` must have length at least
1097
+ ``prediction_length + (num_val_windows - 1) * val_step_size + 1``.
1098
+
1099
+ The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
1100
+ the predictor.
1101
+ model : str, list[str], or None, default = None
1102
+ Name of the model(s) to generate predictions with. By default, the best model during training
1103
+ (with highest validation score) will be used.
1104
+
1105
+ - If ``str``: Returns predictions for a single model as a list.
1106
+ - If ``list[str]``: Returns predictions for multiple models as a dict mapping model names to lists.
1107
+ - If ``None``: Uses the best model.
1108
+ num_val_windows : int, optional
1109
+ Number of validation windows to generate. If ``None``, uses the ``num_val_windows`` value from training
1110
+ configuration when ``data=None``, otherwise defaults to 1.
1111
+
1112
+ For example, with ``prediction_length=2``, ``num_val_windows=3``, and ``val_step_size=1``, the validation
1113
+ windows are::
1114
+
1115
+ |-------------------|
1116
+ | x x x x x y y - - |
1117
+ | x x x x x x y y - |
1118
+ | x x x x x x x y y |
1119
+
1120
+ where ``x`` denotes training time steps and ``y`` denotes validation time steps for each window.
1121
+ val_step_size : int, optional
1122
+ Number of time steps between the start of consecutive validation windows. If ``None``, defaults to
1123
+ ``prediction_length``.
1124
+ use_cache : bool, default = True
1125
+ If True, will attempt to use cached predictions. If False, cached predictions will be ignored.
1126
+ This argument is ignored if ``cache_predictions`` was set to False when creating the ``TimeSeriesPredictor``.
1127
+
1128
+ Returns
1129
+ -------
1130
+ list[TimeSeriesDataFrame] or dict[str, list[TimeSeriesDataFrame]]
1131
+ Predictions for each validation window.
1132
+
1133
+ - If ``model`` is a ``str`` or ``None``: Returns a list of length ``num_val_windows``, where each element
1134
+ contains the predictions for one validation window.
1135
+ - If ``model`` is a ``list[str]``: Returns a dict mapping each model name to a list of predictions for
1136
+ each validation window.
1137
+
1138
+ Examples
1139
+ --------
1140
+ Make predictions on new data with the best model
1141
+
1142
+ >>> predictor.backtest_predictions(test_data, num_val_windows=2)
1143
+
1144
+ Load validation predictions for all models that were saved during training
1145
+
1146
+ >>> predictor.backtest_predictions(model=predictor.model_names())
1147
+
1148
+ See Also
1149
+ --------
1150
+ backtest_targets
1151
+ Return target values aligned with predictions.
1152
+ evaluate
1153
+ Evaluate forecast accuracy on a hold-out set.
1154
+ predict
1155
+ Generate forecasts for future time steps.
1156
+ """
1157
+ self._assert_is_fit("backtest_predictions")
1158
+ if data is not None:
1159
+ data = self._check_and_prepare_data_frame(data)
1160
+
1161
+ if model is None:
1162
+ model_names = [self.model_best]
1163
+ elif isinstance(model, str):
1164
+ model_names = [model]
1165
+ else:
1166
+ model_names = model
1167
+
1168
+ result = self._learner.backtest_predictions(
1169
+ data=data,
1170
+ model_names=model_names,
1171
+ num_val_windows=num_val_windows,
1172
+ val_step_size=val_step_size,
1173
+ use_cache=use_cache,
1174
+ )
1175
+
1176
+ if isinstance(model, list):
1177
+ return result
1178
+ else:
1179
+ return result[model_names[0]]
1180
+
1181
+ def backtest_targets(
1182
+ self,
1183
+ data: TimeSeriesDataFrame | None = None,
1184
+ *,
1185
+ num_val_windows: int | None = None,
1186
+ val_step_size: int | None = None,
1187
+ ) -> list[TimeSeriesDataFrame]:
1188
+ """Return target values for each validation window.
1189
+
1190
+ Returns the actual target values corresponding to each validation window used in
1191
+ :meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`. The returned targets are aligned
1192
+ with the predictions, making it easy to compute custom evaluation metrics or analyze forecast errors.
1193
+
1194
+ Parameters
1195
+ ----------
1196
+ data : TimeSeriesDataFrame, optional
1197
+ Time series data to extract targets from. If ``None``, returns the targets from the validation windows
1198
+ used during training.
1199
+
1200
+ If provided, all time series in ``data`` must have length at least
1201
+ ``prediction_length + (num_val_windows - 1) * val_step_size + 1``.
1202
+
1203
+ The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
1204
+ the predictor.
1205
+ num_val_windows : int, optional
1206
+ Number of validation windows to extract targets for. If ``None``, uses the ``num_val_windows`` value from
1207
+ training configuration when ``data=None``, otherwise defaults to 1.
1208
+
1209
+ This should match the ``num_val_windows`` argument passed to
1210
+ :meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`.
1211
+ val_step_size : int, optional
1212
+ Number of time steps between the start of consecutive validation windows. If ``None``, defaults to
1213
+ ``prediction_length``.
1214
+
1215
+ This should match the ``val_step_size`` argument passed to
1216
+ :meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`.
1217
+
1218
+ Returns
1219
+ -------
1220
+ list[TimeSeriesDataFrame]
1221
+ Target values for each validation window. Returns a list of length ``num_val_windows``,
1222
+ where each element contains the full time series data for one validation window.
1223
+ Each dataframe includes both historical context and the last ``prediction_length`` time steps
1224
+ that represent the target values to compare against predictions.
1225
+
1226
+ The returned targets are aligned with the output of
1227
+ :meth:`~autogluon.timeseries.TimeSeriesPredictor.backtest_predictions`, so ``targets[i]`` corresponds
1228
+ to ``predictions[i]`` for the i-th validation window.
1229
+
1230
+ See Also
1231
+ --------
1232
+ backtest_predictions
1233
+ Return predictions for multiple validation windows.
1234
+ evaluate
1235
+ Evaluate forecast accuracy on a hold-out set.
1236
+ """
1237
+ self._assert_is_fit("backtest_targets")
1238
+ if data is not None:
1239
+ data = self._check_and_prepare_data_frame(data)
1240
+ return self._learner.backtest_targets(
1241
+ data=data,
1242
+ num_val_windows=num_val_windows,
1243
+ val_step_size=val_step_size,
1244
+ )
1245
+
855
1246
  def evaluate(
856
1247
  self,
857
- data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
858
- model: Optional[str] = None,
859
- metrics: Optional[Union[str, TimeSeriesScorer, list[Union[str, TimeSeriesScorer]]]] = None,
860
- cutoff: Optional[int] = None,
1248
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
1249
+ model: str | None = None,
1250
+ metrics: str | TimeSeriesScorer | list[str | TimeSeriesScorer] | None = None,
1251
+ cutoff: int | None = None,
861
1252
  display: bool = False,
862
1253
  use_cache: bool = True,
863
1254
  ) -> dict[str, float]:
@@ -874,7 +1265,7 @@ class TimeSeriesPredictor:
874
1265
 
875
1266
  Parameters
876
1267
  ----------
877
- data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]
1268
+ data : TimeSeriesDataFrame | pd.DataFrame | Path | str
878
1269
  The data to evaluate the best model on. If a ``cutoff`` is not provided, the last ``prediction_length``
879
1270
  time steps of each time series in ``data`` will be held out for prediction and forecast accuracy will
880
1271
  be calculated on these time steps. When a ``cutoff`` is provided, the ``-cutoff``-th to the
@@ -891,7 +1282,7 @@ class TimeSeriesPredictor:
891
1282
  model : str, optional
892
1283
  Name of the model that you would like to evaluate. By default, the best model during training
893
1284
  (with highest validation score) will be used.
894
- metrics : str, TimeSeriesScorer or list[Union[str, TimeSeriesScorer]], optional
1285
+ metrics : str, TimeSeriesScorer or list[str | TimeSeriesScorer], optional
895
1286
  Metric or a list of metrics to compute scores with. Defaults to ``self.eval_metric``. Supports both
896
1287
  metric names as strings and custom metrics based on TimeSeriesScorer.
897
1288
  cutoff : int, optional
@@ -912,7 +1303,7 @@ class TimeSeriesPredictor:
912
1303
  will have their signs flipped to obey this convention. For example, negative MAPE values will be reported.
913
1304
  To get the ``eval_metric`` score, do ``output[predictor.eval_metric.name]``.
914
1305
  """
915
-
1306
+ self._assert_is_fit("evaluate")
916
1307
  data = self._check_and_prepare_data_frame(data)
917
1308
  data = self._check_and_prepare_data_frame_for_evaluation(data, cutoff=cutoff)
918
1309
 
@@ -924,15 +1315,15 @@ class TimeSeriesPredictor:
924
1315
 
925
1316
  def feature_importance(
926
1317
  self,
927
- data: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
928
- model: Optional[str] = None,
929
- metric: Optional[Union[str, TimeSeriesScorer]] = None,
930
- features: Optional[list[str]] = None,
931
- time_limit: Optional[float] = None,
1318
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
1319
+ model: str | None = None,
1320
+ metric: str | TimeSeriesScorer | None = None,
1321
+ features: list[str] | None = None,
1322
+ time_limit: float | None = None,
932
1323
  method: Literal["naive", "permutation"] = "permutation",
933
1324
  subsample_size: int = 50,
934
- num_iterations: Optional[int] = None,
935
- random_seed: Optional[int] = 123,
1325
+ num_iterations: int | None = None,
1326
+ random_seed: int | None = 123,
936
1327
  relative_scores: bool = False,
937
1328
  include_confidence_band: bool = True,
938
1329
  confidence_level: float = 0.99,
@@ -1029,6 +1420,7 @@ class TimeSeriesPredictor:
1029
1420
  'importance': The estimated feature importance score.
1030
1421
  'stddev': The standard deviation of the feature importance score. If NaN, then not enough ``num_iterations`` were used.
1031
1422
  """
1423
+ self._assert_is_fit("feature_importance")
1032
1424
  if data is not None:
1033
1425
  data = self._check_and_prepare_data_frame(data)
1034
1426
  data = self._check_and_prepare_data_frame_for_evaluation(data)
@@ -1047,7 +1439,7 @@ class TimeSeriesPredictor:
1047
1439
  include_confidence_band=include_confidence_band,
1048
1440
  confidence_level=confidence_level,
1049
1441
  )
1050
- return fi_df
1442
+ return fi_df.sort_values("importance", ascending=False)
1051
1443
 
1052
1444
  @classmethod
1053
1445
  def _load_version_file(cls, path: str) -> str:
@@ -1075,7 +1467,7 @@ class TimeSeriesPredictor:
1075
1467
  return version
1076
1468
 
1077
1469
  @classmethod
1078
- def load(cls, path: Union[str, Path], require_version_match: bool = True) -> "TimeSeriesPredictor":
1470
+ def load(cls, path: str | Path, require_version_match: bool = True) -> "TimeSeriesPredictor":
1079
1471
  """Load an existing ``TimeSeriesPredictor`` from given ``path``.
1080
1472
 
1081
1473
  .. warning::
@@ -1159,15 +1551,14 @@ class TimeSeriesPredictor:
1159
1551
  @property
1160
1552
  def model_best(self) -> str:
1161
1553
  """Returns the name of the best model from trainer."""
1554
+ self._assert_is_fit("model_best")
1162
1555
  if self._trainer.model_best is not None:
1163
1556
  models = self._trainer.get_model_names()
1164
1557
  if self._trainer.model_best in models:
1165
1558
  return self._trainer.model_best
1166
1559
  return self._trainer.get_model_best()
1167
1560
 
1168
- def persist(
1169
- self, models: Union[Literal["all", "best"], list[str]] = "best", with_ancestors: bool = True
1170
- ) -> list[str]:
1561
+ def persist(self, models: Literal["all", "best"] | list[str] = "best", with_ancestors: bool = True) -> list[str]:
1171
1562
  """Persist models in memory for reduced inference latency. This is particularly important if the models are being used for online
1172
1563
  inference where low latency is critical. If models are not persisted in memory, they are loaded from disk every time they are
1173
1564
  asked to make predictions. This is especially cumbersome for large deep learning based models which have to be loaded into
@@ -1190,6 +1581,7 @@ class TimeSeriesPredictor:
1190
1581
  list_of_models : list[str]
1191
1582
  List of persisted model names.
1192
1583
  """
1584
+ self._assert_is_fit("persist")
1193
1585
  return self._learner.persist_trainer(models=models, with_ancestors=with_ancestors)
1194
1586
 
1195
1587
  def unpersist(self) -> list[str]:
@@ -1208,10 +1600,10 @@ class TimeSeriesPredictor:
1208
1600
 
1209
1601
  def leaderboard(
1210
1602
  self,
1211
- data: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
1212
- cutoff: Optional[int] = None,
1603
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str | None = None,
1604
+ cutoff: int | None = None,
1213
1605
  extra_info: bool = False,
1214
- extra_metrics: Optional[list[Union[str, TimeSeriesScorer]]] = None,
1606
+ extra_metrics: list[str | TimeSeriesScorer] | None = None,
1215
1607
  display: bool = False,
1216
1608
  use_cache: bool = True,
1217
1609
  **kwargs,
@@ -1236,7 +1628,7 @@ class TimeSeriesPredictor:
1236
1628
 
1237
1629
  Parameters
1238
1630
  ----------
1239
- data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str], optional
1631
+ data : TimeSeriesDataFrame | pd.DataFrame | Path | str, optional
1240
1632
  dataset used for additional evaluation. Must include both historical and future data (i.e., length of all
1241
1633
  time series in ``data`` must be at least ``prediction_length + 1``, if ``cutoff`` is not provided,
1242
1634
  ``-cutoff + 1`` otherwise).
@@ -1255,7 +1647,7 @@ class TimeSeriesPredictor:
1255
1647
  If True, the leaderboard will contain an additional column ``hyperparameters`` with the hyperparameters used
1256
1648
  by each model during training. An empty dictionary ``{}`` means that the model was trained with default
1257
1649
  hyperparameters.
1258
- extra_metrics : list[Union[str, TimeSeriesScorer]], optional
1650
+ extra_metrics : list[str | TimeSeriesScorer], optional
1259
1651
  A list of metrics to calculate scores for and include in the output DataFrame.
1260
1652
 
1261
1653
  Only valid when ``data`` is specified. The scores refer to the scores on ``data`` (same data as used to
@@ -1277,6 +1669,7 @@ class TimeSeriesPredictor:
1277
1669
  The leaderboard containing information on all models and in order of best model to worst in terms of
1278
1670
  test performance.
1279
1671
  """
1672
+ self._assert_is_fit("leaderboard")
1280
1673
  if "silent" in kwargs:
1281
1674
  # keep `silent` logic for backwards compatibility
1282
1675
  assert isinstance(kwargs["silent"], bool)
@@ -1301,12 +1694,12 @@ class TimeSeriesPredictor:
1301
1694
  print(leaderboard)
1302
1695
  return leaderboard
1303
1696
 
1304
- def make_future_data_frame(self, data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]) -> pd.DataFrame:
1697
+ def make_future_data_frame(self, data: TimeSeriesDataFrame | pd.DataFrame | Path | str) -> pd.DataFrame:
1305
1698
  """Generate a dataframe with the ``item_id`` and ``timestamp`` values corresponding to the forecast horizon.
1306
1699
 
1307
1700
  Parameters
1308
1701
  ----------
1309
- data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]
1702
+ data : TimeSeriesDataFrame | pd.DataFrame | Path | str
1310
1703
  Historical time series data.
1311
1704
 
1312
1705
  Returns
@@ -1354,6 +1747,7 @@ class TimeSeriesPredictor:
1354
1747
  Dict containing various detailed information. We do not recommend directly printing this dict as it may
1355
1748
  be very large.
1356
1749
  """
1750
+ self._assert_is_fit("fit_summary")
1357
1751
  # TODO: HPO-specific information currently not reported in fit_summary
1358
1752
  # TODO: Revisit after ray tune integration
1359
1753
 
@@ -1414,6 +1808,7 @@ class TimeSeriesPredictor:
1414
1808
  ``predictor.predict(data)`` is called will be the refit_full version instead of the original version of the
1415
1809
  model. Has no effect if ``model`` is not the best model.
1416
1810
  """
1811
+ self._assert_is_fit("refit_full")
1417
1812
  logger.warning(
1418
1813
  "\tWARNING: refit_full functionality for TimeSeriesPredictor is experimental "
1419
1814
  "and is not yet supported by all models."
@@ -1466,7 +1861,7 @@ class TimeSeriesPredictor:
1466
1861
  trainer = self._trainer
1467
1862
  train_data = trainer.load_train_data()
1468
1863
  val_data = trainer.load_val_data()
1469
- base_model_names = trainer.get_model_names(level=0)
1864
+ base_model_names = trainer.get_model_names(layer=0)
1470
1865
  pred_proba_dict_val: dict[str, list[TimeSeriesDataFrame]] = {
1471
1866
  model_name: trainer._get_model_oof_predictions(model_name)
1472
1867
  for model_name in base_model_names
@@ -1502,27 +1897,27 @@ class TimeSeriesPredictor:
1502
1897
 
1503
1898
  def plot(
1504
1899
  self,
1505
- data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
1506
- predictions: Optional[TimeSeriesDataFrame] = None,
1507
- quantile_levels: Optional[list[float]] = None,
1508
- item_ids: Optional[list[Union[str, int]]] = None,
1900
+ data: TimeSeriesDataFrame | pd.DataFrame | Path | str,
1901
+ predictions: TimeSeriesDataFrame | None = None,
1902
+ quantile_levels: list[float] | None = None,
1903
+ item_ids: list[str | int] | None = None,
1509
1904
  max_num_item_ids: int = 8,
1510
- max_history_length: Optional[int] = None,
1511
- point_forecast_column: Optional[str] = None,
1512
- matplotlib_rc_params: Optional[dict] = None,
1905
+ max_history_length: int | None = None,
1906
+ point_forecast_column: str | None = None,
1907
+ matplotlib_rc_params: dict | None = None,
1513
1908
  ):
1514
1909
  """Plot historical time series values and the forecasts.
1515
1910
 
1516
1911
  Parameters
1517
1912
  ----------
1518
- data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]
1913
+ data : TimeSeriesDataFrame | pd.DataFrame | Path | str
1519
1914
  Observed time series data.
1520
1915
  predictions : TimeSeriesDataFrame, optional
1521
1916
  Predictions generated by calling :meth:`~autogluon.timeseries.TimeSeriesPredictor.predict`.
1522
1917
  quantile_levels : list[float], optional
1523
1918
  Quantile levels for which to plot the prediction intervals. Defaults to lowest & highest quantile levels
1524
1919
  available in ``predictions``.
1525
- item_ids : list[Union[str, int]], optional
1920
+ item_ids : list[str | int], optional
1526
1921
  If provided, plots will only be generated for time series with these item IDs. By default (if set to
1527
1922
  ``None``), item IDs are selected randomly. In either case, plots are generated for at most
1528
1923
  ``max_num_item_ids`` time series.