Inteligencia Artificial

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]:

StoreDeptDateWeekly_SalesIsHolidayTypeSizeTemperatureFuel_PriceMarkDown1MarkDown2MarkDown3MarkDown4MarkDown5CPIUnemployment
0112010-02-0524924.50FalseA15131542.312.572NaNNaNNaNNaNNaN211.0963588.106
1112010-02-1246039.49TrueA15131538.512.548NaNNaNNaNNaNNaN211.2421708.106
2112010-02-1941595.55FalseA15131539.932.514NaNNaNNaNNaNNaN211.2891438.106
3112010-02-2619403.54FalseA15131546.632.561NaNNaNNaNNaNNaN211.3196438.106
4112010-03-0521827.90FalseA15131546.502.625NaNNaNNaNNaNNaN211.3501438.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]:

MetricStoreDeptDateWeekly_SalesIsHolidayTypeSizeTemperatureFuel_PriceMarkDown1MarkDown2MarkDown3MarkDown4MarkDown5CPIUnemployment
count421570.000000421570.000000421570421570.000000421570421570421570.000000421570.000000421570.000000150681.000000111248.000000137091.000000134967.000000151432.000000421570.000000421570.000000
uniqueNaNNaN143NaN23NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
topNaNNaN2011-12-23NaNFalseANaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
freqNaNNaN3027NaN391909215478NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
mean22.20054644.260317NaN15981.258123NaNNaN136727.91573960.0900593.3610277246.4201963334.6286211439.4213843383.1682564628.975079171.2019477.960289
std12.78529730.492054NaN22711.183519NaNNaN60980.58332818.4479310.4585158291.2213459475.3573259623.0782906292.3840315962.88745539.1592761.863296
min1.0000001.000000NaN-4988.940000NaNNaN34875.000000-2.0600002.4720000.270000-265.760000-29.1000000.220000135.160000126.0640003.879000
25%11.00000018.000000NaN2079.650000NaNNaN93638.00000046.6800002.9330002240.27000041.6000005.080000504.2200001878.440000132.0226676.891000
50%22.00000037.000000NaN7612.030000NaNNaN140167.00000062.0900003.4520005347.450000192.00000024.6000001481.3100003359.450000182.3187807.866000
75%33.00000074.000000NaN20205.852500NaNNaN202505.00000074.2800003.7380009210.9000001926.940000103.9900003595.0400005563.800000212.4169938.572000
max45.00000099.000000NaN693099.360000NaNNaN219622.000000100.1400004.46800088646.760000104519.540000141630.61000067474.850000108519.280000227.23280714.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]:

#featureimportance
1Dept0.636039
2Size0.198802
0Store0.072103
10Week0.055737
12Type_B0.013551
5MarkDown30.004337
9Month0.003614
8Year0.003393
6MarkDown40.003347
11Type_A0.003196
7MarkDown50.002141
3MarkDown10.001904
4MarkDown20.001453
13Type_C0.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]:

#featureimportance
1Dept0.627801
2Size0.205967
0Store0.073425
10Week0.050421
12Type_B0.011083
5MarkDown30.007831
9Month0.007729
8Year0.003370
11Type_A0.002985
6MarkDown40.002700
7MarkDown50.002547
3MarkDown10.001938
4MarkDown20.001794
13Type_C0.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]:

#StoreDeptIsHolidayTypeSizeMarkDown1MarkDown2MarkDown3MarkDown4MarkDown5YearMonthWeek
011FalseA1513156766.445147.7050.823639.902737.4220121144
111FalseA15131511421.323370.8940.284646.796154.1620121145
211FalseA1513159696.28292.10103.781133.156612.6920121146
311TrueA151315883.594.1774910.32209.91303.3220121147
411FalseA1513152460.030.003838.35150.576966.3420121148
1150594598FalseB1182214842.29975.033.002449.973169.692013626
1150604598FalseB1182219090.482268.58582.745797.471514.932013727
1150614598FalseB1182213789.941827.3185.72744.842150.362013728
1150624598FalseB1182212961.491047.07204.19363.001059.462013729
1150634598FalseB118221212.02851.732.0610.881864.572013730

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]:

submission.csv

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:

Sergio Alves

Ingeniero de Sistemas. MSc. en Data Science. Cuento con una amplia trayectoria profesional en las áreas de Desarrollo Web FullStack, DBA, DevOps, Inteligencia Artificial y Ciencia de Datos. Soy un entusiasta de la música, la tecnología y el aprendizaje contínuo.

Artículos Relacionados

Back to top button