Walmart – Predicción de Ventas en Tiendas

Para este proyecto de aprendizaje automático, utilizaremos el conjunto de datos «Walmart Recruiting – Store Sales Forecasting», de Kaggle.
El objetivo es predecir las ventas semanales de tiendas, departamentos y fechas específicas.
Descargar datos
En primer lugar, instalamos la biblioteca opendatasets.
In [1]:
pip install opendatasets --upgrade
Collecting opendatasets Downloading opendatasets-0.1.20-py3-none-any.whl (14 kB) Requirement already satisfied: click in /opt/conda/lib/python3.9/site-packages (from opendatasets) (8.0.1) Collecting kaggle Downloading kaggle-1.5.12.tar.gz (58 kB) |████████████████████████████████| 58 kB 4.8 MB/s eta 0:00:011 Requirement already satisfied: tqdm in /opt/conda/lib/python3.9/site-packages (from opendatasets) (4.61.2) Requirement already satisfied: six>=1.10 in /opt/conda/lib/python3.9/site-packages (from kaggle->opendatasets) (1.16.0) Requirement already satisfied: certifi in /opt/conda/lib/python3.9/site-packages (from kaggle->opendatasets) (2021.5.30) Requirement already satisfied: python-dateutil in /opt/conda/lib/python3.9/site-packages (from kaggle->opendatasets) (2.8.2) Requirement already satisfied: requests in /opt/conda/lib/python3.9/site-packages (from kaggle->opendatasets) (2.26.0) Collecting python-slugify Downloading python_slugify-5.0.2-py2.py3-none-any.whl (6.7 kB) Requirement already satisfied: urllib3 in /opt/conda/lib/python3.9/site-packages (from kaggle->opendatasets) (1.26.6) Collecting text-unidecode>=1.3 Downloading text_unidecode-1.3-py2.py3-none-any.whl (78 kB) |████████████████████████████████| 78 kB 6.7 MB/s eta 0:00:01 Requirement already satisfied: charset-normalizer~=2.0.0 in /opt/conda/lib/python3.9/site-packages (from requests->kaggle->opendatasets) (2.0.0) Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.9/site-packages (from requests->kaggle->opendatasets) (3.1) Building wheels for collected packages: kaggle Building wheel for kaggle (setup.py) ... done Created wheel for kaggle: filename=kaggle-1.5.12-py3-none-any.whl size=73053 sha256=dd4304085b0006cc5664f8c02d94d7c54a480af025e40f5521209280e0c17b9a Stored in directory: /home/jovyan/.cache/pip/wheels/ac/b2/c3/fa4706d469b5879105991d1c8be9a3c2ef329ba9fe2ce5085e Successfully built kaggle Installing collected packages: text-unidecode, python-slugify, kaggle, opendatasets Successfully installed kaggle-1.5.12 opendatasets-0.1.20 python-slugify-5.0.2 text-unidecode-1.3 Note: you may need to restart the kernel to use updated packages.
Ahora importamos algunas librerías que utilizaremos
In [2]:
import opendatasets as od
import os
from zipfile import ZipFile
import numpy as np # algebra lineal
import pandas as pd # procesamiento de data
import seaborn as sns; sns.set(style="ticks", color_codes=True)
import matplotlib.pyplot as plt
Aquí descargamos los datasets de Kaggle
In [4]:
dataset_url = 'https://www.kaggle.com/c/walmart-recruiting-store-sales-forecasting' od.download('https://www.kaggle.com/c/walmart-recruiting-store-sales-forecasting')
Skipping, found downloaded files in "./walmart-recruiting-store-sales-forecasting" (use force=True to force download)
Revisemos los archivos descargados.
In [5]:
os.listdir('walmart-recruiting-store-sales-forecasting')
Out[5]:
['test.csv.zip', 'train.csv.zip', 'stores.csv', 'features.csv.zip', 'sampleSubmission.csv.zip']
1. stores.csv: Este archivo contiene información anonimizada sobre las 45 tiendas, indicando el tipo y el tamaño de la tienda.
2. train.csv: Estos son los datos históricos de entrenamiento, que cubren desde 2010-02-05 hasta 2012-11-01. Dentro de este archivo encontraremos los siguientes campos:
- Store – el número de tienda
- Dept – el número de departamento
- Date – la semana
- Weekly_Sales – ventas para el departamento dado en la tienda dada
- IsHoliday – si la semana es una semana especial de vacaciones
3. test.csv: Este archivo es idéntico a train.csv, salvo que hemos omitido las ventas semanales. Debemos predecir las ventas para cada triplete de tienda, departamento y fecha en este archivo.
4. features.csv: Este archivo contiene datos adicionales relacionados con la tienda, el departamento y la actividad regional para las fechas dadas. Contiene los siguientes campos:
- Store- el número de la tienda
- Date – la semana
- Temperature – temperatura media en la región
- Fuel_Price – coste del combustible en la región
- MarkDown1-5 – datos anónimos relacionados con las rebajas promocionales de Walmart. Los datos de MarkDown sólo están disponibles a partir de noviembre de 2011 y no están disponibles para todas las tiendas en todo momento. Cualquier valor que falte se marca con un NA.
- CPI – el índice de precios al consumo
- Unemployment – la tasa de desempleo
- IsHoliday – si la semana es una semana especial de vacaciones
Ahora, obtenemos los archivos zip y creamos los conjuntos de datos que utilizaremos
In [6]:
train_zip_file = ZipFile('walmart-recruiting-store-sales-forecasting/train.csv.zip') features_zip_file = ZipFile('walmart-recruiting-store-sales-forecasting/features.csv.zip') test_zip_file = ZipFile('walmart-recruiting-store-sales-forecasting/test.csv.zip') sample_submission_zip_file = ZipFile('walmart-recruiting-store-sales-forecasting/sampleSubmission.csv.zip')
In [7]:
train_df = pd.read_csv(train_zip_file.open('train.csv')) features_df = pd.read_csv(features_zip_file.open('features.csv')) stores_df = pd.read_csv('walmart-recruiting-store-sales-forecasting/stores.csv') test_df = pd.read_csv(test_zip_file.open('test.csv')) submission_df = pd.read_csv(sample_submission_zip_file.open('sampleSubmission.csv'))
Antes de empezar a trabajar con los datos, podemos fusionar los archivos train, stores y features, para aumentar el número de variables de entrada.
In [8]:
dataset = train_df.merge(stores_df, how='left').merge(features_df, how='left') test_dataset = test_df.merge(stores_df, how='left').merge(features_df, how='left')
Exploración de datos
Veamos nuestro dataset
In [9]:
dataset.head()
Out[9]:
Store | Dept | Date | Weekly_Sales | IsHoliday | Type | Size | Temperature | Fuel_Price | MarkDown1 | MarkDown2 | MarkDown3 | MarkDown4 | MarkDown5 | CPI | Unemployment | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 2010-02-05 | 24924.50 | False | A | 151315 | 42.31 | 2.572 | NaN | NaN | NaN | NaN | NaN | 211.096358 | 8.106 |
1 | 1 | 1 | 2010-02-12 | 46039.49 | True | A | 151315 | 38.51 | 2.548 | NaN | NaN | NaN | NaN | NaN | 211.242170 | 8.106 |
2 | 1 | 1 | 2010-02-19 | 41595.55 | False | A | 151315 | 39.93 | 2.514 | NaN | NaN | NaN | NaN | NaN | 211.289143 | 8.106 |
3 | 1 | 1 | 2010-02-26 | 19403.54 | False | A | 151315 | 46.63 | 2.561 | NaN | NaN | NaN | NaN | NaN | 211.319643 | 8.106 |
4 | 1 | 1 | 2010-03-05 | 21827.90 | False | A | 151315 | 46.50 | 2.625 | NaN | NaN | NaN | NaN | NaN | 211.350143 | 8.106 |
Podemos identificar las siguientes variables de entrada:
- Store
- Dept
- Date
- IsHoliday
- Type
- Size
- Temperature
- Fuel_Price
- MarkDown1
- MarkDown2
- MarkDown3
- MarkDown4
- MarkDown5
- CPI
- Unemployment
La variable objetivo es Weekly_Sales
Repasemos ahora algunas medidas de tendencia central.
In [10]:
dataset.describe(include = 'all')
Out[10]:
Metric | Store | Dept | Date | Weekly_Sales | IsHoliday | Type | Size | Temperature | Fuel_Price | MarkDown1 | MarkDown2 | MarkDown3 | MarkDown4 | MarkDown5 | CPI | Unemployment |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 421570.000000 | 421570.000000 | 421570 | 421570.000000 | 421570 | 421570 | 421570.000000 | 421570.000000 | 421570.000000 | 150681.000000 | 111248.000000 | 137091.000000 | 134967.000000 | 151432.000000 | 421570.000000 | 421570.000000 |
unique | NaN | NaN | 143 | NaN | 2 | 3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
top | NaN | NaN | 2011-12-23 | NaN | False | A | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
freq | NaN | NaN | 3027 | NaN | 391909 | 215478 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
mean | 22.200546 | 44.260317 | NaN | 15981.258123 | NaN | NaN | 136727.915739 | 60.090059 | 3.361027 | 7246.420196 | 3334.628621 | 1439.421384 | 3383.168256 | 4628.975079 | 171.201947 | 7.960289 |
std | 12.785297 | 30.492054 | NaN | 22711.183519 | NaN | NaN | 60980.583328 | 18.447931 | 0.458515 | 8291.221345 | 9475.357325 | 9623.078290 | 6292.384031 | 5962.887455 | 39.159276 | 1.863296 |
min | 1.000000 | 1.000000 | NaN | -4988.940000 | NaN | NaN | 34875.000000 | -2.060000 | 2.472000 | 0.270000 | -265.760000 | -29.100000 | 0.220000 | 135.160000 | 126.064000 | 3.879000 |
25% | 11.000000 | 18.000000 | NaN | 2079.650000 | NaN | NaN | 93638.000000 | 46.680000 | 2.933000 | 2240.270000 | 41.600000 | 5.080000 | 504.220000 | 1878.440000 | 132.022667 | 6.891000 |
50% | 22.000000 | 37.000000 | NaN | 7612.030000 | NaN | NaN | 140167.000000 | 62.090000 | 3.452000 | 5347.450000 | 192.000000 | 24.600000 | 1481.310000 | 3359.450000 | 182.318780 | 7.866000 |
75% | 33.000000 | 74.000000 | NaN | 20205.852500 | NaN | NaN | 202505.000000 | 74.280000 | 3.738000 | 9210.900000 | 1926.940000 | 103.990000 | 3595.040000 | 5563.800000 | 212.416993 | 8.572000 |
max | 45.000000 | 99.000000 | NaN | 693099.360000 | NaN | NaN | 219622.000000 | 100.140000 | 4.468000 | 88646.760000 | 104519.540000 | 141630.610000 | 67474.850000 | 108519.280000 | 227.232807 | 14.313000 |
Valores Nulos
Hay algunos valores nulos, repasémoslos
In [11]:
# Comprobación del porcentaje de NaN
dataset.isnull().mean() * 100
Out[11]:
Store 0.000000 Dept 0.000000 Date 0.000000 Weekly_Sales 0.000000 IsHoliday 0.000000 Type 0.000000 Size 0.000000 Temperature 0.000000 Fuel_Price 0.000000 MarkDown1 64.257181 MarkDown2 73.611025 MarkDown3 67.480845 MarkDown4 67.984676 MarkDown5 64.079038 CPI 0.000000 Unemployment 0.000000 dtype: float64
Dejaremos los valores nulos en la sección Manipulación de datos.
Correlación de Variables de entrada con la característica de salida Weekly_Sales
In [12]:
corr = dataset.corr() f, ax = plt.subplots(figsize=(15, 15)) cmap = sns.diverging_palette(220, 20, as_cmap=True) sns.heatmap(corr, cmap=cmap, vmax=.3, center=0, annot=True, square=True, linewidths=.5, cbar_kws={'shrink': .5}) plt.show()

