Multi-series forecasting con Python y Skforecast

Si te gusta  Skforecast ,  ayúdanos dándonos una estrella en   GitHub! ⭐️

Multi-series forecasting con Python y Skforecast

Joaquín Amat Rodrigo, Javier Escobar Ortiz
Octubre 2022

Forecasting multiseries

En el forecasting de series temporales univariantes, una única serie temporal se modela como una combinación lineal o no lineal de sus lags. Es decir, los valores pasados de la serie se utilizan para predecir su comportamiento futuro. En el forecasting multiserie, dos o más series temporales se modelan conjuntamente mediante un único modelo. Se pueden distinguir dos estrategias:

Múltiples series temporales no multivariantes

En esta situación, cada serie temporal es independiente de las demás o, dicho de otro modo, los valores pasados de una serie no se utilizan como predictores de las otras series. ¿Por qué es útil entonces modelar todo junto? Aunque las series no dependen unas de otras, pueden seguir el mismo patrón intrínseco en cuanto a sus valores pasados y futuros. Por ejemplo, en una misma tienda, las ventas de los productos A y B pueden no estar relacionadas, pero siguen la misma dinámica, la de la tienda.

Para predecir los siguientes n steps, se sigue una estrategia recurisva, recursive multi-step forecasting. La única diferencia es que se debe indicar el nombre, o nivel, de la serie para la que se quieren realizar las predicciones.

Múltiples series temporales multivariantes

Todas las series se modelan teniendo en cuenta que cada serie temporal depende no sólo de sus valores pasados, sino también de los valores pasados de las demás series. Se espera que el modelo no sólo aprenda la información de cada serie por separado, sino que también las relacione. Por ejemplo, las mediciones realizadas por todos los sensores (caudal, temperatura, presión...) instalados en una máquina industrial como un compresor.

  Note

La clase ForecasterAutoregMultiSeries cubre el caso de uso de series temporales no multivariantes. API Reference

ForecasterAutoregMultivariate se publicará en la próxima versión de Skforecast - stay tuned!

Ventajas y limitaciones


El forecasting multiserie no siempre superan al modelado de la serie individual. Cuál de ellas funciona mejor depende en gran medida de las características del caso de uso al que se aplican. Sin embargo, conviene tener en cuenta la siguiente heurística:

Ventajas de los modelos multiseries:

  • Es más fácil mantener y controlar un solo modelo que varios.

  • Dado que todas las series temporales se combinan durante el entrenamiento, cuando las series sean cortas (pocos datos) el modelo tendrá una mayor capacidad de aprendizaje al disponer de más observaciones.

  • Al combinar múltiples series temporales, el modelo puede aprender patrones más generalizables.

Desventajas de los modelos multiseries:

  • Si las series no siguen la misma dinámica interna, el modelo puede aprender un patrón que no represente a ninguna de ellas.

  • Las series pueden enmascararse unas a otras, por lo que el modelo puede no predecirlas todas con el mismo rendimiento.

  • Es más exigente desde el punto de vista computacional (tiempo y recursos) entrenar y realizar backtesting de un modelo grande que de varios pequeños.

Caso de uso


El objetivo de este estudio es comparar los resultados obtenidos por un modelo multiserie frente a la utilización de un modelo diferente para cada serie.

Los datos se han obtenido de Store Item Demand Forecasting Challenge. Este dataset contiene 913.000 transacciones de ventas desde el 2013-01-01 hasta el 2017-12-31 para 50 productos (SKU) en 10 tiendas. El objetivo es predecir las ventas de los próximos 7 días de 50 artículos diferentes en una tienda utilizando el historial disponible de 5 años.

Librerías

In [1]:
# Librerías
# ==============================================================================================
import numpy as np
import pandas as pd
from tqdm import tqdm
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import  HistGradientBoostingRegressor

from skforecast.ForecasterAutoregMultiSeries import ForecasterAutoregMultiSeries
from skforecast.ForecasterAutoreg import ForecasterAutoreg
from skforecast.model_selection import backtesting_forecaster
from skforecast.model_selection import grid_search_forecaster
from skforecast.model_selection_multiseries import backtesting_forecaster_multiseries
from skforecast.model_selection_multiseries import grid_search_forecaster_multiseries

