Construyendo Un Modelo De Regresión Lineal Con Python Que Prediga El Gasto De Un Cliente De Un Comercio

Daniel Morales
Jan 26, 2021

Construyendo Un Modelo De Regresión Lineal Con Python Que Prediga El Gasto De Un Cliente De Un Comercio

Jan 26, 2021 12 minutes read

Crearemos un proyecto completo tratando de predecir los gastos de los clientes mediante una regresión lineal con Python. En este ejercicio, tenemos algunos datos históricos de transacciones de 2010 y 2011. Para cada transacción, tenemos un identificador de cliente (CustomerID), el número de unidades compradas (Quantity), la fecha de la compra (InvoiceDate) y el costo unitario (UnitPrice), así como alguna otra información sobre el artículo comprado.

Usted puede encontrar el dataset aqui

Queremos preparar estos datos para una regresión de los datos de las transacciones de los clientes de 2010 contra los gastos de 2011. Por lo tanto, crearemos características a partir de los datos del año 2010 y calcularemos el objetivo (la cantidad de dinero gastada) para 2011. 

Cuando creemos este modelo, debería generalizarse a los años futuros para los que aún no tenemos el resultado. Por lo tanto, podríamos utilizar los datos de 2020 para predecir el comportamiento de gasto de 2021 por adelantado, a menos que el mercado o el negocio haya cambiado significativamente desde el período de tiempo al que se refieren los datos utilizados para ajustar el modelo: 

import pandas as pd

df = pd.read_csv('datasets/retail_transactions.csv')
df.head()

resultado


Convierta la columna InvoiceDate en formato de fecha utilizando el siguiente código:

df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
df.head()

resultado


Calcule los ingresos de cada fila, multiplicando la cantidad por el precio unitario:

df['revenue'] = df['UnitPrice']*df['Quantity']
df.head()
resultado


Observará que cada factura está repartida en varias filas, una para cada tipo de producto adquirido. Éstas pueden combinarse de tal manera que los datos de cada transacción se encuentran en una sola fila. Para ello, podemos realizar una operación agrupada en InvoiceNo. Sin embargo, antes de eso, necesitamos especificar cómo combinar esas filas que están agrupadas. Utilice el siguiente código:

operations = {'revenue':'sum',
              'InvoiceDate':'first',
              'CustomerID':'first' 
             }

df = df.groupby('InvoiceNo').agg(operations)
df.head()

resultado


En el fragmento de código precedente, especificamos primero las funciones de agregación que utilizaremos para cada columna, y luego realizamos la agrupación y aplicamos esas funciones. InvoiceDate y CustomerID serán los mismos para todas las filas de la misma factura, por lo que sólo podemos tomar la primera entrada para ellas. Para los ingresos, sumamos los ingresos de todos los artículos de la misma factura para obtener el total de ingresos de esa factura.

Dado que usaremos el año para decidir qué filas se están usando para la predicción y cuáles estamos prediciendo, cree una columna separada llamada año para el año, de la siguiente manera:

df['year'] = df['InvoiceDate'].apply(lambda x: x.year)
df.head()
resultado


Las fechas de las transacciones también pueden ser una fuente importante de características. Los días desde la última transacción de un cliente hasta el final del año, o lo temprano que un cliente tuvo su primera transacción, puede decirnos un poco sobre el historial de compras del cliente, lo cual podría ser importante. Por lo tanto, para cada transacción, calcularemos cuántos días de diferencia hay entre el último día de 2010 y la fecha de la factura:

df['days_since'] = (pd.datetime(year=2010, month=12, day=31) - 
                    df['InvoiceDate']).apply(lambda x: x.days)
df.head()
resultado


Actualmente, tenemos los datos agrupados por factura, pero realmente queremos que se agrupen por cliente. 

Empezaremos calculando todos nuestros predictores. Definiremos de nuevo un conjunto de funciones de agregación para cada una de nuestras variables y las aplicaremos usando groupby. Calcularemos la suma de los ingresos. 

