Forecasting de demanda intermitente con skforecast

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

Forecasting demanda intermitente con skforecast

Joaquín Amat Rodrigo, Javier Escobar Ortiz
April, 2023 (última actualización Agosto 2024)

Introducción

El forecasting de la demanda intermitente es un método estadístico utilizado para predecir la demanda de productos que tienen patrones de venta esporádicos o irregulares. Este tipo de productos se caracteriza por periodos de gran demanda seguidos de periodos de escasa o nula demanda. La previsión de la demanda intermitente se utiliza en las industrias manufacturera, minorista y sanitaria para gestionar los niveles de inventario, optimizar los programas de producción y reducir las roturas de stock y los costes por exceso de inventario.

La demanda intermitente regular se refiere a patrones de demanda predecibles con intervalos conocidos entre periodos de demanda. Este tipo de demanda intermitente se produce con cierta regularidad, como un producto que tiene una demanda estacional o un producto que se pide todos los meses en una fecha determinada. La previsión de la demanda intermitente regular puede realizarse mediante métodos estadísticos, como el método de Croston, que es una técnica habitual para prever la demanda en estas situaciones. Sin embargo, la mayoría de implementaciones no suelen permitir incorporar variables exógenas, a pesar de que pueden contener información muy relevante sobre los intervalos de demanda. Por esta razón, el forecasting basado en modelos de machine learning puede ser una alternativa interesante.

Por otro lado, la demanda intermitente irregular se refiere a patrones de demanda impredecibles, sin intervalos conocidos entre periodos de demanda. Este tipo de demanda intermitente es aleatoria e imprevisible, como un producto que se pide sólo unas pocas veces al año y en cantidades variables. Prever la demanda intermitente irregular es un reto porque no hay un patrón predecible para la demanda. Los métodos de previsión tradicionales pueden no funcionar eficazmente en estas situaciones y puede ser necesario recurrir a métodos de previsión alternativos, como el bootstrapping o la simulación.

En resumen, la demanda intermitente regular tiene un patrón predecible, mientras que la demanda intermitente irregular no lo tiene. La previsión de la demanda intermitente regular es más fácil que la previsión de la demanda intermitente irregular debido a la previsibilidad del patrón de demanda.

Este documento muestra cómo se puede utilizar la librería Python skforecast para predecir escenarios de demanda intermitente regular. Utilizando esta librería, el modelo de machine learning puede centrarse en aprender a predecir la demanda durante los periodos de interés, evitando la influencia de los periodos en los que no hay demanda.

Librerías

In [1]:
# Tratamiento de datos
# ==============================================================================
import numpy as np
import pandas as pd

# Gráficos
# ==============================================================================
import plotly.graph_objects as go
import plotly.express as px
import plotly.io as pio
import plotly.offline as poff
pio.templates.default = "seaborn"
poff.init_notebook_mode(connected=True)

# Modelado y Forecasting
# ==============================================================================
import skforecast
import sklearn
import lightgbm
from lightgbm import LGBMRegressor
from sklearn.metrics import mean_absolute_error
from skforecast.ForecasterAutoreg import ForecasterAutoreg
from skforecast.model_selection import grid_search_forecaster
from skforecast.model_selection import backtesting_forecaster

# Warnings
# ==============================================================================
import warnings
warnings.filterwarnings('once')

color = '\033[1m\033[38;5;208m' 
print(f"{color}Version skforecast: {skforecast.__version__}")
print(f"{color}Version scikit-learn: {sklearn.__version__}")
print(f"{color}Version pandas: {pd.__version__}")
print(f"{color}Version numpy: {np.__version__}")
Version skforecast: 0.13.0
Version scikit-learn: 1.5.1
Version pandas: 2.2.2
Version numpy: 2.0.1

Datos