Datos

In [2]:
# Descarga de datos
# ==============================================================================================
data = pd.read_csv('./train_stores_kaggle.csv')
data
Out[2]:
date store item sales
0 2013-01-01 1 1 13
1 2013-01-02 1 1 11
2 2013-01-03 1 1 14
3 2013-01-04 1 1 13
4 2013-01-05 1 1 10
... ... ... ... ...
912995 2017-12-27 10 50 63
912996 2017-12-28 10 50 59
912997 2017-12-29 10 50 74
912998 2017-12-30 10 50 62
912999 2017-12-31 10 50 82

913000 rows × 4 columns

In [3]:
# Preparación del dato
# ==============================================================================================
selected_store = 2
selected_items = data.item.unique() # All items
#selected_items = [1, 2, 3, 4 , 5] # Selection of items to reduce computation time

data = data[(data['store'] == selected_store) & (data['item'].isin(selected_items))].copy()
data['date'] = pd.to_datetime(data['date'], format='%Y-%m-%d')
data = pd.pivot_table(
            data    = data,
            values  = 'sales',
            index   = 'date',
            columns = 'item'
        )
data.columns.name = None
data.columns = [f"item_{col}" for col in data.columns]
data = data.asfreq('1D')
data = data.sort_index()
data.head(4)
Out[3]:
item_1 item_2 item_3 item_4 item_5 item_6 item_7 item_8 item_9 item_10 ... item_41 item_42 item_43 item_44 item_45 item_46 item_47 item_48 item_49 item_50
date
2013-01-01 12 41 19 21 4 34 39 49 28 51 ... 11 25 36 12 45 43 12 45 29 43
2013-01-02 16 33 32 14 6 40 47 42 21 56 ... 19 21 35 25 50 52 13 37 25 57
2013-01-03 16 46 26 12 12 41 43 46 29 46 ... 23 20 52 18 56 30 5 45 30 45
2013-01-04 20 50 34 17 16 41 44 55 32 56 ... 15 28 50 24 57 46 19 32 20 45

4 rows × 50 columns

  Warning

El modelado de 50 items puede requerir un tiempo computacional significativo. Siéntase libre de seleccionar sólo un subconjunto de items para acelerar la ejecución.

El dataset se divide en 3 particiones: una para el entrenamiento, otra para la validación y otra para test.

In [4]:
# Separación datos train-validation-test
# ======================================================================================
end_train = '2016-05-31 23:59:00'
end_val = '2017-05-31 23:59:00'

data_train = data.loc[:end_train, :].copy()
data_val   = data.loc[end_train:end_val, :].copy()
data_test  = data.loc[end_val:, :].copy()

print(f"Fechas train      : {data_train.index.min()} --- {data_train.index.max()}  (n={len(data_train)})")
print(f"Fechas validación : {data_val.index.min()} --- {data_val.index.max()}  (n={len(data_val)})")
print(f"Fechas test       : {data_test.index.min()} --- {data_test.index.max()}  (n={len(data_test)})")
Fechas train      : 2013-01-01 00:00:00 --- 2016-05-31 00:00:00  (n=1247)
Fechas validación : 2016-06-01 00:00:00 --- 2017-05-31 00:00:00  (n=365)
Fechas test       : 2017-06-01 00:00:00 --- 2017-12-31 00:00:00  (n=214)

Se dibujan cuatro de las series para comprender sus tendencias y patrones. Se recomienda encarecidamente al lector que grafique más de ellas para comprenderlas en profundidad.

In [5]:
# Gráfico series temporales
# ======================================================================================
fig, ax = plt.subplots(figsize=(9, 6))
data.iloc[:, :4].plot(
    legend   = True,
    subplots = True, 
    sharex   = True,
    title    = 'Sales of store 2',
    ax = ax, 
);
In [20]:
# Gráfico autocorrelación
# ======================================================================================
fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(9, 7), sharex=True)
axes = axes.flat
for i, col in enumerate(data.columns[:4]):
    plot_acf(data[col], ax=axes[i], lags=7*5)
    axes[i].set_title(f'{col}')
fig.tight_layout()
plt.show()

