Más sobre forecasting en: cienciadedatos.net
- Forecasting series temporales con machine learning
- Modelos ARIMA y SARIMAX
- Forecasting series temporales con gradient boosting: XGBoost, LightGBM y CatBoost
- Global Forecasting: Multi-series forecasting
- Forecasting de la demanda eléctrica con machine learning
- Forecasting con deep learning
- Forecasting de visitas a página web con machine learning
- Forecasting del precio de Bitcoin
- Forecasting probabilístico
- Forecasting de demanda intermitente
- Reducir el impacto del Covid en modelos de forecasting
- Modelar series temporales con tendencia utilizando modelos de árboles

Introducción¶
La predicción de la demanda energética desempeña un papel fundamental en la gestión y planificación de los recursos necesarios para la generación, distribución y utilización de la energía. Predecir la demanda de energía es una tarea compleja en la que influyen factores como los patrones meteorológicos, las condiciones económicas y el comportamiento de la sociedad. Este documento muestra cómo utilizar modelos de machine learning para predecir la demanda de energía.
Series temporales y forecasting
Una serie temporal (time series) es una sucesión de datos ordenados cronológicamente, espaciados a intervalos iguales o desiguales. El proceso de forecasting consiste en predecir el valor futuro de una serie temporal, bien modelando la serie únicamente en función de su comportamiento pasado (autorregresivo) o empleando otras variables externas.
Cuando se trabaja con series temporales, raramente se quiere predecir solo el siguiente elemento de la serie ($t_{+1}$), sino todo un horizonte futuro ($t_{+1}$), ..., ($t_{+n}$)) o un punto alejado en el tiempo ($t_{+n}$). Existen varias estrategias que permiten generar este tipo de predicciones, la librería skforecast recoge las siguientes para series temporales univariantes:
- Forecasting multi-step recursivo: este método consiste en utilizar propias predicciones del modelo como valores de entrada para predecir el siguiente valor. Por ejemplo, para predecir los 5 valores siguientes de una serie temporal, se entrena un modelo para predecir el siguiente valor ($t_{+1}$), y se utiliza este valor para predecir el siguiente ($t_{+2}$), y así sucesivamente. Todo este proceso se automatiza con la clase
ForecasterRecursive
.

- Forecasting multi-step directo: este método consiste en entrenar un modelo diferente para cada valor futuro (step) del horizonte de predicción. Por ejemplo, para predecir los 5 siguientes valores de una serie temporal, se entrenan 5 modelos diferentes, uno para cada step. De este modo, las predicciones son independientes entre sí. Todo este proceso se automatiza en la clase
ForecasterDirect
.

