Análisis de las tendencias de compra del Black Friday a través del aprendizaje automático

Wikipedia define Black Friday como un nombre informal para el viernes siguiente al Día de Acción de Gracias en los Estados Unidos, que se celebra el cuarto jueves de...

Introducción

Wikipedia define Black Friday como un nombre informal para el viernes siguiente al Día de Acción de Gracias en los Estados Unidos, que se celebra el cuarto jueves de noviembre. [El Black Friday es] considerado como el comienzo de la temporada de compras navideñas en Estados Unidos [...].

En este artículo, intentaremos explorar diferentes tendencias del conjunto de datos de compras del Black Friday. Extraeremos información útil que dará respuesta a preguntas como: ¿qué género compra más el Black Friday? ¿Las ocupaciones de las personas tienen algún impacto en las ventas? ¿Qué grupo de edad es el que más gasta?

Al final, crearemos un algoritmo de aprendizaje automático simple que prediga la cantidad de dinero que una persona probablemente gastará en el Black Friday según características como el género, la edad y la ocupación.

El conjunto de datos que usaremos en este artículo incluye 550 000 observaciones sobre el Black Friday, que se realizan en una tienda minorista. El archivo se puede descargar en el siguiente enlace de Kaggle: Estudio de caso de viernes negro.

Análisis de datos {#análisis de datos}

El primer paso es importar las librerías que necesitaremos en este apartado:

1
2
3
4
5
import pandas as pd
import numpy as np
import matplotlib as pyplot
%matplotlib inline
import seaborn as sns

A continuación, necesitamos importar nuestros datos.

1
data = pd.read_csv('E:/Datasets/BlackFriday.csv')

¡Veamos información básica sobre nuestros datos!

1
data.info()

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 537577 entries, 0 to 537576
Data columns (total 12 columns):
User_ID                       537577 non-null int64
Product_ID                    537577 non-null object
Gender                        537577 non-null object
Age                           537577 non-null object
Occupation                    537577 non-null int64
City_Category                 537577 non-null object
Stay_In_Current_City_Years    537577 non-null object
Marital_Status                537577 non-null int64
Product_Category_1            537577 non-null int64
Product_Category_2            370591 non-null float64
Product_Category_3            164278 non-null float64
Purchase                      537577 non-null int64
dtypes: float64(2), int64(5), object(5)
memory usage: 49.2+ MB

Mirando los datos, podemos concluir que nuestro conjunto posee 12 parámetros diferentes: 7 numéricos (entero y flotante) y 5 variables de objeto. Además, el conjunto de datos contiene dos variables de tipo corto: Product_Category_2 y Product_Category_3. Más adelante veremos cómo manejar este problema.

Bien, ahora tenemos una imagen general de los datos, imprimamos información sobre los primeros cinco clientes (primeras cinco filas de nuestro DataFrame):

1
data.head()

{.img-responsive}

La primera pregunta que quiero hacer desde el comienzo de este estudio, ¿es cierto que las clientas son muy dominantes en comparación con los clientes masculinos? Usaremos la biblioteca seaborn y la función countplot para trazar el número de clientes masculinos y femeninos.

1
sns.countplot(data['Gender'])

{.img-responsive}

¡Guau! ¡El gráfico muestra que hay casi 3 veces más clientes masculinos que femeninos! ¿Porqué es eso? Tal vez los visitantes masculinos sean más propensos a salir y comprar algo para sus damas cuando hay más ofertas.

Exploremos un poco más la categoría Género. Queremos ver ahora la distribución de la variable de género, pero teniendo en cuenta la categoría ‘Edad’. Una vez más, se utilizará la función countplot, pero ahora con el parámetro hue definido.

1
sns.countplot(data['Age'], hue=data['Gender'])

{.img-responsive}

De la figura anterior, podemos concluir fácilmente que el mayor número de clientes pertenecen al grupo de edad entre 26 y 35 años, para ambos sexos. La población joven y mayor está mucho menos representada en el Black Friday. Según estos resultados, la tienda minorista debería vender la mayoría de los productos dirigidos a personas entre los veinte y treinta años. Para aumentar las ganancias, se puede aumentar la cantidad de productos dirigidos a personas alrededor de los treinta, mientras que se puede reducir la cantidad de productos dirigidos a la población mayor o más joven.

A continuación, usaremos la función describe para analizar nuestras categorías, en términos de valores medios, valores mínimos y máximos, desviaciones estándar, etc.

1
data.describe()

{.img-responsive}

Además, a continuación analizamos la columna User_ID utilizando el método nunique. De esto podemos concluir que en esta tienda minorista específica, durante el Black Friday, 5.891 clientes diferentes han comprado algo de la tienda. Además, de la categoría Product_ID podemos extraer información de que se venden 3.623 productos diferentes.

1
data['User_ID'].nunique()

Producción:

1
5891
1
data['User_ID'].nunique()

Producción:

1
3623

Ahora exploremos la categoría Ocupación. El número de Ocupación es el número de identificación del tipo de ocupación de cada cliente. Podemos ver que existen alrededor de 20 ocupaciones diferentes. Pero vamos a realizar un análisis exacto. Primero, necesitamos crear la función que extraerá todos los elementos únicos de una columna (para extraer todas las ocupaciones diferentes).

Usaremos la función única para eso, de la biblioteca de Python numpy.

1
2
3
def unique(column):
    x = np.array(column)
    print(np.unique(x))
1
2
print("The unique ID numbers of customers occupations:")
unique(data['Occupation'])

Producción:

1
2
The unique ID numbers of costumers occupations:
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20]