Los gráficos de autocorrelación muestran una clara asociación entre las ventas de un día y las del mismo día una semana antes. Este tipo de correlación es un indicio de que los modelos autorregresivos pueden funcionar bien.

También hay una estacionalidad semanal común entre las series. Cuanto más similar sea la dinámica entre las series, más probable será que el modelo multiseries aprenda patrones útiles.

Forecasting individual para cada item


Se entrena un modelo diferente para cada artículo de la tienda y se estima su error medio absoluto mediante backtesting.

In [21]:
# Entrenar y realizar backtesting de un modelo para cada item
# ======================================================================================
items = []
mae_values = []
predictions = {}

for i, item in enumerate(tqdm(data.columns)):

    # Definir el forecaster
    forecaster = ForecasterAutoreg(
                     regressor        = HistGradientBoostingRegressor(random_state=123),
                     lags             = 14,
                     transformer_y    = StandardScaler()
                 )

    # Backtesting forecaster
    metric, preds = backtesting_forecaster(
                        forecaster         = forecaster,
                        y                  = data[item],
                        initial_train_size = len(data_train) + len(data_val),
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        refit              = False,
                        fixed_train_size   = False,
                        verbose            = False
                    )

    items.append(item)
    mae_values.append(metric)
    predictions[item] = preds

# Resultados
uni_series_mae = pd.Series(
                     data  = mae_values,
                     index = items,
                     name  = 'uni_series_mae'
                 )
100%|██████████| 50/50 [02:03<00:00,  2.48s/it]

Forecasting multiseries


Se entrena un único modelo multiserie para predecir las ventas de cada producto en los próximos 7 días. En este caso, se debe indicar a qué item, level, se desea realizar el backtesting.

In [22]:
# Entrenar y realizar backtesting con un único modelo para todos los items
# ======================================================================================
items = []
mae_values = []
predictions_ms = {}

# Definir el forecaster
forecaster_ms = ForecasterAutoregMultiSeries(
                    regressor          = HistGradientBoostingRegressor(random_state=123),
                    lags               = 14,
                    transformer_series = StandardScaler(),
                )

# Backtesting forecaster para cada item
for i, item in enumerate(tqdm(data.columns)):

    metric, preds = backtesting_forecaster_multiseries(
                        forecaster         = forecaster_ms,
                        level              = item,
                        series             = data,
                        initial_train_size = len(data_train) + len(data_val),
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        refit              = False,
                        fixed_train_size   = False,
                        verbose            = False
                    )

    items.append(item)
    mae_values.append(metric)
    predictions_ms[item] = preds

# Resultados
multi_series_mae = pd.Series(
                       data  = mae_values,
                       index = items,
                       name  = 'multi_series_mae'
                   )
100%|██████████| 50/50 [05:18<00:00,  6.38s/it]

Comparación