Los datos utilizados en este ejemplo representan el número de usuarios que visitaron una tienda durante su horario de apertura de lunes a viernes, entre las 7:00 y las 20:00 horas. Por lo tanto, cualquier predicción fuera de este periodo no es útil y puede ignorarse o fijarse en 0.

In [2]:
# DEscarga de datos
# ======================================================================================
url = ('https://raw.githubusercontent.com/JoaquinAmatRodrigo/Estadistica-machine'
       '-learning-python/master/data/intermittent_demand.csv')
data = pd.read_csv(url, sep=',')
data['date_time'] = pd.to_datetime(data['date_time'], format='%Y-%m-%d %H:%M:%S')
data = data.set_index('date_time')
data = data.asfreq('H')
data = data.sort_index()
data.head(3)
/tmp/ipykernel_25759/938442837.py:8: FutureWarning:

'H' is deprecated and will be removed in a future version, please use 'h' instead.

Out[2]:
users
date_time
2011-01-01 00:00:00 0.0
2011-01-01 01:00:00 0.0
2011-01-01 02:00:00 0.0
In [3]:
# División train-valalidación-test
# ======================================================================================
end_train = '2012-03-31 23:59:00'
end_validation = '2012-08-31 23:59:00'
data_train = data.loc[: end_train, :]
data_val   = data.loc[end_train:end_validation, :]
data_test  = data.loc[end_validation:, :]

print(f"Fechas train      : {data_train.index.min()} --- {data_train.index.max()}  (n={len(data_train)})")
print(f"Fechas validation : {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      : 2011-01-01 00:00:00 --- 2012-03-31 23:00:00  (n=10944)
Fechas validation : 2012-04-01 00:00:00 --- 2012-08-31 23:00:00  (n=3672)
Fechas test       : 2012-09-01 00:00:00 --- 2012-12-31 23:00:00  (n=2928)
In [4]:
# Gráfico time series
# ======================================================================================
fig = go.Figure()
trace1 = go.Scatter(x=data_train.index, y=data_train['users'], name="train", mode="lines")
trace2 = go.Scatter(x=data_val.index, y=data_val['users'], name="validation", mode="lines")
trace3 = go.Scatter(x=data_test.index, y=data_test['users'], name="test", mode="lines")
fig.add_trace(trace1)
fig.add_trace(trace2)
fig.add_trace(trace3)
fig.update_layout(
    title="Serie temporal de usuarios",
    xaxis_title="Date time",
    yaxis_title="Usuarios",
    width  = 800,
    height = 400,
    margin=dict(l=20, r=20, t=50, b=20),
    legend=dict(
        orientation="h",
        yanchor="top",
        y=1.1,
        xanchor="left",
        x=0.001
    )
)
fig.show()
In [5]:
# Boxplot para la estacionalidad semanal
# ==============================================================================
data['week_day'] = data.index.day_of_week + 1
fig = px.box(
        data,
        x="week_day",
        y="users",
        title = 'Distribusión de usuarios por día de la semana',
        width=600,
        height=300
     )
median_values = data.groupby('week_day')['users'].median()
fig.add_trace(
    go.Scatter(
        x=median_values.index,
        y=median_values.values,
        mode='lines+markers',
        line=dict(color='blue', dash='dash'),
        showlegend=False
    )
)
fig.update_layout(margin=dict(l=20, r=20, t=35, b=20))
fig.show()
In [6]:
# Boxplot para la estacionalidad intra-diaria
# ==============================================================================
data['hour_day'] = data.index.hour + 1
fig = px.box(
        data,
        x="hour_day",
        y="users",
        title = 'Distribución de usuarios por hora del día',
        width=600,
        height=300
     )
median_values = data.groupby('hour_day')['users'].median()
fig.add_trace(
    go.Scatter(
        x=median_values.index,
        y=median_values.values,
        mode='lines+markers',
        line=dict(color='blue', dash='dash'),
        showlegend=False
    )
)
fig.update_layout(margin=dict(l=20, r=20, t=35, b=20))
fig.show()