Para `days_since`, calcularemos el número máximo y mínimo de días (dándonos características que nos digan cuánto tiempo ha estado activo este cliente en 2010, y qué tan recientemente), así como el número de valores únicos (dándonos cuántos días separados este cliente hizo una compra). Dado que son para nuestros pronosticadores, sólo aplicaremos estas funciones a nuestros datos a partir de 2010, y los almacenaremos en una variable, X, y usaremos la función `head` para ver los resultados:

operations = {'revenue':'sum',
              'days_since':['max','min','nunique'],
             }

X = df[df['year'] == 2010].groupby('CustomerID').agg(operations)
X.head()
resultado


Como puede ver en la figura anterior, como realizamos múltiples tipos de agregaciones en la columna `days_since`, terminamos con etiquetas de columna de varios niveles. Para simplificar esto, podemos reajustar los nombres de las columnas para facilitar su referencia posterior. Utilice el siguiente código e imprima los resultados:

X.columns = [' '.join(col).strip() for col in X.columns.values]
X.head()
resultado


Vamos a calcular una característica más: el gasto medio por pedido. Podemos calcularlo dividiendo la suma de los ingresos por `days_since_nunique` (esto es realmente el gasto medio por día, no por pedido, pero estamos asumiendo que si dos pedidos se hicieron en el mismo día, podemos tratarlos como parte del mismo pedido para nuestros propósitos):

X['avg_order_cost'] = X['revenue sum']/X['days_since nunique']
X.head()
resultado


Ahora que tenemos nuestros pronosticadores, necesitamos el resultado que predeciremos, que es sólo la suma de los ingresos para 2011. Podemos calcularlo con un simple groupby y almacenar los valores en la variable y, de la siguiente manera:

y = df[df['year'] == 2011].groupby('CustomerID')['revenue'].sum()
y
resultado


Ahora podemos poner nuestros predictores y resultados en un solo DataFrame, `wrangled_df`, y renombrar las columnas para tener nombres más intuitivos. Finalmente, mira el DataFrame resultante, usando la función `head`:

wrangled_df = pd.concat([X,y], axis=1)
wrangled_df.columns = ['2010 revenue',
                       'days_since_first_purchase',
                       'days_since_last_purchase',
                       'number_of_purchases',
                       'avg_order_cost',
                       '2011 revenue']
wrangled_df.head()
resultado


Observe que muchos de los valores de nuestro DataFrame son `NaN`. Esto es causado por clientes que estuvieron activos sólo en 2010 o sólo en 2011, por lo que no hay datos para el otro año. Más adelante trabajaremos en la predicción de cuáles de nuestros clientes se darán de baja, pero por ahora, sólo dejaremos de lado a todos los clientes que no estén activos en ambos años. Note que esto significa que nuestro modelo predecirá el gasto de los clientes en el próximo año asumiendo que siguen siendo clientes activos. Para eliminar los clientes sin valores, eliminaremos las filas donde cualquiera de las columnas de ingresos son nulas, de la siguiente manera:

wrangled_df = wrangled_df[~wrangled_df['2010 revenue'].isnull()]
wrangled_df = wrangled_df[~wrangled_df['2011 revenue'].isnull()]
wrangled_df.head()

resultado


Como paso final de limpieza de datos, a menudo es una buena idea deshacerse de los valores atípicos. Una definición estándar es que un valor atípico es cualquier punto de datos que esté más de tres desviaciones estándar por encima de la mediana, por lo que lo usaremos para eliminar los clientes que son valores atípicos en términos de ingresos de 2010 o 2011:

wrangled_df = wrangled_df[wrangled_df['2011 revenue'] 
                          < ((wrangled_df['2011 revenue'].median()) 
                             + wrangled_df['2011 revenue'].std()*3)]