Como podemos ver, se registran 21 ID de ocupación diferentes durante el día de compras.

El número de ocupación podría representar diferentes profesiones de los clientes: por ejemplo, el número 1 podría ser un ingeniero, el número 2, un médico, el número 3, un artista, etc.

También sería interesante ver cuánto dinero gastó cada grupo de clientes (agrupados por ID de ocupación). Para hacer eso, podemos usar un bucle for y sumar el dinero gastado para cada ID de ocupación individual:

1
2
3
4
5
6
occupations_id = list(range(0, 21))
spent_money = []
for oid in occupations_id:
    spent_money.append(data[data['Occupation'] == oid]['Purchase'].sum())

spent_money

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
[625814811,
 414552829,
 233275393,
 160428450,
 657530393,
 112525355,
 185065697,
 549282744,
 14594599,
 53619309,
 114273954,
 105437359,
 300672105,
 71135744,
 255594745,
 116540026,
 234442330,
 387240355,
 60249706,
 73115489,
 292276985]

Hemos creado la lista spent_money, que incluye cantidades sumadas de dólares para los ID de Ocupaciones, del 0 al 20. Puede parecer extraño en los resultados que se gasten cientos de millones de dólares. Pero tenga en cuenta que nuestro conjunto de datos incluye 500 000 observaciones, por lo que esto es muy probable. O tal vez la tienda minorista es en realidad un gran centro comercial. Otra explicación de las enormes sumas de dinero gastadas por cada ocupación es que estos datos pueden representar las transacciones de varias noches de Black Friday, y no solo una.

Ahora, tenemos información sobre cuánto dinero se gasta por categoría de ocupación. Ahora representemos gráficamente esta información.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import matplotlib.pyplot as plt; plt.rcdefaults()
import matplotlib.pyplot as plt

objects = ('0', '1', '2', '3', '4', '5','6','7','8','9','10', '11','12', '13', '14', '15', '16', '17', '18', '19', '20')
y_pos = np.arange(len(objects))

plt.bar(y_pos, spent_money, align='center', alpha=0.5)
plt.xticks(y_pos, objects)
plt.ylabel('Money spent')
plt.title('Occupation ID')

plt.show()

{.img-responsive}

Se puede observar fácilmente que las personas que tienen las ocupaciones 0 y 4 gastaron la mayor cantidad de dinero durante las ventas del Black Friday. Por otro lado, las personas pertenecientes a las ocupaciones con DNI 18, 19, y en especial la ocupación 8, han gastado menor cantidad de dinero. Puede implicar que estos grupos son los más pobres, o por el contrario, las personas más ricas a las que no les gusta comprar en ese tipo de tiendas minoristas. Tenemos una deficiencia de información para responder a esa pregunta, y por eso, nos detendríamos aquí con el análisis de la categoría Ocupación.