Observando la matriz de correlaciones, podemos ver que Weekly_Sales tiene una mayor correlación con Store, Dept y Size.
Eliminaremos las variables con menor correlación en la sección Manipulación de datos.
Manipulación de Datos
In [13]:
from sklearn.impute import SimpleImputer from sklearn.preprocessing import MinMaxScaler, OneHotEncoder from sklearn.model_selection import train_test_split
Ahora, haremos los siguientes pasos:
- Eliminar los valores nulos de las variables markdown.
- Cree variables para el año, el mes y la semana, basadas en el campo de fecha.
- Elimine las variables con baja correlación.
In [14]:
dataset[['MarkDown1','MarkDown2','MarkDown3','MarkDown4', 'MarkDown5']] = dataset[['MarkDown1','MarkDown2','MarkDown3','MarkDown4','MarkDown5']].fillna(0) dataset['Year'] = pd.to_datetime(dataset['Date']).dt.year dataset['Month'] = pd.to_datetime(dataset['Date']).dt.month dataset['Week'] = pd.to_datetime(dataset['Date']).dt.isocalendar().week dataset = dataset.drop(columns=["Date", "CPI", "Fuel_Price", 'Unemployment', 'Temperature'])
Podemos mover la variable de destino a la última columna del marco de datos para facilitar la manipulación de los datos.
In [15]:
df = dataset.pop('Weekly_Sales') dataset['Weekly_Sales'] = df
Aquí identificamos las entradas y las columnas de destino.
In [16]:
input_cols, target_col = dataset.columns[:-1], dataset.columns[-1] inputs_df, targets = dataset[input_cols].copy(), dataset[target_col].copy()
Ahora, identificamos las columnas numéricas y categóricas.
In [17]:
numeric_cols = dataset[input_cols].select_dtypes(include=np.number).columns.tolist() categorical_cols = dataset[input_cols].select_dtypes(include='object').columns.tolist()
Aquí, imputamos (rellenamos) y escalamos columnas numéricas.
In [18]:
imputer = SimpleImputer().fit(inputs_df[numeric_cols]) inputs_df[numeric_cols] = imputer.transform(inputs_df[numeric_cols]) scaler = MinMaxScaler().fit(inputs_df[numeric_cols]) inputs_df[numeric_cols] = scaler.transform(inputs_df[numeric_cols])
Sólo podemos utilizar datos numéricos para entrenar nuestros modelos, por eso tenemos que utilizar una técnica llamada «codificación de un solo golpe» para nuestras columnas categóricas.
Una codificación en caliente consiste en añadir una nueva columna binaria (0/1) para cada categoría única de una columna categórica.
In [19]:
encoder = OneHotEncoder(sparse=False, handle_unknown='ignore').fit(inputs_df[categorical_cols]) encoded_cols = list(encoder.get_feature_names(categorical_cols)) inputs_df[encoded_cols] = encoder.transform(inputs_df[categorical_cols])
Por último, dividamos el conjunto de datos en un conjunto de entrenamiento y otro de validación. Para la validación utilizaremos un 25% de los datos seleccionados aleatoriamente. Además, utilizaremos sólo las columnas numéricas y codificadas, ya que las entradas de nuestro modelo deben ser números.
In [20]:
train_inputs, val_inputs, train_targets, val_targets = train_test_split( inputs_df[numeric_cols + encoded_cols], targets, test_size=0.25, random_state=42)
Modelos
Ahora que tenemos nuestro conjunto de entrenamiento y validación, revisaremos dos modelos de machine learning:
- Árbol de decisión
- Bosque aleatorio
En función de los resultados, elegiremos a uno de ellos.
Árbol de Decisión
Un árbol de decisión es una herramienta de apoyo a la toma de decisiones que utiliza un modelo arborescente de decisiones y sus posibles consecuencias, incluidos los resultados de sucesos fortuitos, los costes de recursos y la utilidad. Es una forma de mostrar un algoritmo que sólo contiene sentencias de control condicional.
Para crear nuestro modelo de árbol de decisión, podemos utilizar la función DecisionTreeRegressor.
In [21]:
from sklearn.tree import DecisionTreeRegressor
In [22]:
tree = DecisionTreeRegressor(random_state=0)
Ahora, ajustamos nuestro modelo a los datos de entrenamiento.
In [23]:
%%time tree.fit(train_inputs, train_targets)
CPU times: user 2.17 s, sys: 26.8 ms, total: 2.2 s Wall time: 2.19 s
Out[23]:
DecisionTreeRegressor(random_state=0)
Generamos predicciones sobre los conjuntos de entrenamiento y validación utilizando el árbol de decisión entrenado y calculamos la pérdida de error cuadrático medio (RMSE).
In [24]:
from sklearn.metrics import mean_squared_error
In [25]:
tree_train_preds = tree.predict(train_inputs)
In [26]:
tree_train_rmse = mean_squared_error(train_targets, tree_train_preds, squared=False)
In [27]:
tree_val_preds = tree.predict(val_inputs)
In [28]:
tree_val_rmse = mean_squared_error(val_targets, tree_val_preds, squared=False)
In [29]:
print('Train RMSE: {}, Validation RMSE: {}'.format(tree_train_rmse, tree_val_rmse))
Train RMSE: 6.045479193458183e-20, Validation RMSE: 5441.340994336662
Aquí, podemos ver que la pérdida RMSE para nuestros datos de entrenamiento es 6.045479193458183e-20, y la pérdida RMSE para nuestros datos de validación es 5441.340994336662
Visualización del árbol de decisión
In [30]:
import matplotlib.pyplot as plt from sklearn.tree import plot_tree, export_text import seaborn as sns sns.set_style('darkgrid') %matplotlib inline
Visualicemos el árbol gráficamente utilizando plot_tree.
In [31]:
plt.figure(figsize=(30,15)) plot_tree(tree, feature_names=train_inputs.columns, max_depth=3, filled=True);