In [23]:
# Diferencia de la métrica de backtesting para cada item
# ======================================================================================
results = pd.concat((uni_series_mae, multi_series_mae), axis = 1)
results['improvement'] = results.eval('uni_series_mae - multi_series_mae')
results['improvement_(%)'] = 100 * results.eval('(uni_series_mae - multi_series_mae) / uni_series_mae')
results = results.round(2)
results
Out[23]:
uni_series_mae multi_series_mae improvement improvement_(%)
item_1 6.19 5.58 0.61 9.88
item_2 9.85 9.27 0.58 5.85
item_3 8.66 7.41 1.25 14.47
item_4 5.43 4.98 0.44 8.17
item_5 5.00 4.66 0.34 6.82
item_6 10.36 10.04 0.31 3.02
item_7 10.04 9.80 0.24 2.39
item_8 11.30 10.42 0.88 7.80
item_9 9.45 8.77 0.68 7.24
item_10 11.57 10.57 1.01 8.70
item_11 11.22 10.32 0.91 8.08
item_12 12.07 10.98 1.09 9.01
item_13 11.95 11.37 0.58 4.87
item_14 10.17 9.60 0.57 5.65
item_15 12.87 11.38 1.49 11.60
item_16 6.09 6.03 0.07 1.11
item_17 7.69 7.29 0.40 5.17
item_18 12.55 12.01 0.54 4.30
item_19 7.88 7.33 0.55 6.93
item_20 8.37 7.87 0.50 5.97
item_21 8.52 8.10 0.42 4.96
item_22 11.85 10.79 1.06 8.94
item_23 7.41 6.72 0.68 9.23
item_24 10.71 9.95 0.76 7.08
item_25 12.52 11.74 0.78 6.24
item_26 9.11 8.59 0.52 5.76
item_27 5.52 5.15 0.36 6.61
item_28 13.17 11.87 1.30 9.86
item_29 10.71 10.38 0.34 3.14
item_30 8.41 7.82 0.60 7.11
item_31 10.72 10.05 0.67 6.21
item_32 9.55 9.23 0.32 3.30
item_33 9.42 9.30 0.12 1.28
item_34 6.54 5.86 0.68 10.43
item_35 10.77 10.23 0.54 4.98
item_36 11.55 10.68 0.87 7.52
item_37 6.53 6.09 0.43 6.66
item_38 12.06 11.27 0.79 6.51
item_39 8.05 7.27 0.78 9.73
item_40 7.22 6.53 0.69 9.56
item_41 5.69 5.25 0.44 7.80
item_42 7.29 6.98 0.31 4.29
item_43 8.65 8.48 0.17 1.97
item_44 6.53 6.39 0.14 2.11
item_45 12.76 11.78 0.98 7.71
item_46 10.05 9.70 0.35 3.48
item_47 5.22 4.99 0.24 4.51
item_48 9.10 8.20 0.90 9.90
item_49 6.17 5.93 0.24 3.81
item_50 12.00 10.65 1.34 11.18
In [24]:
# Mejora media de todos los items
# ======================================================================================
results[['improvement', 'improvement_(%)']].agg(['mean', 'min', 'max'])
Out[24]:
improvement improvement_(%)
mean 0.6172 6.578
min 0.0700 1.110
max 1.4900 14.470
In [25]:
# Número de series con mejora positiva y negativa
# ======================================================================================
pd.Series(np.where(results['improvement_(%)'] < 0, 'negative', 'positive')).value_counts()
Out[25]:
positive    50
dtype: int64
In [26]:
# Gráfico del item con la máxima mejora
# ======================================================================================
fig, ax=plt.subplots(figsize=(11, 4))
data_test['item_3'].tail(60).plot(ax=ax)
predictions['item_3'].tail(60).plot(ax=ax)
predictions_ms['item_3'].tail(60).plot(ax=ax)
ax.legend(['real', 'predictions single forecaster', 'predictions multi series forecaster']);
In [27]:
# Gráfico del item con la menor mejora
# ======================================================================================
fig, ax=plt.subplots(figsize=(11, 4))
data_test['item_13'].tail(60).plot(ax=ax)
predictions['item_13'].tail(60).plot(ax=ax)
predictions_ms['item_13'].tail(60).plot(ax=ax)
ax.legend(['real', 'predictions single forecaster', 'predictions multi series forecaster']);

Impacto de la longitud de la serie


Si una serie temporal tiene pocas observaciones, la cantidad de información disponible para que el modelo aprenda es limitada. Este es un problema común en casos reales en los que no hay muchos datos históricos disponibles. Los forecasters multiseries no multivariantes combinan todas las series durante el entrenamiento, por lo que pueden acceder a más datos.

En esta sección, se realiza la misma comparación entre el forecasting de una sola serie y el forecasting multiserie, pero, esta vez, la longitud de las series es mucho menor.

In [6]:
# Separación datos train-validation-test
# ======================================================================================
start_train = '2017-01-01 00:00:00'
end_train = '2017-05-01 00:00:00'
end_val = '2017-07-31 23:59:00'
end_test = '2017-09-30 23:59:00'

data = data.loc[start_train:, :].copy()
data_train = data.loc[:end_train, :].copy()
data_val   = data.loc[end_train:end_val, :].copy()
data_test  = data.loc[end_val:end_test, :].copy()

