Desarrollar, evaluar y puntuar un modelo de previsión para las ventas de superstore
En este tutorial se presenta un ejemplo completo de un flujo de trabajo de ciencia de datos de Synapse en Microsoft Fabric. El escenario crea un modelo de previsión que usa datos históricos de ventas para predecir las ventas de categorías de productos en un superstore.
La previsión es un activo fundamental en las ventas. Combina datos históricos y métodos predictivos para proporcionar información sobre las tendencias futuras. La previsión puede analizar las ventas anteriores para identificar patrones y aprender del comportamiento del consumidor para optimizar las estrategias de inventario, producción y marketing. Este enfoque proactivo mejora la capacidad de adaptación, la capacidad de respuesta y el rendimiento general de las empresas en un marketplace dinámico.
En este tutorial se describen estos pasos:
- Carga de los datos
- Uso del análisis de datos exploratorios para comprender y procesar los datos
- Entrenamiento de un modelo de Machine Learning con un paquete de software de código abierto y seguimiento de experimentos con MLflow y la característica de registro automático de Fabric
- Guarde el modelo de aprendizaje automático final y realice predicciones.
- Mostrar el rendimiento del modelo con visualizaciones de Power BI
Prerrequisitos
Obtenga una suscripción a Microsoft Fabric. También puede registrarse para obtener una evaluación gratuita de Microsoft Fabric.
Inicie sesión en Microsoft Fabric.
Use el conmutador de experiencia en la parte inferior izquierda de la página principal para cambiar a Fabric.
- Si es necesario, cree un almacén de lago de datos de Microsoft Fabric como se describe en Creación de un almacén de lago de datos en Microsoft Fabric.
Seguimiento en un cuaderno
Puede elegir una de estas opciones para seguir en un cuaderno:
- Abra y ejecute el cuaderno integrado en la experiencia de ciencia de datos de Synapse
- Carga tu cuaderno desde GitHub a la experiencia de Synapse para ciencia de datos
Abra el cuaderno integrado.
El cuaderno de ejemplo de previsión de ventas acompaña a este tutorial.
Para abrir el cuaderno de ejemplo de este tutorial, siga las instrucciones de Preparar el sistema para tutoriales de ciencia de datos.
Asegúrese de adjuntar un almacén de lago de datos al cuaderno antes de empezar a ejecutar el código.
Importación del cuaderno desde GitHub
El cuaderno AIsample - Superstore Forecast.ipynb acompaña a este tutorial.
Para abrir el cuaderno complementario de este tutorial, siga las instrucciones de Cómo preparar su sistema para los tutoriales de ciencia de datos, para importar el cuaderno a su área de trabajo.
Si prefiere copiar y pegar el código de esta página, puede crear un cuaderno nuevo.
Asegúrese de adjuntar una instancia de lakehouse al cuaderno antes de empezar a ejecutar código.
Paso 1: Cargar los datos
El conjunto de datos contiene 9995 instancias de ventas de varios productos. También incluye 21 atributos. Esta tabla procede del archivo Superstore.xlsx usado en este cuaderno:
Identificador de fila | Id. de pedido | Fecha de pedido | Fecha de envío | Modo de envío | Id. de cliente | Nombre del cliente | Segment | País | Ciudad | Estado | Código postal | Región | Id. de producto | Categoría | Subcategoría | Nombre del producto | Ventas | Cantidad | Descuento | Beneficio |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4 | US-2015-108966 | 2015-10-11 | 2015-10-18 | Clase estándar | SO-20335 | Sean O'Donnell | Consumidor | Estados Unidos | Fort Lauderdale | Florida | 33311 | Sur | FUR-TA-10000577 | Mueble | Tablas | Mesa rectangular delgada Serie CR4500 de Bretford | 957,5775 | 5 | 0.45 | -383.0310 |
11 | CA-2014-115812 | 2014-06-09 | 2014-06-09 | Clase estándar | Clase estándar | Brosina Hoffman | Consumidor | Estados Unidos | Los Ángeles | California | 90032 | Oeste | FUR-TA-10001539 | Mueble | Tablas | Tablas de conferencias rectangulares de Chromcraft | 1706.184 | 9 | 0.2 | 85.3092 |
31 | US-2015-150630 | 2015-09-17 | 2015-09-21 | Clase estándar | TB-21520 | Tracy Blumstein | Consumidor | Estados Unidos | Filadelfia | Pensilvania | 19140 | East | OFF-EN-10001509 | Suministros de Office | Sobres | Sobres con cierre de cuerda de poliéster | 3,264 | 2 | 0.2 | 1,1016 |
Defina estos parámetros para poder usar este cuaderno con diferentes conjuntos de datos:
IS_CUSTOM_DATA = False # If TRUE, the dataset has to be uploaded manually
IS_SAMPLE = False # If TRUE, use only rows of data for training; otherwise, use all data
SAMPLE_ROWS = 5000 # If IS_SAMPLE is True, use only this number of rows for training
DATA_ROOT = "/lakehouse/default"
DATA_FOLDER = "Files/salesforecast" # Folder with data files
DATA_FILE = "Superstore.xlsx" # Data file name
EXPERIMENT_NAME = "aisample-superstore-forecast" # MLflow experiment name
Descarga el conjunto de datos y súbelo a la lakehouse
Este código descarga una versión disponible públicamente del conjunto de datos y, a continuación, la almacena en fabric lakehouse:
Importante
Asegúrese de agregar un almacén de lago de datos al cuaderno antes de ejecutarlo. De lo contrario, se producirá un error.
import os, requests
if not IS_CUSTOM_DATA:
# Download data files into the lakehouse if they're not already there
remote_url = "https://synapseaisolutionsa.blob.core.windows.net/public/Forecast_Superstore_Sales"
file_list = ["Superstore.xlsx"]
download_path = "/lakehouse/default/Files/salesforecast/raw"
if not os.path.exists("/lakehouse/default"):
raise FileNotFoundError(
"Default lakehouse not found, please add a lakehouse and restart the session."
)
os.makedirs(download_path, exist_ok=True)
for fname in file_list:
if not os.path.exists(f"{download_path}/{fname}"):
r = requests.get(f"{remote_url}/{fname}", timeout=30)
with open(f"{download_path}/{fname}", "wb") as f:
f.write(r.content)
print("Downloaded demo data files into lakehouse.")
Configuración del seguimiento de experimentos de MLflow
Microsoft Fabric captura automáticamente los valores de los parámetros de entrada y las métricas de salida de un modelo de Machine Learning a medida que se entrena. Esto amplía las funcionalidades de registro automático de MLflow. A continuación, la información se registra en el área de trabajo, donde puede acceder a ella y visualizarla con las API de MLflow o el experimento correspondiente en el área de trabajo. Para obtener más información sobre el registro automático, consulte Registro automático en Microsoft Fabric.
Para desactivar el registro automático de Microsoft Fabric en una sesión de cuaderno, llame a mlflow.autolog()
y establezca disable=True
:
# Set up MLflow for experiment tracking
import mlflow
mlflow.set_experiment(EXPERIMENT_NAME)
mlflow.autolog(disable=True) # Turn off MLflow autologging
Lectura de datos sin procesar desde el lago de datos
Lea datos sin procesar de la sección Archivos del lago de datos. Agregue más columnas para diferentes partes de fecha. La misma información se usa para crear una tabla delta con particiones. Dado que los datos sin procesar se almacenan como un archivo de Excel, debe usar pandas para leerlos:
import pandas as pd
df = pd.read_excel("/lakehouse/default/Files/salesforecast/raw/Superstore.xlsx")
Paso 2: Realizar análisis de datos exploratorios
Importar bibliotecas
Antes de cualquier análisis, importe las bibliotecas necesarias:
# Importing required libraries
import warnings
import itertools
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
plt.style.use('fivethirtyeight')
import pandas as pd
import statsmodels.api as sm
import matplotlib
matplotlib.rcParams['axes.labelsize'] = 14
matplotlib.rcParams['xtick.labelsize'] = 12
matplotlib.rcParams['ytick.labelsize'] = 12
matplotlib.rcParams['text.color'] = 'k'
from sklearn.metrics import mean_squared_error,mean_absolute_percentage_error
Mostrar los datos sin procesar
Revise manualmente un subconjunto de los datos para comprender mejor el propio conjunto de datos y use la función display
para imprimir el dataframe. Además, las vistas de Chart
pueden visualizar fácilmente subconjuntos del conjunto de datos.
display(df)
Este cuaderno se centra principalmente en la previsión de las ventas de la categoría Furniture
. Esto acelera el cálculo y ayuda a mostrar el rendimiento del modelo. Sin embargo, este cuaderno usa técnicas adaptables. Puede ampliar esas técnicas para predecir las ventas de otras categorías de productos.
# Select "Furniture" as the product category
furniture = df.loc[df['Category'] == 'Furniture']
print(furniture['Order Date'].min(), furniture['Order Date'].max())
Preprocesar los datos
Los escenarios empresariales del mundo real suelen necesitar predecir las ventas en tres categorías distintas:
- Una categoría de producto específica
- Una categoría de cliente específica
- Una combinación específica de categoría de producto y categoría de cliente
En primer lugar, quite las columnas innecesarias para preprocesar los datos. Algunas de las columnas (Row ID
, Order ID
,Customer ID
y Customer Name
) no son necesarias porque no tienen ningún impacto. Queremos predecir las ventas generales, en el estado y la región, para una categoría de producto específica (Furniture
), por lo que podemos quitar las columnas State
, Region
, Country
, City
y Postal Code
. Para predecir las ventas de una ubicación o categoría específicas, es posible que tenga que ajustar el paso de preprocesamiento en consecuencia.
# Data preprocessing
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name',
'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category',
'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
# Drop unnecessary columns
furniture.drop(cols, axis=1, inplace=True)
furniture = furniture.sort_values('Order Date')
furniture.isnull().sum()
El conjunto de datos se estructura diariamente. Debemos re-muestrear en la columna Order Date
, ya que queremos desarrollar un modelo para pronosticar las ventas en una base mensual.
En primer lugar, agrupe la categoría Furniture
por Order Date
. A continuación, calcule la suma de la columna de Sales
para cada grupo, para determinar el total de ventas de cada valor único Order Date
. Haga un nuevo muestreo de la columna Sales
con la frecuencia de MS
, para agregar los datos por mes. Por último, calcule el valor medio de ventas de cada mes.
# Data preparation
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
furniture = furniture.set_index('Order Date')
furniture.index
y = furniture['Sales'].resample('MS').mean()
y = y.reset_index()
y['Order Date'] = pd.to_datetime(y['Order Date'])
y['Order Date'] = [i+pd.DateOffset(months=67) for i in y['Order Date']]
y = y.set_index(['Order Date'])
maximim_date = y.reset_index()['Order Date'].max()
Demuestre el impacto de Order Date
en Sales
para la categoría de Furniture
:
# Impact of order date on the sales
y.plot(figsize=(12, 3))
plt.show()
Antes de cualquier análisis estadístico, debe importar el módulo statsmodels
Python. Proporciona clases y funciones para la estimación de muchos modelos estadísticos. También proporciona clases y funciones para realizar pruebas estadísticas y exploración de datos estadísticos.
import statsmodels.api as sm
Realizar análisis estadísticos
Una serie temporal realiza un seguimiento de estos elementos de datos a intervalos establecidos para determinar la variación de esos elementos en el patrón de serie temporal:
Nivel: componente fundamental que representa el valor medio de un período de tiempo específico
Trend: describe si la serie temporal disminuye, permanece constante o aumenta con el tiempo.
estacionalidad: describe la señal periódica en la serie temporal y busca apariciones cíclicas que afectan a los patrones de series temporales crecientes o decrecientes.
Ruido/Residual: Hace referencia a las fluctuaciones y variabilidad aleatorias en los datos de serie temporal que el modelo no puede explicar.
En este código, observará esos elementos para el conjunto de datos después del preprocesamiento:
# Decompose the time series into its components by using statsmodels
result = sm.tsa.seasonal_decompose(y, model='additive')
# Labels and corresponding data for plotting
components = [('Seasonality', result.seasonal),
('Trend', result.trend),
('Residual', result.resid),
('Observed Data', y)]
# Create subplots in a grid
fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(12, 7))
plt.subplots_adjust(hspace=0.8) # Adjust vertical space
axes = axes.ravel()
# Plot the components
for ax, (label, data) in zip(axes, components):
ax.plot(data, label=label, color='blue' if label != 'Observed Data' else 'purple')
ax.set_xlabel('Time')
ax.set_ylabel(label)
ax.set_xlabel('Time', fontsize=10)
ax.set_ylabel(label, fontsize=10)
ax.legend(fontsize=10)
plt.show()
Los gráficos describen la estacionalidad, las tendencias y el ruido en los datos de previsión. Puede capturar los patrones subyacentes y desarrollar modelos que realicen predicciones precisas que sean resistentes a las fluctuaciones aleatorias.
Paso 3: Entrenamiento y seguimiento del modelo
Ahora que tiene los datos disponibles, defina el modelo de previsión. En este cuaderno, aplique el modelo de previsión denominado media móvil integrada autorregresiva estacional con factores exógenos (SARIMAX). SARIMAX combina componentes autorregresivos (AR) y de media móvil (MA), diferenciación estacional (S) y predictores externos para hacer pronósticos precisos y flexibles para los datos de series temporales.
También se usa el registro automático de MLflow y Fabric para realizar un seguimiento de los experimentos. Aquí, cargue la tabla delta desde el lago de datos. Puede usar otras tablas delta teniendo en cuenta el almacén de lago como origen.
# Import required libraries for model evaluation
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
Ajuste de hiperparámetros
SARIMAX tiene en cuenta los parámetros implicados en el modo de media móvil integrada (ARIMA) normal (p
, d
, q
) y agrega los parámetros de estacionalidad (P
, D
, Q
, s
). Estos argumentos del modelo SARIMAX se denominan orden (p
, d
, q
) y de orden estacional (P
, D
, Q
, s
), respectivamente. Por lo tanto, para entrenar el modelo, primero debemos ajustar siete parámetros.
Parámetros de orden:
p
: el orden del componente AR, que representa el número de observaciones anteriores en la serie temporal usada para predecir el valor actual.Normalmente, este parámetro debe ser un entero no negativo. Los valores comunes están en el intervalo de
0
a3
, aunque son posibles valores más altos, en función de las características de datos específicas. Un valor dep
superior indica una memoria más larga de valores anteriores en el modelo.d
: el orden de diferenciación, que representa el número de veces que debe diferenciarse la serie temporal, para lograr la estacionaridad.Este parámetro debe ser un entero no negativo. Los valores comunes están en el intervalo de
0
para2
. Un valor ded
de0
significa que la serie temporal ya es estacionaria. Los valores más altos indican el número de operaciones de diferenciación necesarias para que sea estacionario.q
: El orden del componente MA, que representa el número de términos de error de ruido blanco pasados que se utilizan para predecir el valor actual.Este parámetro debe ser un entero no negativo. Los valores comunes están en el intervalo de
0
a3
, pero los valores más altos podrían ser necesarios para determinadas series temporales. Un valor deq
mayor indica una mayor dependencia de los términos de error pasados para realizar predicciones.
Los parámetros de orden estacional:
P
: el orden estacional del componente AR, similar ap
pero para la parte estacionalD
: el orden estacional de diferenciación, similar ad
pero para la parte estacionalQ
: el orden estacional del componente ma, similar aq
pero para la parte estacionals
: el número de pasos de tiempo por ciclo estacional (por ejemplo, 12 para los datos mensuales con una estacionalidad anual)
# Hyperparameter tuning
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]
print('Examples of parameter combinations for Seasonal ARIMA...')
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[1]))
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[2]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[3]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[4]))
SARIMAX tiene otros parámetros:
enforce_stationarity
: si el modelo debe aplicar la estacionalidad o no en los datos de serie temporal antes de ajustar el modelo SARIMAX.Si
enforce_stationarity
se establece enTrue
(por defecto), indica que el modelo SARIMAX debe aplicar la estacionariedad a los datos de serie temporal. A continuación, el modelo SARIMAX aplica automáticamente diferenciación a los datos, para hacerlos estacionarios, según lo especificado por los órdenesd
yD
, antes de ajustar el modelo. Se trata de una práctica común porque muchos modelos de serie temporal, incluido SARIMAX, asumen que los datos son fijos.Para una serie temporal no estacionaria (por ejemplo, muestra tendencias o estacionalidad), se recomienda establecer
enforce_stationarity
enTrue
y permitir que el modelo SARIMAX controle la diferenciación para lograr la estacionariedad. Para una serie temporal fija (por ejemplo, una sin tendencias o estacionalidad), establezcaenforce_stationarity
enFalse
para evitar diferencias innecesarias.enforce_invertibility
: controla si el modelo debe aplicar o no la invertibilidad en los parámetros estimados durante el proceso de optimización.Si
enforce_invertibility
se establece enTrue
(valor predeterminado), indica que el modelo SARIMAX debe aplicar la invertibilidad en los parámetros estimados. La invertibilidad garantiza que el modelo esté bien definido y que los coeficientes estimados de AR y MA se encuentran dentro del intervalo de estacionaridad.La aplicación de la invertibilidad ayuda a garantizar que el modelo SARIMAX cumpla los requisitos teóricos de un modelo de serie temporal estable. También ayuda a evitar problemas con la estimación y la estabilidad del modelo.
El valor predeterminado es un modelo AR(1)
. Esto hace referencia a (1, 0, 0)
. Sin embargo, es habitual probar diferentes combinaciones de los parámetros de pedido y los parámetros de orden estacional y evaluar el rendimiento del modelo de un conjunto de datos. Los valores adecuados pueden variar de una serie temporal a otra.
La determinación de los valores óptimos suele implicar el análisis de la función de autocorrelación (ACF) y la función de autocorrelación parcial (PACF) de los datos de serie temporal. También suele implicar el uso de criterios de selección de modelos( por ejemplo, el criterio de información Akaike (AIC) o el criterio de información bayesiana (BIC).
Ajuste los hiperparámetros:
# Tune the hyperparameters to determine the best model
for param in pdq:
for param_seasonal in seasonal_pdq:
try:
mod = sm.tsa.statespace.SARIMAX(y,
order=param,
seasonal_order=param_seasonal,
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit(disp=False)
print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
except:
continue
Después de la evaluación de los resultados anteriores, puede determinar los valores de los parámetros de pedido y los parámetros de orden estacional. La elección es order=(0, 1, 1)
y seasonal_order=(0, 1, 1, 12)
, que ofrecen el AIC más bajo (por ejemplo, 279.58). Use estos valores para entrenar el modelo.
Entrenamiento del modelo
# Model training
mod = sm.tsa.statespace.SARIMAX(y,
order=(0, 1, 1),
seasonal_order=(0, 1, 1, 12),
enforce_stationarity=False,
enforce_invertibility=False)
results = mod.fit(disp=False)
print(results.summary().tables[1])
Este código visualiza una previsión de series temporales para los datos de ventas de muebles. Los resultados representados gráficamente muestran tanto los datos observados como la predicción de un paso hacia adelante, con una región sombreada para el intervalo de confianza.
# Plot the forecasting results
pred = results.get_prediction(start=maximim_date, end=maximim_date+pd.DateOffset(months=6), dynamic=False) # Forecast for the next 6 months (months=6)
pred_ci = pred.conf_int() # Extract the confidence intervals for the predictions
ax = y['2019':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead forecast', alpha=.7, figsize=(12, 7))
ax.fill_between(pred_ci.index,
pred_ci.iloc[:, 0],
pred_ci.iloc[:, 1], color='k', alpha=.2)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()
plt.show()
# Validate the forecasted result
predictions = results.get_prediction(start=maximim_date-pd.DateOffset(months=6-1), dynamic=False)
# Forecast on the unseen future data
predictions_future = results.get_prediction(start=maximim_date+ pd.DateOffset(months=1),end=maximim_date+ pd.DateOffset(months=6),dynamic=False)
Use predictions
para evaluar el rendimiento del modelo, comparándolo con los valores reales. El valor de predictions_future
indica la previsión futura.
# Log the model and parameters
model_name = f"{EXPERIMENT_NAME}-Sarimax"
with mlflow.start_run(run_name="Sarimax") as run:
mlflow.statsmodels.log_model(results,model_name,registered_model_name=model_name)
mlflow.log_params({"order":(0,1,1),"seasonal_order":(0, 1, 1, 12),'enforce_stationarity':False,'enforce_invertibility':False})
model_uri = f"runs:/{run.info.run_id}/{model_name}"
print("Model saved in run %s" % run.info.run_id)
print(f"Model URI: {model_uri}")
mlflow.end_run()
# Load the saved model
loaded_model = mlflow.statsmodels.load_model(model_uri)
Paso 4: Puntuar el modelo y guardar predicciones
Integre los valores reales con los valores previstos para crear un informe de Power BI. Almacene estos resultados en una tabla dentro de lakehouse.
# Data preparation for Power BI visualization
Future = pd.DataFrame(predictions_future.predicted_mean).reset_index()
Future.columns = ['Date','Forecasted_Sales']
Future['Actual_Sales'] = np.NAN
Actual = pd.DataFrame(predictions.predicted_mean).reset_index()
Actual.columns = ['Date','Forecasted_Sales']
y_truth = y['2023-02-01':]
Actual['Actual_Sales'] = y_truth.values
final_data = pd.concat([Actual,Future])
# Calculate the mean absolute percentage error (MAPE) between 'Actual_Sales' and 'Forecasted_Sales'
final_data['MAPE'] = mean_absolute_percentage_error(Actual['Actual_Sales'], Actual['Forecasted_Sales']) * 100
final_data['Category'] = "Furniture"
final_data[final_data['Actual_Sales'].isnull()]
input_df = y.reset_index()
input_df.rename(columns = {'Order Date':'Date','Sales':'Actual_Sales'}, inplace=True)
input_df['Category'] = 'Furniture'
input_df['MAPE'] = np.NAN
input_df['Forecasted_Sales'] = np.NAN
# Write back the results into the lakehouse
final_data_2 = pd.concat([input_df,final_data[final_data['Actual_Sales'].isnull()]])
table_name = "Demand_Forecast_New_1"
spark.createDataFrame(final_data_2).write.mode("overwrite").format("delta").save(f"Tables/{table_name}")
print(f"Spark DataFrame saved to delta table: {table_name}")
Paso 5: Visualización en Power BI
El informe de Power BI muestra un error medio de porcentaje absoluto (MAPE) de 16,58. La métrica MAPE define la precisión de un método de previsión. Representa la precisión de las cantidades previstas, en comparación con las cantidades reales.
MAPE es una métrica sencilla. Un MAPE de 10% representa que la desviación media entre los valores previstos y los valores reales es 10%, independientemente de si la desviación era positiva o negativa. Los estándares de valores MAPE deseables varían en todos los sectores.
La línea azul claro de este gráfico representa los valores reales de ventas. La línea azul oscuro representa los valores de ventas previstos. La comparación de ventas reales y previstas revela que el modelo predice eficazmente las ventas de la categoría Furniture
durante los primeros seis meses de 2023.
En función de esta observación, podemos tener confianza en las capacidades de previsión del modelo con respecto a las ventas generales en los últimos seis meses de 2023 y que se extienden hasta 2024. Esta confianza puede informar a las decisiones estratégicas sobre la gestión del inventario, la adquisición de materias primas y otras consideraciones relacionadas con la empresa.