Ahora, vamos a visualizar el árbol textualmente usando export_text.
In [32]:
tree_text = export_text(tree, feature_names=list(train_inputs.columns))
Aquí podemos visualizar las primeras líneas.
In [33]:
print(tree_text[:2000])
|--- Dept <= 0.89 | |--- Dept <= 0.13 | | |--- Size <= 0.35 | | | |--- Size <= 0.08 | | | | |--- Dept <= 0.02 | | | | | |--- Dept <= 0.01 | | | | | | |--- Type_A <= 0.50 | | | | | | | |--- Week <= 0.75 | | | | | | | | |--- Week <= 0.32 | | | | | | | | | |--- MarkDown5 <= 0.01 | | | | | | | | | | |--- Store <= 0.94 | | | | | | | | | | | |--- truncated branch of depth 14 | | | | | | | | | | |--- Store > 0.94 | | | | | | | | | | | |--- truncated branch of depth 13 | | | | | | | | | |--- MarkDown5 > 0.01 | | | | | | | | | | |--- MarkDown5 <= 0.01 | | | | | | | | | | | |--- truncated branch of depth 6 | | | | | | | | | | |--- MarkDown5 > 0.01 | | | | | | | | | | | |--- truncated branch of depth 14 | | | | | | | | |--- Week > 0.32 | | | | | | | | | |--- Store <= 0.07 | | | | | | | | | | |--- MarkDown5 <= 0.00 | | | | | | | | | | | |--- truncated branch of depth 10 | | | | | | | | | | |--- MarkDown5 > 0.00 | | | | | | | | | | | |--- truncated branch of depth 9 | | | | | | | | | |--- Store > 0.07 | | | | | | | | | | |--- Store <= 0.83 | | | | | | | | | | | |--- truncated branch of depth 15 | | | | | | | | | | |--- Store > 0.83 | | | | | | | | | | | |--- truncated branch of depth 15 | | | | | | | |--- Week > 0.75 | | | | | | | | |--- Week <= 0.85 | | | | | | | | | |--- Week <= 0.81 | | | | | | | | | | |--- Store <= 0.07 | | | | | | | | | | | |--- truncated branch of depth 5 | | | | |
Árbol de decisión Importancia de las características
Veamos las ponderaciones asignadas a las distintas columnas, para averiguar qué columnas del conjunto de datos son las más importantes.
In [34]:
tree_importances = tree.feature_importances_
In [35]:
tree_importance_df = pd.DataFrame({ 'feature': train_inputs.columns, 'importance': tree_importances }).sort_values('importance', ascending=False)
In [36]:
tree_importance_df
Out[36]:
# | feature | importance |
---|---|---|
1 | Dept | 0.636039 |
2 | Size | 0.198802 |
0 | Store | 0.072103 |
10 | Week | 0.055737 |
12 | Type_B | 0.013551 |
5 | MarkDown3 | 0.004337 |
9 | Month | 0.003614 |
8 | Year | 0.003393 |
6 | MarkDown4 | 0.003347 |
11 | Type_A | 0.003196 |
7 | MarkDown5 | 0.002141 |
3 | MarkDown1 | 0.001904 |
4 | MarkDown2 | 0.001453 |
13 | Type_C | 0.000383 |
In [37]:
plt.title('Decision Tree Feature Importance') sns.barplot(data=tree_importance_df.head(10), x='importance', y='feature');