- Forecasting multi-output: Determinados modelos, por ejemplo, las redes neuronales LSTM, son capaces de predecir de forma simultánea varios valores de una secuencia (one-shot). Esta estrategia está disponible con la clase
ForecasterRnn
.
✎ Nota
Otros dos ejemplos de cómo utilizar machine learning (*gradient boosting*) para forecasting de series temporales son:Librerías¶
Las librerías utilizadas en este documento son:
# Tratamiento de datos
# ==============================================================================
import numpy as np
import pandas as pd
from astral.sun import sun
from astral import LocationInfo
from skforecast.datasets import fetch_dataset
from feature_engine.datetime import DatetimeFeatures
from feature_engine.creation import CyclicalFeatures
from feature_engine.timeseries.forecasting import WindowFeatures
# Gráficos
# ==============================================================================
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.graphics.tsaplots import plot_pacf
from skforecast.plot import plot_residuals
import plotly.graph_objects as go
import plotly.io as pio
import plotly.offline as poff
pio.templates.default = "seaborn"
poff.init_notebook_mode(connected=True)
plt.style.use('seaborn-v0_8-darkgrid')
plt.rcParams.update({'font.size': 8})
# Modelado y Forecasting
# ==============================================================================
import skforecast
import lightgbm
import sklearn
from lightgbm import LGBMRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn.feature_selection import RFECV
from skforecast.recursive import ForecasterEquivalentDate, ForecasterRecursive
from skforecast.direct import ForecasterDirect
from skforecast.model_selection import TimeSeriesFold, bayesian_search_forecaster, backtesting_forecaster
from skforecast.feature_selection import select_features
from skforecast.preprocessing import RollingFeatures
from skforecast.plot import calculate_lag_autocorrelation, plot_residuals
from skforecast.metrics import calculate_coverage
import shap
# Configuración warnings
# ==============================================================================
import warnings
warnings.filterwarnings('once')
color = '\033[1m\033[38;5;208m'
print(f"{color}Versión skforecast: {skforecast.__version__}")
print(f"{color}Versión scikit-learn: {sklearn.__version__}")
print(f"{color}Versión lightgbm: {lightgbm.__version__}")
print(f"{color}Versión pandas: {pd.__version__}")
print(f"{color}Versión numpy: {np.__version__}")
Versión skforecast: 0.15.0 Versión scikit-learn: 1.6.1 Versión lightgbm: 4.6.0 Versión pandas: 2.2.3 Versión numpy: 2.1.3
Datos¶
Se dispone de una serie temporal de la demanda de electricidad (MW) para el estado de Victoria (Australia) desde 2011-12-31 hasta 2014-12-31. Los datos empleados en este documento se han obtenido del paquete de R tsibbledata. El set de datos contiene 5 columnas y 52608 registros completos. La información de cada columna es:
- Time: fecha y hora del registro.
- Date: fecha del registro
- Demand: demanda de electricidad (MW).
- Temperature: temperatura en Melbourne, capital de Victoria.
- Holiday: indicador si el día es festivo (vacaciones).
# Descarga de datos
# ==============================================================================
datos = fetch_dataset(name='vic_electricity', raw=True)
datos.info()
vic_electricity --------------- Half-hourly electricity demand for Victoria, Australia O'Hara-Wild M, Hyndman R, Wang E, Godahewa R (2022).tsibbledata: Diverse Datasets for 'tsibble'. https://tsibbledata.tidyverts.org/, https://github.com/tidyverts/tsibbledata/. https://tsibbledata.tidyverts.org/reference/vic_elec.html Shape of the dataset: (52608, 5) <class 'pandas.core.frame.DataFrame'> RangeIndex: 52608 entries, 0 to 52607 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Time 52608 non-null object 1 Demand 52608 non-null float64 2 Temperature 52608 non-null float64 3 Date 52608 non-null object 4 Holiday 52608 non-null bool dtypes: bool(1), float64(2), object(2) memory usage: 1.7+ MB
La columna Time
está almacenada como string
. Para convertirla en datetime
, se emplea la función pd.to_datetime()
. Una vez en formato datetime
, y para hacer uso de las funcionalidades de pandas, se establece como índice. Además, dado que los datos se han registrado cada 30 minutos, se indica la frecuencia '30min'
.
# Conversión del formato fecha
# ==============================================================================
datos['Time'] = pd.to_datetime(datos['Time'], format='%Y-%m-%dT%H:%M:%SZ')
datos = datos.set_index('Time')
datos = datos.asfreq('30min')
datos = datos.sort_index()
datos.head(2)
Demand | Temperature | Date | Holiday | |
---|---|---|---|---|
Time | ||||
2011-12-31 13:00:00 | 4382.825174 | 21.40 | 2012-01-01 | True |
2011-12-31 13:30:00 | 4263.365526 | 21.05 | 2012-01-01 | True |
Uno de los primeros análisis que hay que realizar al trabajar con series temporales es verificar si la serie está completa.
# Verificar que un índice temporal está completo
# ==============================================================================
fecha_inicio = datos.index.min()
fecha_fin = datos.index.max()
date_range_completo = pd.date_range(start=fecha_inicio, end=fecha_fin, freq=datos.index.freq)
print(f"Índice completo: {(datos.index == date_range_completo).all()}")
print(f"Filas con valores ausentes: {datos.isnull().any(axis=1).mean()}")
Índice completo: True Filas con valores ausentes: 0.0
# Completar huecos en un índice temporal
# ==============================================================================
# datos.asfreq(freq='30min', fill_value=np.nan)
Aunque los datos se encuentran en intervalos de 30 minutos, el objetivo es crear un modelo capaz de predecir la demanda eléctrica a nivel horario, por lo que se tienen que agregar los datos. Este tipo de transformación es muy sencillas si se combina el índice DatetimeIndex
de pandas y su método resample()
.
Es muy importante utilizar correctamente los argumentos closed='left'
y label='right'
para no introducir en el entrenamiento información a futuro (leakage)). Supóngase que se dispone de valores para las 10:10, 10:30, 10:45, 11:00, 11:12 y 11:30. Si se quiere obtener el promedio horario, el valor asignado a las 11:00 debe calcularse utilizando los valores de las 10:10, 10:30 y 10:45; y el de las 12:00, con el valor de las 11:00, 11:12 y 11:30.
Para el valor promedio de las 11:00 no se incluye el valor puntual de las 11:00 por que, en la realidad, en ese momento exacto no se dispone todavía del valor.
# Agregado en intervalos de 1H
# ==============================================================================
# Se elimina la columna Date para que no genere error al agregar.
datos = datos.drop(columns='Date')
datos = (
datos
.resample(rule="h", closed="left", label="right")
.agg({
"Demand": "mean",
"Temperature": "mean",
"Holiday": "mean",
})
)
datos
Demand | Temperature | Holiday | |
---|---|---|---|
Time | |||
2011-12-31 14:00:00 | 4323.095350 | 21.225 | 1.0 |
2011-12-31 15:00:00 | 3963.264688 | 20.625 | 1.0 |
2011-12-31 16:00:00 | 3950.913495 | 20.325 | 1.0 |
2011-12-31 17:00:00 | 3627.860675 | 19.850 | 1.0 |
2011-12-31 18:00:00 | 3396.251676 | 19.025 | 1.0 |
... | ... | ... | ... |
2014-12-31 09:00:00 | 4069.625550 | 21.600 | 0.0 |
2014-12-31 10:00:00 | 3909.230704 | 20.300 | 0.0 |
2014-12-31 11:00:00 | 3900.600901 | 19.650 | 0.0 |
2014-12-31 12:00:00 | 3758.236494 | 18.100 | 0.0 |
2014-12-31 13:00:00 | 3785.650720 | 17.200 | 0.0 |
26304 rows × 3 columns
El set de datos empieza el 2011-12-31 14:00:00 y termina el 2014-12-31 13:00:00. Se descartan los primeros 10 y los últimos 13 registros para que empiece el 2012-01-01 00:00:00 y termine el 2014-12-30 23:00:00. Además, para poder optimizar los hiperparámetros del modelo y evaluar su capacidad predictiva, se dividen los datos en 3 conjuntos, uno de entrenamiento, uno de validación y otro de test.
# Separación datos train-val-test
# ==============================================================================
datos = datos.loc['2012-01-01 00:00:00':'2014-12-30 23:00:00', :].copy()
fin_train = '2013-12-31 23:59:00'
fin_validacion = '2014-11-30 23:59:00'
datos_train = datos.loc[: fin_train, :].copy()
datos_val = datos.loc[fin_train:fin_validacion, :].copy()
datos_test = datos.loc[fin_validacion:, :].copy()
print(f"Fechas train : {datos_train.index.min()} --- {datos_train.index.max()} (n={len(datos_train)})")
print(f"Fechas validacion : {datos_val.index.min()} --- {datos_val.index.max()} (n={len(datos_val)})")
print(f"Fechas test : {datos_test.index.min()} --- {datos_test.index.max()} (n={len(datos_test)})")
Fechas train : 2012-01-01 00:00:00 --- 2013-12-31 23:00:00 (n=17544) Fechas validacion : 2014-01-01 00:00:00 --- 2014-11-30 23:00:00 (n=8016) Fechas test : 2014-12-01 00:00:00 --- 2014-12-30 23:00:00 (n=720)
Exploración gráfica¶
La exploración gráfica de series temporales es una forma eficaz de identificar tendencias, patrones y estacionalidad. Esto, a su vez, ayuda a orientar la selección del modelo de forecasting más adecuado.
Gráfico de la serie temporal¶
Serie temporal completa
# Gráfico interactivo de la serie temporal
# ==============================================================================
fig = go.Figure()
fig.add_trace(go.Scatter(x=datos_train.index, y=datos_train['Demand'], mode='lines', name='Train'))
fig.add_trace(go.Scatter(x=datos_val.index, y=datos_val['Demand'], mode='lines', name='Validation'))
fig.add_trace(go.Scatter(x=datos_test.index, y=datos_test['Demand'], mode='lines', name='Test'))
fig.update_layout(
title = 'Demanda eléctrica horaria',
xaxis_title="Fecha",
yaxis_title="Demanda (MWh)",
legend_title="Partición:",
width=800,
height=400,
margin=dict(l=20, r=20, t=35, b=20),
legend=dict(orientation="h", yanchor="top", y=1, xanchor="left", x=0.001)
)
#fig.update_xaxes(rangeslider_visible=True)
fig.show()
El gráfico anterior muestra que la demanda eléctrica tiene estacionalidad anual. Se observa un incremento centrado en el mes de Julio y picos de demanda muy acentuados entre enero y marzo.
Sección de la serie temporal
Debido a la varianza de la serie temporal, no es posible apreciar con un solo gráfico el posible patrón intradiario.
# Gráfico serie temporal con zoom
# ==============================================================================
zoom = ('2013-05-01 14:00:00','2013-06-01 14:00:00')
fig = plt.figure(figsize=(8, 4))
grid = plt.GridSpec(nrows=8, ncols=1, hspace=0.6, wspace=0)
main_ax = fig.add_subplot(grid[1:3, :])
zoom_ax = fig.add_subplot(grid[5:, :])
datos.Demand.plot(ax=main_ax, c='black', alpha=0.5, linewidth=0.5)
min_y = min(datos.Demand)
max_y = max(datos.Demand)
main_ax.fill_between(zoom, min_y, max_y, facecolor='blue', alpha=0.5, zorder=0)
main_ax.set_xlabel('')
datos.loc[zoom[0]: zoom[1]].Demand.plot(ax=zoom_ax, color='blue', linewidth=1)
main_ax.set_title(f'Demanda electricidad: {datos.index.min()}, {datos.index.max()}', fontsize=10)
zoom_ax.set_title(f'Demanda electricidad: {zoom}', fontsize=10)
zoom_ax.set_xlabel('')
plt.subplots_adjust(hspace=1)
Al aplicar zoom sobre la serie temporal, se hace patente una clara estacionalidad semanal, con consumos más elevados durante la semana laboral (lunes a viernes) y menor en los fines de semana. Se observa también que existe una clara correlación entre el consumo de un día con el del día anterior.
Gráfico de estacionalidad¶
# Estacionalidad anual, semanal y diaria
# ==============================================================================
fig, axs = plt.subplots(2, 2, figsize=(8, 5), sharex=False, sharey=True)
axs = axs.ravel()
# Distribusión de demanda por mes
datos['month'] = datos.index.month
datos.boxplot(column='Demand', by='month', ax=axs[0], flierprops={'markersize': 3, 'alpha': 0.3})
datos.groupby('month')['Demand'].median().plot(style='o-', linewidth=0.8, ax=axs[0])
axs[0].set_ylabel('Demand')
axs[0].set_title('Distribución de demanda por mes', fontsize=9)
# Distribusión de demanda por día de la semana
datos['week_day'] = datos.index.day_of_week + 1
datos.boxplot(column='Demand', by='week_day', ax=axs[1], flierprops={'markersize': 3, 'alpha': 0.3})
datos.groupby('week_day')['Demand'].median().plot(style='o-', linewidth=0.8, ax=axs[1])
axs[1].set_ylabel('Demand')
axs[1].set_title('Distribución de demanda por día de la semana', fontsize=9)
# Distribusión de demanda por hora del día
datos['hour_day'] = datos.index.hour + 1
datos.boxplot(column='Demand', by='hour_day', ax=axs[2], flierprops={'markersize': 3, 'alpha': 0.3})
datos.groupby('hour_day')['Demand'].median().plot(style='o-', linewidth=0.8, ax=axs[2])
axs[2].set_ylabel('Demand')
axs[2].set_title('Distribución de demanda por hora del día', fontsize=9)
# Distribusión de demanda por día de la semana y hora del día
mean_day_hour = datos.groupby(["week_day", "hour_day"])["Demand"].mean()
mean_day_hour.plot(ax=axs[3])
axs[3].set(
title = "Promedio de demanda",
xticks = [i * 24 for i in range(7)],
xticklabels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
xlabel = "Día y hora",
ylabel = "Demand"
)
axs[3].title.set_size(10)
fig.suptitle("Gráficos de estacionalidad", fontsize=12)
fig.tight_layout()
Se observa que hay una estacionalidad anual, con valores de demanda (mediana) superiores en los meses de junio, julio y agosto, y con elevados picos de demanda en los meses de noviembre, diciembre, enero, febrero y marzo. Se aprecia una estacionalidad semanal, con valores de demanda inferiores durante el fin de semana. También existe una estacionalidad diaria, ya que la demanda se reduce entre las 16:00 y las 21:00 horas.
Gráficos de autocorrelación¶
Los gráficos de autocorrelación muestran la correlación entre una serie temporal y sus valores pasados. Son una herramienta útil para identificar el orden de un modelo autorregresivo, es decir, los valores pasados (lags) que se deben incluir en el modelo.
La función de autocorrelación (ACF) mide la correlación entre una serie temporal y sus valores pasados. La función de autocorrelación parcial (PACF) mide la correlación entre una serie temporal y sus valores pasados, pero solo después de eliminar las variaciones explicadas por los valores pasados intermedios.
# Gráfico autocorrelación
# ==============================================================================
fig, ax = plt.subplots(figsize=(5, 2))
plot_acf(datos['Demand'], ax=ax, lags=60)
plt.show()
# Gráfico autocorrelación parcial
# ==============================================================================
fig, ax = plt.subplots(figsize=(5, 2))
plot_pacf(datos['Demand'], ax=ax, lags=60)
plt.show()
# Top 10 lags con mayor autocorrelación parcial absoluta
# ==============================================================================
calculate_lag_autocorrelation(
data = datos['Demand'],
n_lags = 60,
sort_by = "partial_autocorrelation_abs"
).head(10)
lag | partial_autocorrelation_abs | partial_autocorrelation | autocorrelation_abs | autocorrelation | |
---|---|---|---|---|---|
0 | 1 | 0.949526 | 0.949526 | 0.949490 | 0.949490 |
1 | 25 | 0.761400 | -0.761400 | 0.731620 | 0.731620 |
2 | 2 | 0.657961 | -0.657961 | 0.836792 | 0.836792 |
3 | 26 | 0.634868 | 0.634868 | 0.622458 | 0.622458 |
4 | 24 | 0.308085 | -0.308085 | 0.785622 | 0.785622 |
5 | 19 | 0.291113 | 0.291113 | 0.302308 | 0.302308 |
6 | 27 | 0.281261 | -0.281261 | 0.488351 | 0.488351 |
7 | 21 | 0.269274 | 0.269274 | 0.537173 | 0.537173 |
8 | 20 | 0.201192 | 0.201192 | 0.414709 | 0.414709 |
9 | 9 | 0.184975 | 0.184975 | 0.037677 | 0.037677 |
Los gráficos de autocorrelación y autocorrelación parcial muestran una clara asociación entre la demanda de una hora y las horas anteriores, así como entre la demanda de una hora y la demanda de esa misma hora los días anteriores. Este tipo de correlación, es un indicativo de que los modelos autorregresivos pueden funcionar bien.
Modelo Baseline¶
Al enfrentarse a un problema de forecasting, es recomendable disponer de un modelo de referencia (baseline). Suele tratarse de un modelo muy sencillo que puede utilizarse como referencia para evaluar si merece la pena aplicar modelos más complejos.
Skforecast permite crear fácilmente un modelo de referencia con su clase ForecasterEquivalentDate. Este modelo, también conocido como Seasonal Naive Forecasting, simplemente devuelve el valor observado en el mismo periodo de la temporada anterior (por ejemplo, el mismo día laboral de la semana anterior, la misma hora del día anterior, etc.).
A partir del análisis exploratorio realizado, el modelo de referencia será el que prediga cada hora utilizando el valor de la misma hora del día anterior.
✎ Nota
En las siguientes celdas de código, se entrena un modelo *baseline* y se evalúa su capacidad predictiva mediante un proceso de *backtesting*. Si este concepto es nuevo para ti, no te preocupes, se explicará en detalle a lo largo del documento. Por ahora, basta con saber que el proceso de *backtesting* consiste en entrenar el modelo con una cierta cantidad de datos y evaluar su capacidad predictiva con los datos que el modelo no ha visto. La métrica del error se utilizará como referencia para comparar la capacidad predictiva de los modelos más complejos que se implementarán a lo largo del documento.# Crear un baseline: valor de la misma hora del día anterior
# ==============================================================================
forecaster = ForecasterEquivalentDate(
offset = pd.DateOffset(days=1),
n_offsets = 1
)
# Entremaiento del forecaster
# ==============================================================================
forecaster.fit(y=datos.loc[:fin_validacion, 'Demand'])
forecaster
======================== ForecasterEquivalentDate ======================== Offset: <DateOffset: days=1> Number of offsets: 1 Aggregation function: mean Window size: 24 Training range: [Timestamp('2012-01-01 00:00:00'), Timestamp('2014-11-30 23:00:00')] Training index type: DatetimeIndex Training index frequency: h Creation date: 2025-03-13 16:07:34 Last fit date: 2025-03-13 16:07:34 Skforecast version: 0.15.0 Python version: 3.11.11 Forecaster id: None
# Backtesting
# ==============================================================================
cv = TimeSeriesFold(
steps = 24,
initial_train_size = len(datos.loc[:fin_validacion]),
refit = False
)
metrica, predicciones = backtesting_forecaster(
forecaster = forecaster,
y = datos['Demand'],
cv = cv,
metric = 'mean_absolute_error'
)
metrica
0%| | 0/30 [00:00<?, ?it/s]
mean_absolute_error | |
---|---|
0 | 308.375272 |
El error del modelo base se utiliza como referencia para evaluar si merece la pena aplicar los modelos más complejos.
Modelo autoregresivo recursivo¶
Se entrena un modelo autorregresivo recursivo (ForecasterRecursive
) con un gradient boosting LGBMRegressor como regresor para predecir la demanda de energía de las próximas 24 horas.
Se utilizan como predictores los valores de consumo de las últimas 24 horas (24 lags) así como la media movil de los últimos 3 días. Los hiperparámetros del regresor se dejan en sus valores por defecto.
# Crear el forecaster
# ==============================================================================
window_features = RollingFeatures(stats=["mean"], window_sizes=24 * 3)
forecaster = ForecasterRecursive(
regressor = LGBMRegressor(random_state=15926, verbose=-1),
lags = 24,
window_features = window_features
)
# Entrena el forecaster
# ==============================================================================
forecaster.fit(y=datos.loc[:fin_validacion, 'Demand'])
forecaster
ForecasterRecursive
General Information
- Regressor: LGBMRegressor
- Lags: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
- Window features: ['roll_mean_72']
- Window size: 72
- Exogenous included: False
- Weight function included: False
- Differentiation order: None
- Creation date: 2025-03-13 16:07:35
- Last fit date: 2025-03-13 16:07:35
- Skforecast version: 0.15.0
- Python version: 3.11.11
- Forecaster id: None
Exogenous Variables
-
None
Data Transformations
- Transformer for y: None
- Transformer for exog: None
Training Information
- Training range: [Timestamp('2012-01-01 00:00:00'), Timestamp('2014-11-30 23:00:00')]
- Training index type: DatetimeIndex
- Training index frequency: h
Regressor Parameters
-
{'boosting_type': 'gbdt', 'class_weight': None, 'colsample_bytree': 1.0, 'importance_type': 'split', 'learning_rate': 0.1, 'max_depth': -1, 'min_child_samples': 20, 'min_child_weight': 0.001, 'min_split_gain': 0.0, 'n_estimators': 100, 'n_jobs': None, 'num_leaves': 31, 'objective': None, 'random_state': 15926, 'reg_alpha': 0.0, 'reg_lambda': 0.0, 'subsample': 1.0, 'subsample_for_bin': 200000, 'subsample_freq': 0, 'verbose': -1}
Fit Kwargs
-
{}
Backtesting¶
Para obtener una estimación robusta de la capacidad predictiva del modelo, se realiza un proceso de backtesting. El proceso de backtesting consiste en generar una predicción para cada observación del conjunto de test, siguiendo el mismo procedimiento que se seguiría si el modelo estuviese en producción, y finalmente comparar el valor predicho con el valor real.
El proceso de backtesting se aplica mediante la función backtesting_forecaster()
. Para este caso de uso, la simulación se lleva a cabo de la siguiente manera: el modelo se entrena con datos de 2012-01-01 00:00 a 2014-11-30 23:59, y luego predice las siguientes 24 horas cada día a las 23:59. La métrica de error utilizada es el error absoluto medio (MAE).
Se recomienda revisar la documentación de la función backtesting_forecaster() para comprender mejor sus capacidades. Esto ayudará a utilizar todo su potencial para analizar la capacidad predictiva del modelo.
# Backtesting
# ==============================================================================
metrica, predicciones = backtesting_forecaster(
forecaster = forecaster,
y = datos['Demand'],
cv = cv,
metric = 'mean_absolute_error',
verbose = True, # False para no mostrar info
)
Information of folds -------------------- Number of observations used for initial training: 25560 Number of observations used for backtesting: 720 Number of folds: 30 Number skipped folds: 0 Number of steps per fold: 24 Number of steps to exclude between last observed data (last window) and predictions (gap): 0 Fold: 0 Training: 2012-01-01 00:00:00 -- 2014-11-30 23:00:00 (n=25560) Validation: 2014-12-01 00:00:00 -- 2014-12-01 23:00:00 (n=24) Fold: 1 Training: No training in this fold Validation: 2014-12-02 00:00:00 -- 2014-12-02 23:00:00 (n=24) Fold: 2 Training: No training in this fold Validation: 2014-12-03 00:00:00 -- 2014-12-03 23:00:00 (n=24) Fold: 3 Training: No training in this fold Validation: 2014-12-04 00:00:00 -- 2014-12-04 23:00:00 (n=24) Fold: 4 Training: No training in this fold Validation: 2014-12-05 00:00:00 -- 2014-12-05 23:00:00 (n=24) Fold: 5 Training: No training in this fold Validation: 2014-12-06 00:00:00 -- 2014-12-06 23:00:00 (n=24) Fold: 6 Training: No training in this fold Validation: 2014-12-07 00:00:00 -- 2014-12-07 23:00:00 (n=24) Fold: 7 Training: No training in this fold Validation: 2014-12-08 00:00:00 -- 2014-12-08 23:00:00 (n=24) Fold: 8 Training: No training in this fold Validation: 2014-12-09 00:00:00 -- 2014-12-09 23:00:00 (n=24) Fold: 9 Training: No training in this fold Validation: 2014-12-10 00:00:00 -- 2014-12-10 23:00:00 (n=24) Fold: 10 Training: No training in this fold Validation: 2014-12-11 00:00:00 -- 2014-12-11 23:00:00 (n=24) Fold: 11 Training: No training in this fold Validation: 2014-12-12 00:00:00 -- 2014-12-12 23:00:00 (n=24) Fold: 12 Training: No training in this fold Validation: 2014-12-13 00:00:00 -- 2014-12-13 23:00:00 (n=24) Fold: 13 Training: No training in this fold Validation: 2014-12-14 00:00:00 -- 2014-12-14 23:00:00 (n=24) Fold: 14 Training: No training in this fold Validation: 2014-12-15 00:00:00 -- 2014-12-15 23:00:00 (n=24) Fold: 15 Training: No training in this fold Validation: 2014-12-16 00:00:00 -- 2014-12-16 23:00:00 (n=24) Fold: 16 Training: No training in this fold Validation: 2014-12-17 00:00:00 -- 2014-12-17 23:00:00 (n=24) Fold: 17 Training: No training in this fold Validation: 2014-12-18 00:00:00 -- 2014-12-18 23:00:00 (n=24) Fold: 18 Training: No training in this fold Validation: 2014-12-19 00:00:00 -- 2014-12-19 23:00:00 (n=24) Fold: 19 Training: No training in this fold Validation: 2014-12-20 00:00:00 -- 2014-12-20 23:00:00 (n=24) Fold: 20 Training: No training in this fold Validation: 2014-12-21 00:00:00 -- 2014-12-21 23:00:00 (n=24) Fold: 21 Training: No training in this fold Validation: 2014-12-22 00:00:00 -- 2014-12-22 23:00:00 (n=24) Fold: 22 Training: No training in this fold Validation: 2014-12-23 00:00:00 -- 2014-12-23 23:00:00 (n=24) Fold: 23 Training: No training in this fold Validation: 2014-12-24 00:00:00 -- 2014-12-24 23:00:00 (n=24) Fold: 24 Training: No training in this fold Validation: 2014-12-25 00:00:00 -- 2014-12-25 23:00:00 (n=24) Fold: 25 Training: No training in this fold Validation: 2014-12-26 00:00:00 -- 2014-12-26 23:00:00 (n=24) Fold: 26 Training: No training in this fold Validation: 2014-12-27 00:00:00 -- 2014-12-27 23:00:00 (n=24) Fold: 27 Training: No training in this fold Validation: 2014-12-28 00:00:00 -- 2014-12-28 23:00:00 (n=24) Fold: 28 Training: No training in this fold Validation: 2014-12-29 00:00:00 -- 2014-12-29 23:00:00 (n=24) Fold: 29 Training: No training in this fold Validation: 2014-12-30 00:00:00 -- 2014-12-30 23:00:00 (n=24)
0%| | 0/30 [00:00<?, ?it/s]
# Gráfico prediccion vs valores reales
# ==============================================================================
fig = go.Figure()
trace1 = go.Scatter(x=datos_test.index, y=datos_test['Demand'], name="test", mode="lines")
trace2 = go.Scatter(x=predicciones.index, y=predicciones['pred'], name="prediction", mode="lines")
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.update_layout(
title="Predicción vs valores reales en test",
xaxis_title="Date time",
yaxis_title="Demand",
width=800,
height=400,
margin=dict(l=20, r=20, t=35, b=20),
legend=dict(orientation="h", yanchor="top", y=1.01, xanchor="left", x=0)
)
fig.show()