La variable City_Category es la siguiente. Esta categoría nos da información sobre las ciudades de donde son nuestros clientes. Primero, veamos cuántas ciudades diferentes tenemos.

1
data['City_Category'].nunique()

Producción:

1
3

Ahora, será interesante ver en porcentajes cuál es la proporción de clientes de cada ciudad. Esta información se presentará en forma de gráfico circular de colores. Podemos hacerlo en 5 líneas de código. ¡Todopoderoso Python, gracias! :)

1
2
3
4
5
explode = (0.1, 0, 0)
fig1, ax1 = plt.subplots(figsize=(11,6))
ax1.pie(data['City_Category'].value_counts(), explode=explode, labels=data['City_Category'].unique(), autopct='%1.1f%%')
plt.legend()
plt.show()

{.img-responsive}

Es evidente a partir del gráfico circular que las tres ciudades están representadas casi por igual en la tienda minorista durante el Black Friday. Tal vez la tienda esté en algún lugar entre estas tres ciudades, sea de fácil acceso y tenga buenas conexiones por carretera desde estas ciudades.

Preprocesamiento de datos para algoritmos de aprendizaje automático

Hemos cubierto hasta ahora algunas técnicas básicas para analizar datos sin procesar. Antes de que podamos aplicar algoritmos de aprendizaje automático a nuestro conjunto de datos, debemos convertirlo en una forma determinada en la que puedan operar los algoritmos de aprendizaje automático. La tarea de los algoritmos de aprendizaje será predecir el valor de la variable ‘Compra’, dada la información del cliente como entrada.

Lo primero que debemos hacer es lidiar con los datos faltantes en las columnas Product_Category_2 y Product_Category_3. Tenemos solo el 30 % de los datos dentro de Product_Category_3 y el 69 % de los datos dentro de Product_Category_2. El 30 % de los datos reales es una proporción pequeña, podríamos completar los valores faltantes dentro de esta categoría con la media de los valores existentes, pero eso significa que el 70 % de los datos serán artificiales, lo que podría arruinar nuestro futuro modelo de aprendizaje automático. La mejor alternativa para este problema es eliminar esta columna del análisis posterior. Usaremos la función drop para hacer eso:

1
data = data.drop(['Product_Category_3'], axis=1)

La columna Product_Category_2 posee alrededor del 30% de datos faltantes. Aquí tiene sentido completar los valores faltantes y usar esta columna para ajustar un modelo de aprendizaje automático. Resolveremos este problema insertando un valor medio de los valores existentes en esta columna en los campos que faltan:

1
data['Product_Category_2'].fillna((data['Product_Category_2'].mean()), inplace=True)

Ahora revisemos nuestro marco de datos nuevamente:

1
data.info()

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 537577 entries, 0 to 537576
Data columns (total 11 columns):
User_ID                       537577 non-null int64
Product_ID                    537577 non-null object
Gender                        537577 non-null object
Age                           537577 non-null object
Occupation                    537577 non-null int64
City_Category                 537577 non-null object
Stay_In_Current_City_Years    537577 non-null object
Marital_Status                537577 non-null int64
Product_Category_1            537577 non-null int64
Product_Category_2            537577 non-null float64
Purchase                      537577 non-null int64
dtypes: float64(1), int64(5), object(5)
memory usage: 45.1+ MB

El problema de los valores perdidos está resuelto. A continuación, eliminaremos las columnas que no ayuden en la predicción.

User_ID es el número asignado automáticamente a cada cliente, y no es útil para propósitos de predicción.

La columna Product_ID contiene información sobre el producto comprado. No es una característica del cliente. Por lo tanto, lo eliminaremos también.