print(f"Fechas train      : {data_train.index.min()} --- {data_train.index.max()}  (n={len(data_train)})")
print(f"Fechas validación : {data_val.index.min()} --- {data_val.index.max()}  (n={len(data_val)})")
print(f"Fechas test       : {data_test.index.min()} --- {data_test.index.max()}  (n={len(data_test)})")
Fechas train      : 2017-01-01 00:00:00 --- 2017-05-01 00:00:00  (n=121)
Fechas validación : 2017-05-01 00:00:00 --- 2017-07-31 00:00:00  (n=92)
Fechas test       : 2017-08-01 00:00:00 --- 2017-09-30 00:00:00  (n=61)
In [29]:
# Entrenar y realizar backtesting de un modelo para cada item
# ======================================================================================
items = []
mae_values = []
predictions = {}

for i, item in enumerate(tqdm(data.columns)):

    forecaster = ForecasterAutoreg(
                     regressor        = HistGradientBoostingRegressor(random_state=123),
                     lags             = 14,
                     transformer_y    = StandardScaler()
                 )

    metric, preds = backtesting_forecaster(
                        forecaster         = forecaster,
                        y                  = data[item],
                        initial_train_size = len(data_train) + len(data_val),
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        refit              = False,
                        fixed_train_size   = False,
                        verbose            = False
                    )

    items.append(item)
    mae_values.append(metric)
    predictions[item] = preds

uni_series_mae = pd.Series(
                     data  = mae_values,
                     index = items,
                     name  = 'uni_series_mae'
                 )
100%|██████████| 50/50 [00:24<00:00,  2.05it/s]
In [30]:
# Entrenar y realizar backtesting con un único modelo para todos los items
# ======================================================================================
items = []
mae_values = []
predictions_ms = {}

forecaster_ms = ForecasterAutoregMultiSeries(
                    regressor          = HistGradientBoostingRegressor(random_state=123),
                    lags               = 14,
                    transformer_series = StandardScaler(),
                )

for i, item in enumerate(tqdm(data.columns)):

    metric, preds = backtesting_forecaster_multiseries(
                        forecaster         = forecaster_ms,
                        level              = item,
                        series             = data,
                        initial_train_size = len(data_train) + len(data_val),
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        refit              = False,
                        fixed_train_size   = False,
                        verbose            = False
                    )

    items.append(item)
    mae_values.append(metric)
    predictions_ms[item] = preds

multi_series_mae = pd.Series(
                       data  = mae_values,
                       index = items,
                       name = 'multi_series_mae'
                   )
100%|██████████| 50/50 [00:59<00:00,  1.18s/it]
In [31]:
# Diferencia de la métrica de backtesting para cada item
# ======================================================================================
results = pd.concat((uni_series_mae, multi_series_mae), axis = 1)
results['improvement'] = results.eval('uni_series_mae - multi_series_mae')
results['improvement_(%)'] = 100 * results.eval('(uni_series_mae - multi_series_mae) / uni_series_mae')
results = results.round(2)
results

# Mejora media de todos los items
# ======================================================================================
results[['improvement', 'improvement_(%)']].agg(['mean', 'min', 'max'])
Out[31]:
improvement improvement_(%)
mean 0.88 8.169
min -0.19 -3.390
max 2.25 18.440
In [32]:
# Número de series con mejora positiva y negativa
# ======================================================================================
pd.Series(np.where(results['improvement_(%)'] < 0, 'negative', 'positive')).value_counts()
Out[32]:
positive    49
negative     1
dtype: int64

La mejora media ha pasado del 6.6 al 8.2%. La ventaja de utilizar un forecaster multiserie parece crecer a medida que se reduce la longitud de las series disponibles.

Optimización de hiperparámetros (tuning)


En las secciones anteriores, la comparación entre forecaster se ha realizado sin optimizar los hiperparámetros de los regresores. Para una comparación justa, se utiliza una estrategia de grid search con el fin de seleccionar la mejor configuración para cada forecaster. Véase más información en hyperparameter tuning and lags selection.

  Warning

La sección siguiente puede requerir un tiempo de ejecución considerable (más de 1 hora). Siéntase libre de seleccionar sólo un subconjunto de items para acelerar la ejecución.
In [33]:
# Ocultar progress bar tqdm
# ======================================================================================
from tqdm import tqdm
from functools import partialmethod
tqdm.__init__ = partialmethod(tqdm.__init__, disable=True)
In [ ]:
# Búsqueda de hiperparámetros y backtesting de un modelo para cada item
# ======================================================================================
items = []
mae_values  = []

