autogluon.timeseries 1.3.2b20250712__py3-none-any.whl → 1.4.1b20251116__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.
- autogluon/timeseries/configs/__init__.py +3 -2
- autogluon/timeseries/configs/hyperparameter_presets.py +62 -0
- autogluon/timeseries/configs/predictor_presets.py +84 -0
- autogluon/timeseries/dataset/ts_dataframe.py +98 -72
- autogluon/timeseries/learner.py +19 -18
- autogluon/timeseries/metrics/__init__.py +5 -5
- autogluon/timeseries/metrics/abstract.py +17 -17
- autogluon/timeseries/metrics/point.py +1 -1
- autogluon/timeseries/metrics/quantile.py +2 -2
- autogluon/timeseries/metrics/utils.py +4 -4
- autogluon/timeseries/models/__init__.py +4 -0
- autogluon/timeseries/models/abstract/abstract_timeseries_model.py +52 -75
- autogluon/timeseries/models/abstract/tunable.py +6 -6
- autogluon/timeseries/models/autogluon_tabular/mlforecast.py +72 -76
- autogluon/timeseries/models/autogluon_tabular/per_step.py +104 -46
- autogluon/timeseries/models/autogluon_tabular/transforms.py +9 -7
- autogluon/timeseries/models/chronos/model.py +115 -78
- autogluon/timeseries/models/chronos/{pipeline/utils.py → utils.py} +76 -44
- autogluon/timeseries/models/ensemble/__init__.py +29 -2
- autogluon/timeseries/models/ensemble/abstract.py +16 -52
- autogluon/timeseries/models/ensemble/array_based/__init__.py +3 -0
- autogluon/timeseries/models/ensemble/array_based/abstract.py +247 -0
- autogluon/timeseries/models/ensemble/array_based/models.py +50 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/__init__.py +10 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/abstract.py +87 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/per_quantile_tabular.py +133 -0
- autogluon/timeseries/models/ensemble/array_based/regressor/tabular.py +141 -0
- autogluon/timeseries/models/ensemble/weighted/__init__.py +8 -0
- autogluon/timeseries/models/ensemble/weighted/abstract.py +41 -0
- autogluon/timeseries/models/ensemble/{basic.py → weighted/basic.py} +8 -18
- autogluon/timeseries/models/ensemble/{greedy.py → weighted/greedy.py} +13 -13
- autogluon/timeseries/models/gluonts/abstract.py +26 -26
- autogluon/timeseries/models/gluonts/dataset.py +4 -4
- autogluon/timeseries/models/gluonts/models.py +27 -12
- autogluon/timeseries/models/local/abstract_local_model.py +14 -14
- autogluon/timeseries/models/local/naive.py +4 -0
- autogluon/timeseries/models/local/npts.py +1 -0
- autogluon/timeseries/models/local/statsforecast.py +30 -14
- autogluon/timeseries/models/multi_window/multi_window_model.py +34 -23
- autogluon/timeseries/models/registry.py +65 -0
- autogluon/timeseries/models/toto/__init__.py +3 -0
- autogluon/timeseries/models/toto/_internal/__init__.py +9 -0
- autogluon/timeseries/models/toto/_internal/backbone/__init__.py +3 -0
- autogluon/timeseries/models/toto/_internal/backbone/attention.py +197 -0
- autogluon/timeseries/models/toto/_internal/backbone/backbone.py +262 -0
- autogluon/timeseries/models/toto/_internal/backbone/distribution.py +70 -0
- autogluon/timeseries/models/toto/_internal/backbone/kvcache.py +136 -0
- autogluon/timeseries/models/toto/_internal/backbone/rope.py +94 -0
- autogluon/timeseries/models/toto/_internal/backbone/scaler.py +306 -0
- autogluon/timeseries/models/toto/_internal/backbone/transformer.py +333 -0
- autogluon/timeseries/models/toto/_internal/dataset.py +165 -0
- autogluon/timeseries/models/toto/_internal/forecaster.py +423 -0
- autogluon/timeseries/models/toto/dataloader.py +108 -0
- autogluon/timeseries/models/toto/hf_pretrained_model.py +119 -0
- autogluon/timeseries/models/toto/model.py +236 -0
- autogluon/timeseries/predictor.py +94 -107
- autogluon/timeseries/regressor.py +31 -27
- autogluon/timeseries/splitter.py +7 -31
- autogluon/timeseries/trainer/__init__.py +3 -0
- autogluon/timeseries/trainer/ensemble_composer.py +250 -0
- autogluon/timeseries/trainer/model_set_builder.py +256 -0
- autogluon/timeseries/trainer/prediction_cache.py +149 -0
- autogluon/timeseries/{trainer.py → trainer/trainer.py} +182 -307
- autogluon/timeseries/trainer/utils.py +18 -0
- autogluon/timeseries/transforms/covariate_scaler.py +4 -4
- autogluon/timeseries/transforms/target_scaler.py +14 -14
- autogluon/timeseries/utils/datetime/lags.py +2 -2
- autogluon/timeseries/utils/datetime/time_features.py +2 -2
- autogluon/timeseries/utils/features.py +41 -37
- autogluon/timeseries/utils/forecast.py +5 -5
- autogluon/timeseries/utils/warning_filters.py +3 -1
- autogluon/timeseries/version.py +1 -1
- autogluon.timeseries-1.4.1b20251116-py3.9-nspkg.pth +1 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/METADATA +32 -17
- autogluon_timeseries-1.4.1b20251116.dist-info/RECORD +96 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/WHEEL +1 -1
- autogluon/timeseries/configs/presets_configs.py +0 -79
- autogluon/timeseries/evaluator.py +0 -6
- autogluon/timeseries/models/chronos/pipeline/__init__.py +0 -10
- autogluon/timeseries/models/chronos/pipeline/base.py +0 -160
- autogluon/timeseries/models/chronos/pipeline/chronos.py +0 -544
- autogluon/timeseries/models/chronos/pipeline/chronos_bolt.py +0 -530
- autogluon/timeseries/models/presets.py +0 -358
- autogluon.timeseries-1.3.2b20250712-py3.9-nspkg.pth +0 -1
- autogluon.timeseries-1.3.2b20250712.dist-info/RECORD +0 -71
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info/licenses}/LICENSE +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info/licenses}/NOTICE +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/namespace_packages.txt +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/top_level.txt +0 -0
- {autogluon.timeseries-1.3.2b20250712.dist-info → autogluon_timeseries-1.4.1b20251116.dist-info}/zip-safe +0 -0
|
@@ -5,7 +5,7 @@ import os
|
|
|
5
5
|
import pprint
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any,
|
|
8
|
+
from typing import Any, Literal, Optional, Type, Union, cast
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import pandas as pd
|
|
@@ -21,11 +21,10 @@ 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
|
|
24
|
-
from autogluon.timeseries.configs import
|
|
25
|
-
from autogluon.timeseries.dataset
|
|
24
|
+
from autogluon.timeseries.configs import get_predictor_presets
|
|
25
|
+
from autogluon.timeseries.dataset import TimeSeriesDataFrame
|
|
26
26
|
from autogluon.timeseries.learner import TimeSeriesLearner
|
|
27
27
|
from autogluon.timeseries.metrics import TimeSeriesScorer, check_get_evaluation_metric
|
|
28
|
-
from autogluon.timeseries.splitter import ExpandingWindowSplitter
|
|
29
28
|
from autogluon.timeseries.trainer import TimeSeriesTrainer
|
|
30
29
|
from autogluon.timeseries.utils.forecast import make_future_data_frame
|
|
31
30
|
|
|
@@ -93,15 +92,15 @@ class TimeSeriesPredictor:
|
|
|
93
92
|
eval_metric_seasonal_period : int, optional
|
|
94
93
|
Seasonal period used to compute some evaluation metrics such as mean absolute scaled error (MASE). Defaults to
|
|
95
94
|
``None``, in which case the seasonal period is computed based on the data frequency.
|
|
96
|
-
horizon_weight :
|
|
97
|
-
Weight assigned to each time step in the forecast horizon when computing the
|
|
98
|
-
must be a list with
|
|
99
|
-
AutoGluon will automatically normalize the weights so that they sum up to
|
|
100
|
-
time steps in the forecast horizon have the same weight, which is equivalent to setting
|
|
95
|
+
horizon_weight : list[float], optional
|
|
96
|
+
Weight assigned to each time step in the forecast horizon when computing the ``eval_metric``. If provided, this
|
|
97
|
+
must be a list with ``prediction_length`` non-negative values, where at least some values are greater than zero.
|
|
98
|
+
AutoGluon will automatically normalize the weights so that they sum up to ``prediction_length``. By default, all
|
|
99
|
+
time steps in the forecast horizon have the same weight, which is equivalent to setting ``horizon_weight = [1] * prediction_length``.
|
|
101
100
|
|
|
102
101
|
This parameter only affects model selection and ensemble construction; it has no effect on the loss function of
|
|
103
102
|
the individual forecasting models.
|
|
104
|
-
known_covariates_names:
|
|
103
|
+
known_covariates_names: list[str], optional
|
|
105
104
|
Names of the covariates that are known in advance for all time steps in the forecast horizon. These are also
|
|
106
105
|
known as dynamic features, exogenous variables, additional regressors or related time series. Examples of such
|
|
107
106
|
covariates include holidays, promotions or weather forecasts.
|
|
@@ -111,7 +110,7 @@ class TimeSeriesPredictor:
|
|
|
111
110
|
- :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`, :meth:`~autogluon.timeseries.TimeSeriesPredictor.evaluate`, and :meth:`~autogluon.timeseries.TimeSeriesPredictor.leaderboard` will expect a dataframe with columns listed in ``known_covariates_names`` (in addition to the ``target`` column).
|
|
112
111
|
- :meth:`~autogluon.timeseries.TimeSeriesPredictor.predict` will expect an additional keyword argument ``known_covariates`` containing the future values of the known covariates in ``TimeSeriesDataFrame`` format.
|
|
113
112
|
|
|
114
|
-
quantile_levels :
|
|
113
|
+
quantile_levels : list[float], optional
|
|
115
114
|
List of increasing decimals that specifies which quantiles should be estimated when making distributional
|
|
116
115
|
forecasts. Defaults to ``[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]``.
|
|
117
116
|
path : str or pathlib.Path, optional
|
|
@@ -127,8 +126,8 @@ class TimeSeriesPredictor:
|
|
|
127
126
|
Whether to save the logs into a file for later reference
|
|
128
127
|
log_file_path: Union[str, Path], default = "auto"
|
|
129
128
|
File path to save the logs.
|
|
130
|
-
If auto, logs will be saved under
|
|
131
|
-
Will be ignored if
|
|
129
|
+
If auto, logs will be saved under ``predictor_path/logs/predictor_log.txt``.
|
|
130
|
+
Will be ignored if ``log_to_file`` is set to False
|
|
132
131
|
cache_predictions : bool, default = True
|
|
133
132
|
If True, the predictor will cache and reuse the predictions made by individual models whenever
|
|
134
133
|
:meth:`~autogluon.timeseries.TimeSeriesPredictor.predict`, :meth:`~autogluon.timeseries.TimeSeriesPredictor.leaderboard`,
|
|
@@ -147,17 +146,17 @@ class TimeSeriesPredictor:
|
|
|
147
146
|
def __init__(
|
|
148
147
|
self,
|
|
149
148
|
target: Optional[str] = None,
|
|
150
|
-
known_covariates_names: Optional[
|
|
149
|
+
known_covariates_names: Optional[list[str]] = None,
|
|
151
150
|
prediction_length: int = 1,
|
|
152
151
|
freq: Optional[str] = None,
|
|
153
152
|
eval_metric: Union[str, TimeSeriesScorer, None] = None,
|
|
154
153
|
eval_metric_seasonal_period: Optional[int] = None,
|
|
155
|
-
horizon_weight: Optional[
|
|
154
|
+
horizon_weight: Optional[list[float]] = None,
|
|
156
155
|
path: Optional[Union[str, Path]] = None,
|
|
157
156
|
verbosity: int = 2,
|
|
158
157
|
log_to_file: bool = True,
|
|
159
158
|
log_file_path: Union[str, Path] = "auto",
|
|
160
|
-
quantile_levels: Optional[
|
|
159
|
+
quantile_levels: Optional[list[float]] = None,
|
|
161
160
|
cache_predictions: bool = True,
|
|
162
161
|
label: Optional[str] = None,
|
|
163
162
|
**kwargs,
|
|
@@ -221,20 +220,6 @@ class TimeSeriesPredictor:
|
|
|
221
220
|
ensemble_model_type=kwargs.pop("ensemble_model_type", None),
|
|
222
221
|
)
|
|
223
222
|
|
|
224
|
-
if "ignore_time_index" in kwargs:
|
|
225
|
-
raise TypeError(
|
|
226
|
-
"`ignore_time_index` argument to TimeSeriesPredictor.__init__() has been deprecated.\n"
|
|
227
|
-
"If your data has irregular timestamps, please either 1) specify the desired regular frequency when "
|
|
228
|
-
"creating the predictor as `TimeSeriesPredictor(freq=...)` or 2) manually convert timestamps to "
|
|
229
|
-
"regular frequency with `data.convert_frequency(freq=...)`."
|
|
230
|
-
)
|
|
231
|
-
for k in ["learner_type", "learner_kwargs"]:
|
|
232
|
-
if k in kwargs:
|
|
233
|
-
val = kwargs.pop(k)
|
|
234
|
-
logger.warning(
|
|
235
|
-
f"Passing `{k}` to TimeSeriesPredictor has been deprecated and will be removed in v1.4. "
|
|
236
|
-
f"The provided value {val} will be ignored."
|
|
237
|
-
)
|
|
238
223
|
if len(kwargs) > 0:
|
|
239
224
|
for key in kwargs:
|
|
240
225
|
raise TypeError(f"TimeSeriesPredictor.__init__() got an unexpected keyword argument '{key}'")
|
|
@@ -296,7 +281,10 @@ class TimeSeriesPredictor:
|
|
|
296
281
|
df: TimeSeriesDataFrame = self._to_data_frame(data, name=name)
|
|
297
282
|
if not pd.api.types.is_numeric_dtype(df[self.target]):
|
|
298
283
|
raise ValueError(f"Target column {name}['{self.target}'] has a non-numeric dtype {df[self.target].dtype}")
|
|
284
|
+
# Assign makes a copy, so future operations can be performed in-place
|
|
299
285
|
df = df.assign(**{self.target: df[self.target].astype("float64")})
|
|
286
|
+
df.replace(to_replace=[float("-inf"), float("inf")], value=float("nan"), inplace=True)
|
|
287
|
+
|
|
300
288
|
# MultiIndex.is_monotonic_increasing checks if index is sorted by ["item_id", "timestamp"]
|
|
301
289
|
if not df.index.is_monotonic_increasing:
|
|
302
290
|
df = df.sort_index()
|
|
@@ -414,7 +402,9 @@ class TimeSeriesPredictor:
|
|
|
414
402
|
)
|
|
415
403
|
train_data = train_data.query("item_id not in @too_short_items")
|
|
416
404
|
|
|
417
|
-
all_nan_items = train_data.item_ids[
|
|
405
|
+
all_nan_items = train_data.item_ids[
|
|
406
|
+
train_data[self.target].isna().groupby(TimeSeriesDataFrame.ITEMID, sort=False).all()
|
|
407
|
+
]
|
|
418
408
|
if len(all_nan_items) > 0:
|
|
419
409
|
logger.info(f"\tRemoving {len(all_nan_items)} time series consisting of only NaN values from train_data.")
|
|
420
410
|
train_data = train_data.query("item_id not in @all_nan_items")
|
|
@@ -429,16 +419,16 @@ class TimeSeriesPredictor:
|
|
|
429
419
|
)
|
|
430
420
|
return train_data
|
|
431
421
|
|
|
432
|
-
@apply_presets(
|
|
422
|
+
@apply_presets(get_predictor_presets())
|
|
433
423
|
def fit(
|
|
434
424
|
self,
|
|
435
425
|
train_data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
|
|
436
426
|
tuning_data: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
|
|
437
427
|
time_limit: Optional[int] = None,
|
|
438
428
|
presets: Optional[str] = None,
|
|
439
|
-
hyperparameters: Optional[Union[str,
|
|
440
|
-
hyperparameter_tune_kwargs: Optional[Union[str,
|
|
441
|
-
excluded_model_types: Optional[
|
|
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,
|
|
442
432
|
num_val_windows: int = 1,
|
|
443
433
|
val_step_size: Optional[int] = None,
|
|
444
434
|
refit_every_n_windows: Optional[int] = 1,
|
|
@@ -476,23 +466,23 @@ class TimeSeriesPredictor:
|
|
|
476
466
|
|
|
477
467
|
data.static_features["store_id"] = data.static_features["store_id"].astype("category")
|
|
478
468
|
|
|
479
|
-
If provided data is a
|
|
480
|
-
If a
|
|
469
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
470
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
481
471
|
tuning_data : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str], optional
|
|
482
472
|
Data reserved for model selection and hyperparameter tuning, rather than training individual models. Also
|
|
483
473
|
used to compute the validation scores. Note that only the last ``prediction_length`` time steps of each
|
|
484
474
|
time series are used for computing the validation score.
|
|
485
475
|
|
|
486
476
|
If ``tuning_data`` is provided, multi-window backtesting on training data will be disabled, the
|
|
487
|
-
|
|
477
|
+
``num_val_windows`` will be set to ``0``, and ``refit_full`` will be set to ``False``.
|
|
488
478
|
|
|
489
479
|
Leaving this argument empty and letting AutoGluon automatically generate the validation set from
|
|
490
480
|
``train_data`` is a good default.
|
|
491
481
|
|
|
492
482
|
The names and dtypes of columns and static features in ``tuning_data`` must match the ``train_data``.
|
|
493
483
|
|
|
494
|
-
If provided data is a
|
|
495
|
-
If a
|
|
484
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
485
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
496
486
|
time_limit : int, optional
|
|
497
487
|
Approximately how long :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit` will run (wall-clock time in
|
|
498
488
|
seconds). If not specified, :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit` will run until all models
|
|
@@ -525,7 +515,7 @@ class TimeSeriesPredictor:
|
|
|
525
515
|
[`1 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/configs/presets_configs.py>`_,
|
|
526
516
|
`2 <https://github.com/autogluon/autogluon/blob/stable/timeseries/src/autogluon/timeseries/models/presets.py>`_].
|
|
527
517
|
|
|
528
|
-
If no
|
|
518
|
+
If no ``presets`` are selected, user-provided values for ``hyperparameters`` will be used (defaulting to their
|
|
529
519
|
default values specified below).
|
|
530
520
|
hyperparameters : str or dict, optional
|
|
531
521
|
Determines what models are trained and what hyperparameters are used by each model.
|
|
@@ -611,7 +601,7 @@ class TimeSeriesPredictor:
|
|
|
611
601
|
"scheduler": "local",
|
|
612
602
|
},
|
|
613
603
|
)
|
|
614
|
-
excluded_model_types:
|
|
604
|
+
excluded_model_types: list[str], optional
|
|
615
605
|
Banned subset of model types to avoid training during ``fit()``, even if present in ``hyperparameters``.
|
|
616
606
|
For example, the following code will train all models included in the ``high_quality`` presets except ``DeepAR``::
|
|
617
607
|
|
|
@@ -626,7 +616,7 @@ class TimeSeriesPredictor:
|
|
|
626
616
|
of time series in ``train_data`` are long enough for the chosen number of backtests.
|
|
627
617
|
|
|
628
618
|
Increasing this parameter increases the training time roughly by a factor of ``num_val_windows // refit_every_n_windows``.
|
|
629
|
-
See
|
|
619
|
+
See ``refit_every_n_windows`` and ``val_step_size`` for details.
|
|
630
620
|
|
|
631
621
|
For example, for ``prediction_length=2``, ``num_val_windows=3`` and ``val_step_size=1`` the folds are::
|
|
632
622
|
|
|
@@ -645,11 +635,11 @@ class TimeSeriesPredictor:
|
|
|
645
635
|
This argument has no effect if ``tuning_data`` is provided.
|
|
646
636
|
refit_every_n_windows: int or None, default = 1
|
|
647
637
|
When performing cross validation, each model will be retrained every ``refit_every_n_windows`` validation
|
|
648
|
-
windows, where the number of validation windows is specified by
|
|
649
|
-
default setting where
|
|
638
|
+
windows, where the number of validation windows is specified by ``num_val_windows``. Note that in the
|
|
639
|
+
default setting where ``num_val_windows=1``, this argument has no effect.
|
|
650
640
|
|
|
651
641
|
If set to ``None``, models will only be fit once for the first (oldest) validation window. By default,
|
|
652
|
-
|
|
642
|
+
``refit_every_n_windows=1``, i.e., all models will be refit for each validation window.
|
|
653
643
|
refit_full : bool, default = False
|
|
654
644
|
If True, after training is complete, AutoGluon will attempt to re-train all models using all of training
|
|
655
645
|
data (including the data initially reserved for validation). This argument has no effect if ``tuning_data``
|
|
@@ -748,10 +738,6 @@ class TimeSeriesPredictor:
|
|
|
748
738
|
train_data, num_val_windows=num_val_windows, val_step_size=val_step_size
|
|
749
739
|
)
|
|
750
740
|
|
|
751
|
-
val_splitter = ExpandingWindowSplitter(
|
|
752
|
-
prediction_length=self.prediction_length, num_val_windows=num_val_windows, val_step_size=val_step_size
|
|
753
|
-
)
|
|
754
|
-
|
|
755
741
|
time_left = None if time_limit is None else time_limit - (time.time() - time_start)
|
|
756
742
|
self._learner.fit(
|
|
757
743
|
train_data=train_data,
|
|
@@ -761,7 +747,8 @@ class TimeSeriesPredictor:
|
|
|
761
747
|
excluded_model_types=excluded_model_types,
|
|
762
748
|
time_limit=time_left,
|
|
763
749
|
verbosity=verbosity,
|
|
764
|
-
|
|
750
|
+
num_val_windows=num_val_windows,
|
|
751
|
+
val_step_size=val_step_size,
|
|
765
752
|
refit_every_n_windows=refit_every_n_windows,
|
|
766
753
|
skip_model_selection=skip_model_selection,
|
|
767
754
|
enable_ensemble=enable_ensemble,
|
|
@@ -776,7 +763,7 @@ class TimeSeriesPredictor:
|
|
|
776
763
|
self.save()
|
|
777
764
|
return self
|
|
778
765
|
|
|
779
|
-
def model_names(self) ->
|
|
766
|
+
def model_names(self) -> list[str]:
|
|
780
767
|
"""Returns the list of model names trained by this predictor object."""
|
|
781
768
|
return self._trainer.get_model_names()
|
|
782
769
|
|
|
@@ -798,8 +785,8 @@ class TimeSeriesPredictor:
|
|
|
798
785
|
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
799
786
|
the predictor.
|
|
800
787
|
|
|
801
|
-
If provided data is a
|
|
802
|
-
If a
|
|
788
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
789
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
803
790
|
known_covariates : Union[TimeSeriesDataFrame, pd.DataFrame, Path, str], optional
|
|
804
791
|
If ``known_covariates_names`` were specified when creating the predictor, it is necessary to provide the
|
|
805
792
|
values of the known covariates for each time series during the forecast horizon. Specifically:
|
|
@@ -809,7 +796,7 @@ class TimeSeriesPredictor:
|
|
|
809
796
|
- Must include ``timestamp`` values for the full forecast horizon (i.e., ``prediction_length`` time steps) following the end of each series in the input ``data``.
|
|
810
797
|
|
|
811
798
|
You can use :meth:`autogluon.timeseries.TimeSeriesPredictor.make_future_data_frame` to generate a template
|
|
812
|
-
containing the required ``item_id`` and ``timestamp`` combinations for the
|
|
799
|
+
containing the required ``item_id`` and ``timestamp`` combinations for the ``known_covariates`` dataframe.
|
|
813
800
|
|
|
814
801
|
See example below.
|
|
815
802
|
model : str, optional
|
|
@@ -863,17 +850,17 @@ class TimeSeriesPredictor:
|
|
|
863
850
|
use_cache=use_cache,
|
|
864
851
|
random_seed=random_seed,
|
|
865
852
|
)
|
|
866
|
-
return cast(TimeSeriesDataFrame, predictions.reindex(original_item_id_order, level=ITEMID))
|
|
853
|
+
return cast(TimeSeriesDataFrame, predictions.reindex(original_item_id_order, level=TimeSeriesDataFrame.ITEMID))
|
|
867
854
|
|
|
868
855
|
def evaluate(
|
|
869
856
|
self,
|
|
870
857
|
data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
|
|
871
858
|
model: Optional[str] = None,
|
|
872
|
-
metrics: Optional[Union[str, TimeSeriesScorer,
|
|
859
|
+
metrics: Optional[Union[str, TimeSeriesScorer, list[Union[str, TimeSeriesScorer]]]] = None,
|
|
873
860
|
cutoff: Optional[int] = None,
|
|
874
861
|
display: bool = False,
|
|
875
862
|
use_cache: bool = True,
|
|
876
|
-
) ->
|
|
863
|
+
) -> dict[str, float]:
|
|
877
864
|
"""Evaluate the forecast accuracy for given dataset.
|
|
878
865
|
|
|
879
866
|
This method measures the forecast accuracy using the last ``self.prediction_length`` time steps of each time
|
|
@@ -899,12 +886,12 @@ class TimeSeriesPredictor:
|
|
|
899
886
|
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
900
887
|
the predictor.
|
|
901
888
|
|
|
902
|
-
If provided data is a
|
|
903
|
-
If a
|
|
889
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
890
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
904
891
|
model : str, optional
|
|
905
892
|
Name of the model that you would like to evaluate. By default, the best model during training
|
|
906
893
|
(with highest validation score) will be used.
|
|
907
|
-
metrics : str, TimeSeriesScorer or
|
|
894
|
+
metrics : str, TimeSeriesScorer or list[Union[str, TimeSeriesScorer]], optional
|
|
908
895
|
Metric or a list of metrics to compute scores with. Defaults to ``self.eval_metric``. Supports both
|
|
909
896
|
metric names as strings and custom metrics based on TimeSeriesScorer.
|
|
910
897
|
cutoff : int, optional
|
|
@@ -920,7 +907,7 @@ class TimeSeriesPredictor:
|
|
|
920
907
|
|
|
921
908
|
Returns
|
|
922
909
|
-------
|
|
923
|
-
scores_dict :
|
|
910
|
+
scores_dict : dict[str, float]
|
|
924
911
|
Dictionary where keys = metrics, values = performance along each metric. For consistency, error metrics
|
|
925
912
|
will have their signs flipped to obey this convention. For example, negative MAPE values will be reported.
|
|
926
913
|
To get the ``eval_metric`` score, do ``output[predictor.eval_metric.name]``.
|
|
@@ -940,7 +927,7 @@ class TimeSeriesPredictor:
|
|
|
940
927
|
data: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
|
|
941
928
|
model: Optional[str] = None,
|
|
942
929
|
metric: Optional[Union[str, TimeSeriesScorer]] = None,
|
|
943
|
-
features: Optional[
|
|
930
|
+
features: Optional[list[str]] = None,
|
|
944
931
|
time_limit: Optional[float] = None,
|
|
945
932
|
method: Literal["naive", "permutation"] = "permutation",
|
|
946
933
|
subsample_size: int = 50,
|
|
@@ -976,8 +963,8 @@ class TimeSeriesPredictor:
|
|
|
976
963
|
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
977
964
|
the predictor.
|
|
978
965
|
|
|
979
|
-
If provided data is a
|
|
980
|
-
If a
|
|
966
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
967
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
981
968
|
|
|
982
969
|
If ``data`` is not provided, then validation (tuning) data provided during training (or the held out data used for
|
|
983
970
|
validation if ``tuning_data`` was not explicitly provided ``fit()``) will be used.
|
|
@@ -987,7 +974,7 @@ class TimeSeriesPredictor:
|
|
|
987
974
|
metric : str or TimeSeriesScorer, optional
|
|
988
975
|
Metric to be used for computing feature importance. If None, the ``eval_metric`` specified during initialization of
|
|
989
976
|
the ``TimeSeriesPredictor`` will be used.
|
|
990
|
-
features :
|
|
977
|
+
features : list[str], optional
|
|
991
978
|
List of feature names that feature importances are calculated for and returned. By default, all feature importances
|
|
992
979
|
will be returned.
|
|
993
980
|
method : {"permutation", "naive"}, default = "permutation"
|
|
@@ -1003,12 +990,12 @@ class TimeSeriesPredictor:
|
|
|
1003
990
|
permutation importance.
|
|
1004
991
|
|
|
1005
992
|
subsample_size : int, default = 50
|
|
1006
|
-
The number of items to sample from
|
|
1007
|
-
the feature importance scores. Runtime linearly scales with
|
|
993
|
+
The number of items to sample from ``data`` when computing feature importance. Larger values increase the accuracy of
|
|
994
|
+
the feature importance scores. Runtime linearly scales with ``subsample_size``.
|
|
1008
995
|
time_limit : float, optional
|
|
1009
996
|
Time in seconds to limit the calculation of feature importance. If None, feature importance will calculate without early stopping.
|
|
1010
997
|
If ``method="permutation"``, a minimum of 1 full shuffle set will always be evaluated. If a shuffle set evaluation takes longer than
|
|
1011
|
-
``time_limit``, the method will take the length of a shuffle set evaluation to return regardless of the
|
|
998
|
+
``time_limit``, the method will take the length of a shuffle set evaluation to return regardless of the ``time_limit``.
|
|
1012
999
|
num_iterations : int, optional
|
|
1013
1000
|
The number of different iterations of the data that are evaluated. If ``method="permutation"``, this will be interpreted
|
|
1014
1001
|
as the number of shuffle sets (equivalent to ``num_shuffle_sets`` in :meth:`TabularPredictor.feature_importance`). If ``method="naive"``, the
|
|
@@ -1093,7 +1080,7 @@ class TimeSeriesPredictor:
|
|
|
1093
1080
|
|
|
1094
1081
|
.. warning::
|
|
1095
1082
|
|
|
1096
|
-
:meth:`autogluon.timeseries.TimeSeriesPredictor.load` uses
|
|
1083
|
+
:meth:`autogluon.timeseries.TimeSeriesPredictor.load` uses ``pickle`` module implicitly, which is known to
|
|
1097
1084
|
be insecure. It is possible to construct malicious pickle data which will execute arbitrary code during
|
|
1098
1085
|
unpickling. Never load data that could have come from an untrusted source, or that could have been tampered
|
|
1099
1086
|
with. **Only load data you trust.**
|
|
@@ -1165,7 +1152,7 @@ class TimeSeriesPredictor:
|
|
|
1165
1152
|
self._learner = tmp_learner
|
|
1166
1153
|
self._save_version_file()
|
|
1167
1154
|
|
|
1168
|
-
def info(self) ->
|
|
1155
|
+
def info(self) -> dict[str, Any]:
|
|
1169
1156
|
"""Returns a dictionary of objects each describing an attribute of the training process and trained models."""
|
|
1170
1157
|
return self._learner.get_info(include_model_info=True)
|
|
1171
1158
|
|
|
@@ -1179,8 +1166,8 @@ class TimeSeriesPredictor:
|
|
|
1179
1166
|
return self._trainer.get_model_best()
|
|
1180
1167
|
|
|
1181
1168
|
def persist(
|
|
1182
|
-
self, models: Union[Literal["all", "best"],
|
|
1183
|
-
) ->
|
|
1169
|
+
self, models: Union[Literal["all", "best"], list[str]] = "best", with_ancestors: bool = True
|
|
1170
|
+
) -> list[str]:
|
|
1184
1171
|
"""Persist models in memory for reduced inference latency. This is particularly important if the models are being used for online
|
|
1185
1172
|
inference where low latency is critical. If models are not persisted in memory, they are loaded from disk every time they are
|
|
1186
1173
|
asked to make predictions. This is especially cumbersome for large deep learning based models which have to be loaded into
|
|
@@ -1191,30 +1178,30 @@ class TimeSeriesPredictor:
|
|
|
1191
1178
|
models : list of str or str, default = 'best'
|
|
1192
1179
|
Model names of models to persist.
|
|
1193
1180
|
If 'best' then the model with the highest validation score is persisted (this is the model used for prediction by default).
|
|
1194
|
-
If 'all' then all models are persisted. Valid models are listed in this
|
|
1181
|
+
If 'all' then all models are persisted. Valid models are listed in this ``predictor`` by calling ``predictor.model_names()``.
|
|
1195
1182
|
with_ancestors : bool, default = True
|
|
1196
1183
|
If True, all ancestor models of the provided models will also be persisted.
|
|
1197
|
-
If False, ensemble models will not have the models they depend on persisted unless those models were specified in
|
|
1184
|
+
If False, ensemble models will not have the models they depend on persisted unless those models were specified in ``models``.
|
|
1198
1185
|
This will slow down inference as the ancestor models will still need to be loaded from disk for each predict call.
|
|
1199
1186
|
Only relevant for ensemble models.
|
|
1200
1187
|
|
|
1201
1188
|
Returns
|
|
1202
1189
|
-------
|
|
1203
|
-
list_of_models :
|
|
1190
|
+
list_of_models : list[str]
|
|
1204
1191
|
List of persisted model names.
|
|
1205
1192
|
"""
|
|
1206
1193
|
return self._learner.persist_trainer(models=models, with_ancestors=with_ancestors)
|
|
1207
1194
|
|
|
1208
|
-
def unpersist(self) ->
|
|
1195
|
+
def unpersist(self) -> list[str]:
|
|
1209
1196
|
"""Unpersist models in memory for reduced memory usage. If models are not persisted in memory, they are loaded from
|
|
1210
1197
|
disk every time they are asked to make predictions.
|
|
1211
1198
|
|
|
1212
1199
|
Note: Another way to reset the predictor and unpersist models is to reload the predictor from disk
|
|
1213
|
-
via
|
|
1200
|
+
via ``predictor = TimeSeriesPredictor.load(predictor.path)``.
|
|
1214
1201
|
|
|
1215
1202
|
Returns
|
|
1216
1203
|
-------
|
|
1217
|
-
list_of_models :
|
|
1204
|
+
list_of_models : list[str]
|
|
1218
1205
|
List of unpersisted model names.
|
|
1219
1206
|
"""
|
|
1220
1207
|
return self._learner.unpersist_trainer()
|
|
@@ -1224,7 +1211,7 @@ class TimeSeriesPredictor:
|
|
|
1224
1211
|
data: Optional[Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]] = None,
|
|
1225
1212
|
cutoff: Optional[int] = None,
|
|
1226
1213
|
extra_info: bool = False,
|
|
1227
|
-
extra_metrics: Optional[
|
|
1214
|
+
extra_metrics: Optional[list[Union[str, TimeSeriesScorer]]] = None,
|
|
1228
1215
|
display: bool = False,
|
|
1229
1216
|
use_cache: bool = True,
|
|
1230
1217
|
**kwargs,
|
|
@@ -1257,27 +1244,27 @@ class TimeSeriesPredictor:
|
|
|
1257
1244
|
The names and dtypes of columns and static features in ``data`` must match the ``train_data`` used to train
|
|
1258
1245
|
the predictor.
|
|
1259
1246
|
|
|
1260
|
-
If provided data is a
|
|
1261
|
-
If a
|
|
1247
|
+
If provided data is a ``pandas.DataFrame``, AutoGluon will attempt to convert it to a ``TimeSeriesDataFrame``.
|
|
1248
|
+
If a ``str`` or a ``Path`` is provided, AutoGluon will attempt to load this file.
|
|
1262
1249
|
cutoff : int, optional
|
|
1263
1250
|
A *negative* integer less than or equal to ``-1 * prediction_length`` denoting the time step in ``data``
|
|
1264
1251
|
where the forecast evaluation starts, i.e., time series are evaluated from the ``-cutoff``-th to the
|
|
1265
1252
|
``-cutoff + prediction_length``-th time step. Defaults to ``-1 * prediction_length``, using the last
|
|
1266
1253
|
``prediction_length`` time steps of each time series for evaluation.
|
|
1267
1254
|
extra_info : bool, default = False
|
|
1268
|
-
If True, the leaderboard will contain an additional column
|
|
1269
|
-
by each model during training. An empty dictionary
|
|
1255
|
+
If True, the leaderboard will contain an additional column ``hyperparameters`` with the hyperparameters used
|
|
1256
|
+
by each model during training. An empty dictionary ``{}`` means that the model was trained with default
|
|
1270
1257
|
hyperparameters.
|
|
1271
|
-
extra_metrics :
|
|
1258
|
+
extra_metrics : list[Union[str, TimeSeriesScorer]], optional
|
|
1272
1259
|
A list of metrics to calculate scores for and include in the output DataFrame.
|
|
1273
1260
|
|
|
1274
|
-
Only valid when
|
|
1275
|
-
calculate the
|
|
1261
|
+
Only valid when ``data`` is specified. The scores refer to the scores on ``data`` (same data as used to
|
|
1262
|
+
calculate the ``score_test`` column).
|
|
1276
1263
|
|
|
1277
|
-
This list can contain any values which would also be valid for
|
|
1264
|
+
This list can contain any values which would also be valid for ``eval_metric`` when creating a :class:`~autogluon.timeseries.TimeSeriesPredictor`.
|
|
1278
1265
|
|
|
1279
|
-
For each provided
|
|
1280
|
-
the value of the metric computed on
|
|
1266
|
+
For each provided ``metric``, a column with name ``str(metric)`` will be added to the leaderboard, containing
|
|
1267
|
+
the value of the metric computed on ``data``.
|
|
1281
1268
|
display : bool, default = False
|
|
1282
1269
|
If True, the leaderboard DataFrame will be printed.
|
|
1283
1270
|
use_cache : bool, default = True
|
|
@@ -1315,7 +1302,7 @@ class TimeSeriesPredictor:
|
|
|
1315
1302
|
return leaderboard
|
|
1316
1303
|
|
|
1317
1304
|
def make_future_data_frame(self, data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str]) -> pd.DataFrame:
|
|
1318
|
-
"""Generate a dataframe with the
|
|
1305
|
+
"""Generate a dataframe with the ``item_id`` and ``timestamp`` values corresponding to the forecast horizon.
|
|
1319
1306
|
|
|
1320
1307
|
Parameters
|
|
1321
1308
|
----------
|
|
@@ -1325,8 +1312,8 @@ class TimeSeriesPredictor:
|
|
|
1325
1312
|
Returns
|
|
1326
1313
|
-------
|
|
1327
1314
|
forecast_horizon : pd.DataFrame
|
|
1328
|
-
Data frame with columns
|
|
1329
|
-
in
|
|
1315
|
+
Data frame with columns ``item_id`` and ``timestamp`` corresponding to the forecast horizon. For each item ID
|
|
1316
|
+
in ``data``, ``forecast_horizon`` will contain the timestamps for the next ``prediction_length`` time steps,
|
|
1330
1317
|
following the end of each series in the input data.
|
|
1331
1318
|
|
|
1332
1319
|
Examples
|
|
@@ -1352,7 +1339,7 @@ class TimeSeriesPredictor:
|
|
|
1352
1339
|
data = self._check_and_prepare_data_frame(data)
|
|
1353
1340
|
return make_future_data_frame(data, prediction_length=self.prediction_length, freq=self.freq)
|
|
1354
1341
|
|
|
1355
|
-
def fit_summary(self, verbosity: int = 1) ->
|
|
1342
|
+
def fit_summary(self, verbosity: int = 1) -> dict[str, Any]:
|
|
1356
1343
|
"""Output summary of information about models produced during
|
|
1357
1344
|
:meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`.
|
|
1358
1345
|
|
|
@@ -1363,7 +1350,7 @@ class TimeSeriesPredictor:
|
|
|
1363
1350
|
|
|
1364
1351
|
Returns
|
|
1365
1352
|
-------
|
|
1366
|
-
summary_dict :
|
|
1353
|
+
summary_dict : dict[str, Any]
|
|
1367
1354
|
Dict containing various detailed information. We do not recommend directly printing this dict as it may
|
|
1368
1355
|
be very large.
|
|
1369
1356
|
"""
|
|
@@ -1402,7 +1389,7 @@ class TimeSeriesPredictor:
|
|
|
1402
1389
|
print("****************** End of fit() summary ******************")
|
|
1403
1390
|
return results
|
|
1404
1391
|
|
|
1405
|
-
def refit_full(self, model: str = "all", set_best_to_refit_full: bool = True) ->
|
|
1392
|
+
def refit_full(self, model: str = "all", set_best_to_refit_full: bool = True) -> dict[str, str]:
|
|
1406
1393
|
"""Retrain model on all of the data (training + validation).
|
|
1407
1394
|
|
|
1408
1395
|
This method can only be used if no ``tuning_data`` was passed to :meth:`~autogluon.timeseries.TimeSeriesPredictor.fit`.
|
|
@@ -1480,7 +1467,7 @@ class TimeSeriesPredictor:
|
|
|
1480
1467
|
train_data = trainer.load_train_data()
|
|
1481
1468
|
val_data = trainer.load_val_data()
|
|
1482
1469
|
base_model_names = trainer.get_model_names(level=0)
|
|
1483
|
-
pred_proba_dict_val:
|
|
1470
|
+
pred_proba_dict_val: dict[str, list[TimeSeriesDataFrame]] = {
|
|
1484
1471
|
model_name: trainer._get_model_oof_predictions(model_name)
|
|
1485
1472
|
for model_name in base_model_names
|
|
1486
1473
|
if "_FULL" not in model_name
|
|
@@ -1494,8 +1481,8 @@ class TimeSeriesPredictor:
|
|
|
1494
1481
|
base_model_names, data=past_data, known_covariates=known_covariates
|
|
1495
1482
|
)
|
|
1496
1483
|
|
|
1497
|
-
y_val:
|
|
1498
|
-
select_target(df) for df in trainer.
|
|
1484
|
+
y_val: list[TimeSeriesDataFrame] = [
|
|
1485
|
+
select_target(df) for df in trainer._get_validation_windows(train_data=train_data, val_data=val_data)
|
|
1499
1486
|
]
|
|
1500
1487
|
y_test: TimeSeriesDataFrame = select_target(test_data)
|
|
1501
1488
|
|
|
@@ -1517,8 +1504,8 @@ class TimeSeriesPredictor:
|
|
|
1517
1504
|
self,
|
|
1518
1505
|
data: Union[TimeSeriesDataFrame, pd.DataFrame, Path, str],
|
|
1519
1506
|
predictions: Optional[TimeSeriesDataFrame] = None,
|
|
1520
|
-
quantile_levels: Optional[
|
|
1521
|
-
item_ids: Optional[
|
|
1507
|
+
quantile_levels: Optional[list[float]] = None,
|
|
1508
|
+
item_ids: Optional[list[Union[str, int]]] = None,
|
|
1522
1509
|
max_num_item_ids: int = 8,
|
|
1523
1510
|
max_history_length: Optional[int] = None,
|
|
1524
1511
|
point_forecast_column: Optional[str] = None,
|
|
@@ -1532,10 +1519,10 @@ class TimeSeriesPredictor:
|
|
|
1532
1519
|
Observed time series data.
|
|
1533
1520
|
predictions : TimeSeriesDataFrame, optional
|
|
1534
1521
|
Predictions generated by calling :meth:`~autogluon.timeseries.TimeSeriesPredictor.predict`.
|
|
1535
|
-
quantile_levels :
|
|
1522
|
+
quantile_levels : list[float], optional
|
|
1536
1523
|
Quantile levels for which to plot the prediction intervals. Defaults to lowest & highest quantile levels
|
|
1537
1524
|
available in ``predictions``.
|
|
1538
|
-
item_ids :
|
|
1525
|
+
item_ids : list[Union[str, int]], optional
|
|
1539
1526
|
If provided, plots will only be generated for time series with these item IDs. By default (if set to
|
|
1540
1527
|
``None``), item IDs are selected randomly. In either case, plots are generated for at most
|
|
1541
1528
|
``max_num_item_ids`` time series.
|
|
@@ -1547,8 +1534,8 @@ class TimeSeriesPredictor:
|
|
|
1547
1534
|
Name of the column in ``predictions`` that will be plotted as the point forecast. Defaults to ``"0.5"``,
|
|
1548
1535
|
if this column is present in ``predictions``, otherwise ``"mean"``.
|
|
1549
1536
|
matplotlib_rc_params : dict, optional
|
|
1550
|
-
Dictionary describing the plot style that will be passed to
|
|
1551
|
-
See
|
|
1537
|
+
Dictionary describing the plot style that will be passed to `matplotlib.pyplot.rc_context <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.rc_context.html>`_.
|
|
1538
|
+
See `matplotlib documentation <https://matplotlib.org/stable/users/explain/customizing.html#the-default-matplotlibrc-file>`_ for the list of available options.
|
|
1552
1539
|
"""
|
|
1553
1540
|
import matplotlib.pyplot as plt
|
|
1554
1541
|
|
|
@@ -1618,7 +1605,7 @@ class TimeSeriesPredictor:
|
|
|
1618
1605
|
for q in quantile_levels:
|
|
1619
1606
|
ax.fill_between(forecast.index, point_forecast, forecast[str(q)], color="C1", alpha=0.2)
|
|
1620
1607
|
if len(axes) > len(item_ids):
|
|
1621
|
-
axes[len(item_ids)].set_axis_off()
|
|
1622
|
-
handles, labels = axes[0].get_legend_handles_labels()
|
|
1608
|
+
axes[len(item_ids)].set_axis_off() # type: ignore
|
|
1609
|
+
handles, labels = axes[0].get_legend_handles_labels() # type: ignore
|
|
1623
1610
|
fig.legend(handles, labels, bbox_to_anchor=(0.5, 0.0), ncols=len(handles))
|
|
1624
1611
|
return fig
|