1
2
data = data.drop(['User_ID','Product_ID'], axis=1)
data.info()

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 537577 entries, 0 to 537576
Data columns (total 9 columns):
Gender                        537577 non-null object
Age                           537577 non-null object
Occupation                    537577 non-null int64
City_Category                 537577 non-null object
Stay_In_Current_City_Years    537577 non-null object
Marital_Status                537577 non-null int64
Product_Category_1            537577 non-null int64
Product_Category_2            537577 non-null float64
Purchase                      537577 non-null int64
dtypes: float64(1), int64(4), object(4)
memory usage: 36.9+ MB

Nuestra selección final se basa en 9 columnas: una variable que queremos predecir (la columna ‘Compra’) y 8 variables que usaremos para entrenar nuestro modelo de aprendizaje automático.

Como podemos ver en la tabla de información, estamos tratando con 4 columnas categóricas. Sin embargo, los modelos básicos de aprendizaje automático son capaces de procesar valores numéricos. Por lo tanto, necesitamos convertir las columnas categóricas en numéricas.

Podemos usar una función Python get_dummies que convierte valores categóricos en vectores codificados one-hot. ¿Como funciona? Tenemos 3 ciudades en nuestro conjunto de datos: A, B y C. Digamos que un cliente es de la ciudad B. La función get_dummies devolverá un vector codificado one-hot para ese registro que se ve así: [0 1 0]. Para un cliente de la ciudad A: [1 0 0] y de C: [0 0 1]. En resumen, para cada ciudad se crea una nueva columna, que se llena con ceros excepto las filas donde el cliente pertenece a esa ciudad en particular. Tales filas contendrán 1.

El siguiente script crea vectores codificados one-hot para las columnas Gender, Age, City y Stay_In_Current_City_Years.

1
2
3
4
5
6
7
8
df_Gender = pd.get_dummies(data['Gender'])
df_Age = pd.get_dummies(data['Age'])
df_City_Category = pd.get_dummies(data['City_Category'])
df_Stay_In_Current_City_Years = pd.get_dummies(data['Stay_In_Current_City_Years'])

data_final = pd.concat([data, df_Gender, df_Age, df_City_Category, df_Stay_In_Current_City_Years], axis=1)

data_final.head()

En la siguiente captura de pantalla, se presentan las columnas ficticias recién creadas. Como puede ver, todas las variables categóricas se transforman en numéricas. Entonces, si un cliente tiene entre 0 y 17 años (por ejemplo), solo el valor de esa columna será igual a 1, otras columnas de otros grupos de edad tendrán un valor de 0. De manera similar, si es un cliente masculino, el la columna llamada 'M' será igual a 1 y la columna 'F' será 0.

{.img-responsive}

Ahora tenemos los datos que se pueden usar fácilmente para entrenar un modelo de aprendizaje automático.

Predicción de la cantidad gastada

En este artículo, utilizaremos uno de los modelos de aprendizaje automático más simples, es decir, el modelo de regresión lineal, para predecir la cantidad gastada por el cliente en el Black Friday.

La regresión lineal representa un método muy simple para el aprendizaje supervisado y es una herramienta efectiva para predecir respuestas cuantitativas. Puedes encontrar información básica al respecto aquí mismo: Regresión lineal en Python

Este modelo, como la mayoría de los algoritmos de aprendizaje automático supervisado, hace una predicción basada en las características de entrada. Los valores de salida pronosticados se utilizan para realizar comparaciones con las salidas deseadas y se calcula un error. La señal de error se propaga de nuevo a través del modelo y los parámetros del modelo se actualizan para minimizar el error. Finalmente, se considera que el modelo está completamente entrenado si el error es lo suficientemente pequeño. Esta es una explicación muy básica y vamos a analizar todos estos procesos en detalle en próximos artículos.

Suficiente con la teoría, ¡construyamos un sistema ML real! Primero, necesitamos crear vectores de entrada y salida para nuestro modelo:

1
2
X = data_final[['Occupation', 'Marital_Status', 'Product_Category_2', 'F', 'M', '0-17', '18-25', '26-35', '36-45', '46-50', '51-55', '55+', 'A', 'B', 'C', '0', '1', '2', '3', '4+']]
y = data_final['Purchase']