wrangled_df = wrangled_df[wrangled_df['2010 revenue'] 
                          < ((wrangled_df['2010 revenue'].median()) 
                             + wrangled_df['2010 revenue'].std()*3)]

wrangled_df.head()

resultado


A menudo es una buena idea, después de haber hecho la limpieza de datos y la ingeniería de características, guardar los nuevos datos como un nuevo archivo, de modo que, a medida que se desarrolla el modelo, no sea necesario ejecutar los datos a través de toda la ingeniería de características y la tubería de limpieza cada vez que se quiera volver a ejecutar el código. Podemos hacer esto usando la función `to_csv`. 

wrangled_df.to_csv('datasets/wrangled_transactions.csv')

Examinando las relaciones entre los pronosticadores y el resultado


En este ejercicio, utilizaremos las características que calculamos en el ejercicio anterior y veremos si estas variables tienen alguna relación con nuestro resultado de interés (ingresos por ventas de clientes en 2011):

Usar pandas para importar los datos que guardaste al final del último ejercicio, usando CustomerID como índice:

df = pd.read_csv('datasets/wrangled_transactions.csv', index_col='CustomerID')
La librería seaborn tiene una serie de características de trazado. Su función de diagrama de pares trazará los histogramas y los diagramas de dispersión por pares de todas nuestras variables en una línea, permitiéndonos examinar fácilmente tanto las distribuciones de nuestros datos como las relaciones entre los puntos de datos. Utilice el siguiente código:

import seaborn as sns
%matplotlib inline

sns.pairplot(df)

resultado



En el diagrama anterior, la diagonal muestra un histograma para cada variable, mientras que cada fila muestra el diagrama de dispersión entre una y otra variable. La fila inferior de figuras muestra los diagramas de dispersión de los ingresos de 2011 (nuestro resultado de interés) contra cada una de las otras variables. Debido a que los puntos de datos se superponen y hay una buena cantidad de variación, las relaciones no parecen muy claras en las visualizaciones.

Por lo tanto, podemos usar correlaciones para ayudarnos a interpretar las relaciones. La función `corr` de pandas generará correlaciones entre todas las variables de un DataFrame:

df.corr()
resultado



De nuevo, podemos mirar la última fila para ver las relaciones entre nuestros pronosticadores y el resultado de los intereses (ingresos de 2011). Los números positivos indican una relación positiva, por ejemplo, cuanto más altos sean los ingresos de 2010 de un cliente, mayores serán los ingresos esperados de ellos en 2011. Los números negativos significan lo contrario, por ejemplo, cuantos más días haya transcurrido desde la última compra de un cliente, menor será la expectativa de ingresos para 2011. Además, cuanto más alto sea el número absoluto, más fuerte será la relación.

Las correlaciones resultantes deberían tener sentido. Cuanto más competidores haya en la zona, menor será el ingreso de un lugar, mientras que el ingreso medio, los miembros de la lealtad y la densidad de población están todos positivamente relacionados. La edad de un lugar también está positivamente correlacionada con los ingresos, lo que indica que cuanto más tiempo esté abierto un lugar, más conocido es y más clientes atrae (o tal vez, sólo los lugares que funcionan bien duran mucho tiempo).

Construyendo un modelo lineal que prediga el gasto de los clientes


En este ejercicio, construiremos un modelo lineal sobre los gastos de los clientes utilizando las características creadas en el ejercicio anterior:

Recordemos que sólo hay una relación débil entre `days_since_first_purchase` y los ingresos de 2011- por lo tanto no incluiremos ese predictor en nuestro modelo.

Almacene las columnas de predicción y las columnas de resultados en las variables X e y, respectivamente:


X = df[['2010 revenue',
       'days_since_last_purchase',
       'number_of_purchases',
       'avg_order_cost'
       ]]