Las variables Departamento, Tamaño, Tienda y Semana son las más importantes para este modelo.
Bosques Aleatorios
Los bosques aleatorios son un método de aprendizaje conjunto para clasificación, regresión y otras tareas que funciona construyendo una multitud de árboles de decisión en el momento del entrenamiento.
Para las tareas de clasificación, el resultado del bosque aleatorio es la clase seleccionada por la mayoría de los árboles.
Para las tareas de regresión, se obtiene la predicción media o promedio de los árboles individuales.
Para crear nuestro modelo de bosque aleatorio, podemos utilizar la función RandomForestRegressor.
In [38]:
from sklearn.ensemble import RandomForestRegressor
Cuando creé el bosque aleatorio con el número de estimadores por defecto (100), el cuaderno jupyter se bloqueó por falta de memoria, así que vamos a empezar con un número de estimadores de 10.
In [39]:
rf1 = RandomForestRegressor(random_state=0, n_estimators=10)
Ahora, ajustamos nuestro modelo a los datos de entrenamiento.
In [40]:
%%time rf1.fit(train_inputs, train_targets)
CPU times: user 13.1 s, sys: 112 ms, total: 13.2 s Wall time: 13.2 s
Out[40]:
RandomForestRegressor(n_estimators=10, random_state=0)
Ahora generamos predicciones en los conjuntos de entrenamiento y validación utilizando el bosque aleatorio entrenado, y calculamos la pérdida de error cuadrático medio (RMSE).
In [41]:
rf1_train_preds = rf1.predict(train_inputs)
In [42]:
rf1_train_rmse = mean_squared_error(train_targets, rf1_train_preds, squared=False)
In [43]:
rf1_val_preds = rf1.predict(val_inputs)
In [44]:
rf1_val_rmse = mean_squared_error(val_targets, rf1_val_preds, squared=False)
In [45]:
print('Train RMSE: {}, Validation RMSE: {}'.format(rf1_train_rmse, rf1_val_rmse))
Train RMSE: 1620.993367981347, Validation RMSE: 3997.6712441772224
Aquí, podemos ver que la pérdida RMSE para nuestros datos de entrenamiento es 1620.993367981347, y la pérdida RMSE para nuestros datos de validación es 3997.6712441772224
El modelo de bosque aleatorio muestra mejores resultados para el RMSE de validación, por lo que utilizaremos ese modelo.
Ajuste de Hiperparámetros
Definamos una función de ayuda test_params que pueda comprobar el valor dado de uno o más hiperparámetros.
Para este nuevo modelo de bosque aleatorio, utilizaré un número de estimadores de 16.
In [46]:
def test_params(**params): model = RandomForestRegressor(random_state=0, n_jobs=-1, n_estimators=16, **params).fit(train_inputs, train_targets) train_rmse = mean_squared_error(model.predict(train_inputs), train_targets, squared=False) val_rmse = mean_squared_error(model.predict(val_inputs), val_targets, squared=False) return train_rmse, val_rmse
Definamos también una función de ayuda para probar y trazar diferentes valores de un único parámetro.
In [47]:
def test_param_and_plot(param_name, param_values): train_errors, val_errors = [], [] for value in param_values: params = {param_name: value} train_rmse, val_rmse = test_params(**params) train_errors.append(train_rmse) val_errors.append(val_rmse) plt.figure(figsize=(10,6)) plt.title('Overfitting curve: ' + param_name) plt.plot(param_values, train_errors, 'b-o') plt.plot(param_values, val_errors, 'r-o') plt.xlabel(param_name) plt.ylabel('RMSE') plt.legend(['Training', 'Validation'])
In [48]:
test_params()
Out[48]:
(1538.6031204571725, 3946.105075819126)
Podemos ver mejores resultados con un mayor número de estimadores.
In [49]:
test_param_and_plot('min_samples_leaf', [1, 2, 3, 4, 5])