lags_grid = [7, 14, 21]
param_grid = {
    'max_iter': [100, 500],
    'max_depth': [3, 5, 10, None],
    'learning_rate': [0.01, 0.1]
}

for i, item in enumerate(data.columns):

    forecaster = ForecasterAutoreg(
                     regressor        = HistGradientBoostingRegressor(random_state=123),
                     lags             = 21,
                     transformer_y    = StandardScaler()
                 )

    results_grid = grid_search_forecaster(
                        forecaster         = forecaster,
                        y                  = data.loc[:end_val, item],
                        lags_grid          = lags_grid,
                        param_grid         = param_grid,
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        initial_train_size = len(data_train),
                        refit              = False,
                        fixed_train_size   = False,
                        return_best        = True,
                        verbose            = False
                    )

    metric, preds = backtesting_forecaster(
                        forecaster         = forecaster,
                        y                  = data[item],
                        initial_train_size = len(data_train) + len(data_val),
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        refit              = False,
                        fixed_train_size   = False,
                        verbose            = False
                    )

    items.append(item)
    mae_values.append(metric)

uni_series_mae = pd.Series(
                     data  = mae_values,
                     index = items,
                     name  = 'uni_series_mae'
                 )
In [ ]:
# Búsqueda de hiperparámetros y backtesting para un modelo multiserie
# ======================================================================================
items = []
mae_values  = []

lags_grid = [7, 14, 21]
param_grid = {
    'max_iter': [100, 500],
    'max_depth': [3, 5, 10, None],
    'learning_rate': [0.01, 0.1]
}

forecaster_ms = ForecasterAutoregMultiSeries(
                    regressor          = HistGradientBoostingRegressor(random_state=123),
                    lags               = 21,
                    transformer_series = StandardScaler(),
                )

results_grid_ms = grid_search_forecaster_multiseries(
                      forecaster         = forecaster_ms,
                      series             = data.loc[:end_val, :],
                      levels             = None,
                      levels_weights     = None,
                      lags_grid          = lags_grid,
                      param_grid         = param_grid,
                      steps              = 7,
                      metric             = 'mean_absolute_error',
                      initial_train_size = len(data_train),
                      refit              = False,
                      fixed_train_size   = False,
                      return_best        = True,
                      verbose            = False
                  )               

for i, item in enumerate(data.columns):

    metric, preds = backtesting_forecaster_multiseries(
                        forecaster         = forecaster_ms,
                        level              = item,
                        series             = data,
                        initial_train_size = len(data_train) + len(data_val),
                        steps              = 7,
                        metric             = 'mean_absolute_error',
                        refit              = False,
                        fixed_train_size   = False,
                        verbose            = False
                    )

    items.append(item)
    mae_values.append(metric)

multi_series_mae = pd.Series(
                       data  = mae_values,
                       index = items,
                       name  = 'multi_series_mae'
                   )