Ahora, importaremos la función train_test_split para dividir todos nuestros datos en dos conjuntos: conjunto de entrenamiento y de prueba. El conjunto de entrenamiento se utilizará para adaptarse a nuestro modelo. Los datos de entrenamiento siempre se usan para aprender, ajustar los parámetros de un modelo y minimizar un error en la salida. El resto de los datos (el conjunto de prueba) se utilizará para evaluar el rendimiento.

El siguiente script divide nuestro conjunto de datos en un 60 % de conjunto de entrenamiento y un 40 % de conjunto de prueba:

1
2
3
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)

Ahora es el momento de importar nuestro modelo de regresión lineal y entrenarlo en nuestro conjunto de entrenamiento:

1
2
3
4
5
from sklearn.linear_model import LinearRegression

lm = LinearRegression()
lm.fit(X_train, y_train)
print(lm.fit(X_train, y_train))

Producción:

1
2
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
         normalize=False)

¡Felicidades gente! Nuestro modelo está entrenado. Ahora podemos imprimir el valor del parámetro de intersección y los valores de todos los coeficientes de nuestro modelo, después del procedimiento de aprendizaje:

1
2
3
print('Intercept parameter:', lm.intercept_)
coeff_df = pd.DataFrame(lm.coef_, X.columns, columns=['Coefficient'])
print(coeff_df)

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Intercept parameter: 11224.23064289564
                    Coefficient
Occupation             8.110850
Marital_Status       -79.970182
Product_Category_2  -215.239359
F                   -309.477333
M                    309.477333
0-17                -439.382101
18-25               -126.919625
26-35                 67.617548
36-45                104.096403
46-50                 14.953497
51-55                342.248438
55+                   37.385839
A                   -376.683205
B                   -130.046924
C                    506.730129
0                    -46.230577
1                      4.006429
2                     32.627696
3                     11.786731
4+                    -2.190279

Como puede ver, cada categoría de nuestro conjunto de datos ahora se define con un coeficiente de regresión. El proceso de entrenamiento buscaba los mejores valores de estos coeficientes durante la fase de aprendizaje. Los valores presentados en el resultado anterior son los valores más óptimos para los coeficientes de nuestro modelo de aprendizaje automático.

Es hora de usar los datos de prueba como entradas del modelo para ver qué tan bien funciona nuestro modelo.

1
2
predictions = lm.predict(X_test)
print("Predicted purchases (in dollars) for new costumers:", predictions)

Producción:

1
2
Predicted purchases (in dollars) for new costumers: [10115.30806914  8422.51807746  9976.05377826 ...  9089.65372668
  9435.81550922  8806.79394589]

Estimación del rendimiento del modelo ML

Al final, siempre es bueno estimar nuestros resultados encontrando el error absoluto medio (MAE) y el error cuadrático medio ([MSE](https:/ /en.wikipedia.org/wiki/Mean_squared_error)) de nuestras predicciones. Puede encontrar cómo calcular estos errores aquí: [Cómo seleccionar la métrica de evaluación adecuada para los modelos de aprendizaje automático](https://towardsdatascience.com/how-to-select-the-right-evaluacion-metric-for- machine-learning-models-part-1-regresion-metrics-3606e25beae0).

Para encontrar estos valores, podemos usar métodos de la clase metrics de la biblioteca sklearn.

1
2
3
4
from sklearn import metrics

print('MAE:', metrics.mean_absolute_error(y_test, predictions))
print('MSE:', metrics.mean_squared_error(y_test, predictions))

Producción:

1
2
MAE: 3874.1898429849575
MSE: 23810661.195583127

Conclusión

El aprendizaje automático se puede utilizar para una variedad de tareas. En este artículo, utilizamos un algoritmo de aprendizaje automático para predecir la cantidad que probablemente gastará un cliente en el Black Friday. También realizamos análisis de datos exploratorios para encontrar tendencias interesantes del conjunto de datos. En aras de la práctica, le sugiero que intente predecir el Producto que es más probable que compre el cliente, según su sexo, edad y ocupación.