In [50]:
test_params(min_samples_leaf = 5)
Out[50]:
(3139.860605280673, 4191.528032848276)
Aquí, podemos ver cómo el RMSE aumenta con el parámetro min_samples_leaf, por lo que utilizaremos el valor por defecto (1).
In [51]:
test_param_and_plot('max_leaf_nodes', [20, 25, 30, 35, 40])

In [52]:
test_params(max_leaf_nodes = 20)
Out[52]:
(13172.46592071017, 13485.14172240374)
El RMSE disminuye con el parámetro max_leaf_nodes, por lo que utilizaremos el valor por defecto (ninguno).
In [53]:
test_param_and_plot('max_depth', [5, 10, 15, 20, 25])

In [54]:
test_params(max_depth = 10)
Out[54]:
(7597.643155388232, 8145.895516784769)
El RMSE disminuye con el parámetro max_depth, por lo que utilizaremos el valor por defecto (ninguno).
Entrenar al Mejor Modelo
Creamos un nuevo modelo Random Forest con hiperparámetros personalizados.
In [55]:
rf2 = RandomForestRegressor(n_estimators=16, random_state = 0, min_samples_leaf = 1)
Ahora entrenamos el modelo.
In [56]:
rf2.fit(train_inputs, train_targets)
Out[56]:
RandomForestRegressor(n_estimators=16, random_state=0)
Ahora generamos predicciones para el modelo final.
In [57]:
rf2_train_preds = rf2.predict(train_inputs)
In [58]:
rf2_train_rmse = mean_squared_error(train_targets, rf2_train_preds, squared=False)
In [59]:
rf2_val_preds = rf2.predict(val_inputs)
In [60]:
rf2_val_rmse = mean_squared_error(val_targets, rf2_val_preds, squared=False)
In [61]:
print('Train RMSE: {}, Validation RMSE: {}'.format(rf2_train_rmse, rf2_val_rmse))
Train RMSE: 1538.6031204571725, Validation RMSE: 3946.105075819126
Aquí, podemos ver una disminución de la pérdida RMSE.
Importancia de las Características del Bosque Aleatorio
Veamos las ponderaciones asignadas a las distintas columnas, para averiguar qué columnas del conjunto de datos son las más importantes para este modelo.
In [62]:
rf2_importance_df = pd.DataFrame({ 'feature': train_inputs.columns, 'importance': rf2.feature_importances_ }).sort_values('importance', ascending=False)
In [63]:
rf2_importance_df
Out[63]:
# | feature | importance |
---|---|---|
1 | Dept | 0.627801 |
2 | Size | 0.205967 |
0 | Store | 0.073425 |
10 | Week | 0.050421 |
12 | Type_B | 0.011083 |
5 | MarkDown3 | 0.007831 |
9 | Month | 0.007729 |
8 | Year | 0.003370 |
11 | Type_A | 0.002985 |
6 | MarkDown4 | 0.002700 |
7 | MarkDown5 | 0.002547 |
3 | MarkDown1 | 0.001938 |
4 | MarkDown2 | 0.001794 |
13 | Type_C | 0.000409 |
In [64]:
sns.barplot(data=rf2_importance_df, x='importance', y='feature')
Out[64]:
<AxesSubplot:xlabel='importance', ylabel='feature'>