y = df['2011 revenue']
Usamos sklearn para realizar una división de los datos, para que podamos evaluar el modelo en un conjunto de datos en el que no fue entrenado, como se muestra aquí:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state = 100)
Importamos LinearRegression de sklearn, creamos un modelo de LinearRegression y ajustamos los datos de entrenamiento:

from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train,y_train)
Examinamos los coeficientes del modelo comprobando la propiedad coef_. Note que estos están en el mismo orden que nuestras columnas X: Ingresos de 2010, días desde la última compra, número de compras y coste medio de pedido:

model.coef_
>> array([  5.78799016,   7.47737544, 336.60769871,  -2.0558923 ])

Compruebe el término de intercepción del modelo comprobando la propiedad intercept_:

model.intercept_
>> 264.8693265705956

Ahora podemos usar el modelo ajustado para hacer predicciones sobre un cliente fuera de nuestro conjunto de datos.

Haga un DataFrame que contenga los datos de un cliente, donde los ingresos de 2010 sean 1.000, el número de días desde la última compra sea 20, el número de compras sea 2, y el coste medio del pedido sea 500. Haz que el modelo haga una predicción sobre los datos de este cliente:

single_customer = pd.DataFrame({
    '2010 revenue': [1000],
    'days_since_last_purchase': [20],
    'number_of_purchases': [2],
    'avg_order_cost': [500]
})

single_customer
model.predict(single_customer)
>> array([5847.67624446])

Podemos trazar las predicciones del modelo en el conjunto de prueba contra el valor real. En primer lugar, importamos matplotlib, y hacemos un gráfico de dispersión de las predicciones del modelo en X_test contra y_test.

Limita los ejes x e y a un valor máximo de 10.000 para que tengamos una mejor visión de dónde se encuentran la mayoría de los puntos de datos.

Por último, añadir una línea con la pendiente 1, que servirá como nuestra referencia: si todos los puntos se encuentran en esta línea, significa que tenemos una relación perfecta entre nuestras predicciones y la respuesta verdadera:

import matplotlib.pyplot as plt
%matplotlib inline

plt.scatter(model.predict(X_test),y_test)
plt.xlim(0,10000)
plt.ylim(0,10000)
plt.plot([0, 10000], [0, 10000], 'k-', color = 'r')
plt.xlabel('Model Predictions')
plt.ylabel('True Value')
plt.show()
resultado


En el gráfico anterior, la línea roja indica dónde estarían los puntos si la predicción fuera la misma que el valor real. Dado que muchos de nuestros puntos están bastante lejos de la línea roja, esto indica que el modelo no es completamente exacto. Sin embargo, parece haber alguna relación, ya que las predicciones más altas del modelo tienen valores verdaderos más altos.

Para examinar más a fondo la relación, podemos usar la correlación. Desde scipy, podemos importar la función pearsonr, que calcula la correlación entre dos matrices, tal y como lo hicimos con Pandas para todo nuestro DataFrame. Podemos usarla para calcular la correlación entre las predicciones de nuestro modelo y el valor real de la siguiente manera:

from scipy.stats.stats import pearsonr
pearsonr(model.predict(X_test),y_test)
>> (0.6125740076680493, 1.934002067463782e-20)
Deberías hacer que te devuelvan dos números: (0.6125740076680493, 1.934002067463782e-20). El primer número es la correlación, que está cerca del 0,6, lo que indica una relación fuerte. El segundo número es el valor p, que indica la probabilidad de ver una relación tan fuerte si los dos conjuntos de números no estuvieran relacionados; el número muy bajo aquí significa que es improbable que esta relación se deba al azar.

Conclusión


Hemos construido un ejemplo sencillo de regresión lineal. Podrías intentar este mismo con Arboles de Decisión y revisar las diferencias en los modelos. Más adelante crearemos otro artículo para entender como hacerlo
Join our private community in Discord

Keep up to date by participating in our global community of data scientists and AI enthusiasts. We discuss the latest developments in data science competitions, new techniques for solving complex challenges, AI and machine learning models, and much more!