In [36]:
# Diferencia de la métrica de backtesting para cada item
# ======================================================================================
results = pd.concat((uni_series_mae, multi_series_mae), axis = 1)
results['improvement'] = results.eval('uni_series_mae - multi_series_mae')
results['improvement_(%)'] = 100 * results.eval('(uni_series_mae - multi_series_mae) / uni_series_mae')
results = results.round(2)
results
Out[36]:
uni_series_mae multi_series_mae improvement improvement_(%)
item_1 6.74 6.07 0.67 9.94
item_2 12.59 11.29 1.29 10.28
item_3 8.33 8.03 0.30 3.61
item_4 5.87 5.81 0.06 0.98
item_5 5.59 5.23 0.37 6.56
item_6 11.41 11.26 0.15 1.32
item_7 11.34 11.27 0.07 0.66
item_8 14.34 13.91 0.42 2.94
item_9 11.94 10.74 1.19 9.98
item_10 14.79 13.63 1.16 7.85
item_11 12.73 13.09 -0.36 -2.85
item_12 14.43 14.11 0.31 2.18
item_13 15.60 13.92 1.67 10.74
item_14 11.77 10.84 0.93 7.89
item_15 15.75 15.21 0.54 3.41
item_16 6.92 6.83 0.09 1.32
item_17 8.31 7.89 0.42 5.03
item_18 16.29 15.19 1.09 6.72
item_19 8.62 8.29 0.32 3.75
item_20 9.63 9.32 0.31 3.21
item_21 9.10 8.57 0.53 5.87
item_22 14.30 13.83 0.47 3.30
item_23 7.65 7.51 0.14 1.83
item_24 14.08 12.45 1.63 11.58
item_25 14.26 15.18 -0.92 -6.46
item_26 11.09 10.68 0.41 3.72
item_27 5.83 5.39 0.44 7.53
item_28 14.99 14.72 0.27 1.83
item_29 12.55 12.63 -0.08 -0.65
item_30 10.30 8.77 1.53 14.86
item_31 11.90 11.61 0.29 2.42
item_32 10.19 10.62 -0.43 -4.25
item_33 12.71 12.23 0.48 3.78
item_34 6.42 6.13 0.30 4.66
item_35 12.58 12.54 0.04 0.29
item_36 14.64 13.67 0.97 6.66
item_37 7.19 6.78 0.41 5.67
item_38 15.66 14.65 1.01 6.48
item_39 8.62 8.73 -0.11 -1.22
item_40 8.26 7.22 1.03 12.50
item_41 6.45 5.77 0.68 10.51
item_42 8.86 7.84 1.02 11.54
item_43 10.91 10.34 0.56 5.18
item_44 7.96 6.98 0.99 12.43
item_45 14.17 15.23 -1.06 -7.45
item_46 12.27 12.04 0.23 1.89
item_47 5.93 5.80 0.13 2.21
item_48 9.44 9.68 -0.24 -2.55
item_49 7.27 7.26 0.01 0.16
item_50 12.80 12.60 0.20 1.55
In [37]:
# Mejora media de todos los items
# ======================================================================================
results[['improvement', 'improvement_(%)']].agg(['mean', 'min', 'max'])
Out[37]:
improvement improvement_(%)
mean 0.4386 4.2278
min -1.0600 -7.4500
max 1.6700 14.8600
In [38]:
# Número de series con mejora positiva y negativa
# ======================================================================================
pd.Series(np.where(results['improvement_(%)'] < 0, 'negative', 'positive')).value_counts()
Out[38]:
positive    43
negative     7
dtype: int64

Tras identificar la combinación de lags e hiperparámetros que logran el mejor rendimiento predictivo para cada forecaster, un número superior de modelos univariantes han logrado una mayor capacidad predictiva al generalizar mejor sus propios datos (un item). Aun así, el modelo multiserie proporciona mejores resultados para la mayoría de los items.

Conclusiones


Este caso de uso muestra como un modelo multiserie puede presentar ventajas sobre varios modelos individuales cuando se predicen series temporales con una dinámica similar.

Más allá de las posibles mejoras en la predicción, también es importante tener en cuenta la ventaja de tener un solo modelo que mantener.

Información de sesión

In [42]:
import session_info
session_info.show(html=False)
-----
matplotlib          3.5.0
numpy               1.23.0
pandas              1.4.0
session_info        1.0.0
skforecast          0.5.1
sklearn             1.1.0
statsmodels         0.13.0
tqdm                4.64.0
-----
IPython             8.5.0
jupyter_client      7.3.5
jupyter_core        4.11.1
notebook            6.4.12
-----
Python 3.9.13 (main, Aug 25 2022, 23:26:10) [GCC 11.2.0]
Linux-5.15.0-48-generic-x86_64-with-glibc2.31
-----
Session information updated at 2022-10-12 10:24

¿Cómo citar este documento?

Multi-series forecasting with python and skforecast by Joaquín Amat Rodrigo and Javier Escobar Ortiz, available under a Attribution 4.0 International (CC BY 4.0) at https://www.cienciadedatos.net/documentos/py44-multi-series-forecasting-skforecast-español.html


¿Te ha gustado el artículo? Tu ayuda es importante

Mantener un sitio web tiene unos costes elevados, tu contribución me ayudará a seguir generando contenido divulgativo gratuito. ¡Muchísimas gracias! 😊


Creative Commons Licence
This work by Joaquín Amat Rodrigo and Javier Escobar Ortiz is licensed under a Creative Commons Attribution 4.0 International License.