Las variables Departamento, Tamaño, Tienda y Semana son las más importantes para este modelo.
Predicciones sobre el conjunto de pruebas
Hagamos predicciones sobre el conjunto de prueba proporcionado con los datos.
En primer lugar, tenemos que volver a aplicar todos los pasos de preprocesamiento.
In [65]:
test_dataset[['MarkDown1','MarkDown2','MarkDown3','MarkDown4', 'MarkDown5']] = test_dataset[['MarkDown1','MarkDown2','MarkDown3','MarkDown4','MarkDown5']].fillna(0) test_dataset['Year'] = pd.to_datetime(test_dataset['Date']).dt.year test_dataset['Month'] = pd.to_datetime(test_dataset['Date']).dt.month test_dataset['Week'] = pd.to_datetime(test_dataset['Date']).dt.isocalendar().week test_dataset = test_dataset.drop(columns=["Date", "CPI", "Fuel_Price", 'Unemployment', 'Temperature'])
In [66]:
test_dataset
Out[66]:
# | Store | Dept | IsHoliday | Type | Size | MarkDown1 | MarkDown2 | MarkDown3 | MarkDown4 | MarkDown5 | Year | Month | Week |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | False | A | 151315 | 6766.44 | 5147.70 | 50.82 | 3639.90 | 2737.42 | 2012 | 11 | 44 |
1 | 1 | 1 | False | A | 151315 | 11421.32 | 3370.89 | 40.28 | 4646.79 | 6154.16 | 2012 | 11 | 45 |
2 | 1 | 1 | False | A | 151315 | 9696.28 | 292.10 | 103.78 | 1133.15 | 6612.69 | 2012 | 11 | 46 |
3 | 1 | 1 | True | A | 151315 | 883.59 | 4.17 | 74910.32 | 209.91 | 303.32 | 2012 | 11 | 47 |
4 | 1 | 1 | False | A | 151315 | 2460.03 | 0.00 | 3838.35 | 150.57 | 6966.34 | 2012 | 11 | 48 |
… | … | … | … | … | … | … | … | … | … | … | … | … | … |
115059 | 45 | 98 | False | B | 118221 | 4842.29 | 975.03 | 3.00 | 2449.97 | 3169.69 | 2013 | 6 | 26 |
115060 | 45 | 98 | False | B | 118221 | 9090.48 | 2268.58 | 582.74 | 5797.47 | 1514.93 | 2013 | 7 | 27 |
115061 | 45 | 98 | False | B | 118221 | 3789.94 | 1827.31 | 85.72 | 744.84 | 2150.36 | 2013 | 7 | 28 |
115062 | 45 | 98 | False | B | 118221 | 2961.49 | 1047.07 | 204.19 | 363.00 | 1059.46 | 2013 | 7 | 29 |
115063 | 45 | 98 | False | B | 118221 | 212.02 | 851.73 | 2.06 | 10.88 | 1864.57 | 2013 | 7 | 30 |
115064 rows × 13 columns
In [67]:
test_dataset[numeric_cols] = imputer.transform(test_dataset[numeric_cols]) test_dataset[numeric_cols] = scaler.transform(test_dataset[numeric_cols]) test_dataset[encoded_cols] = encoder.transform(test_dataset[categorical_cols])
In [68]:
test_inputs = test_dataset[numeric_cols + encoded_cols]
Ahora podemos hacer predicciones utilizando nuestro modelo final.
In [69]:
test_preds = rf2.predict(test_inputs)
Sustituyamos los valores de la columna Weekly_Sales por nuestras predicciones.
In [70]:
submission_df['Weekly_Sales'] = test_preds
Guardémoslo como archivo CSV y descarguémoslo.
In [71]:
submission_df.to_csv('submission.csv', index=False)
In [72]:
from IPython.display import FileLink FileLink('submission.csv') # Doesn't work on Colab, use the file browser instead to download the file.
Out[72]:
Predicciones Basadas en Datos Individuales
In [73]:
def predict_input(model, single_input): input_df = pd.DataFrame([single_input]) input_df[numeric_cols] = imputer.transform(input_df[numeric_cols]) input_df[numeric_cols] = scaler.transform(input_df[numeric_cols]) input_df[encoded_cols] = encoder.transform(input_df[categorical_cols].values) return model.predict(input_df[numeric_cols + encoded_cols])[0]
In [74]:
sample_input = {'Store':9, 'Dept':72, 'IsHoliday':True, 'Type':'B', 'Size':125833, 'MarkDown1':2.5, 'MarkDown2':0.02, 'MarkDown3':55952.99, 'MarkDown4':14.64, 'MarkDown5':310.72, 'Year':2012, 'Month':11, 'Week':47}
In [75]:
predicted_price = predict_input(rf2, sample_input)
In [76]:
print('The predicted weekly sales is ${}'.format(predicted_price))
The predicted weekly sales is $475908.2475000001
Guardar el modelo
In [77]:
import joblib
In [78]:
walmart_sales_rf = { 'model': rf2, 'imputer': imputer, 'scaler': scaler, 'encoder': encoder, 'input_cols': input_cols, 'target_col': target_col, 'numeric_cols': numeric_cols, 'categorical_cols': categorical_cols, 'encoded_cols': encoded_cols }
In [79]:
joblib.dump(walmart_sales_rf, 'walmart_sales_rf.joblib')
Out[79]:
['walmart_sales_rf.joblib']
Referencias
Consulta los siguientes recursos para obtener más información:
- Walmart – Store Sales Forecasting: https://www.kaggle.com/c/walmart-recruiting-store-sales-forecasting
- Scikit-learn documentation: https://scikit-learn.org/stable/index.html
- Scikit-learn DecisionTreeRegressor: https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeRegressor.html
- Scikit-learn RandomForestRegressor: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html
- Pandas: https://pandas.pydata.org/docs/user_guide/index.html
- TP2 – Walmart Sales Forecast: https://www.kaggle.com/andredornas/tp2-walmart-sales-forecast
- Walmart – Store Sales Forecasting: https://www.kaggle.com/avelinocaio/walmart-store-sales-forecasting