
Introducción
En los últimos 10-15 años, con el avance de las tecnologías digitales, las estrategias de marketing han cambiado considerablemente. Las grandes marcas y también los mercados más pequeños han recopilado una enorme cantidad de datos sobre transacciones, compras, preferencias, poder adquisitivo, actividad de compra, demografía, reseñas, etc. Todos estos datos ayudan a los equipos de marketing a entender el comportamiento del cliente en cada etapa: desde la intención de compra hasta la compra real y la fidelización. Aquí es donde entra en juego el potencial de la ciencia de datos.
La ciencia de datos convierte los big data de marketing en decisiones prácticas, incluso cuando a primera vista no son tan intuitivas, por ejemplo, ciertos patrones de comportamiento del consumidor y co-ocurrencias que no son evidentes. Como resultado, los equipos de marketing obtienen una visión más nítida de su audiencia objetivo, atraen nuevos clientes y retienen a los existentes, optimizan sus estrategias, aumentan la visibilidad de la empresa, crean campañas publicitarias más efectivas, incorporan nuevos canales y, en definitiva, maximizan notablemente los ingresos.
Uno de los casos de uso más habituales de la ciencia de datos en marketing es la predicción del churn o tasa de abandono de clientes. Vamos a analizarlo en detalle.
Caso de uso de ciencia de datos en marketing: predicción del churn de clientes
El churn de clientes es la tendencia de los usuarios a cancelar la suscripción a un servicio que venían utilizando y, por tanto, dejar de ser clientes. La tasa de churn es el porcentaje de clientes que abandonan dentro de un intervalo de tiempo determinado. Es el indicador opuesto a la tasa de crecimiento de clientes, que mide las altas.
La tasa de churn es un indicador clave de la satisfacción del cliente y de la salud general del negocio. Además del churn natural, que siempre existe en cualquier negocio, o el estacional, típico de algunos servicios, hay otros factores que pueden indicar que algo no va bien y debe corregirse. Algunos de ellos son:
- ausencia o baja calidad del soporte al cliente,
- experiencias negativas de los usuarios,
- cambio a un competidor con mejores condiciones o precios,
- cambios en las prioridades del cliente,
- clientes de larga duración que ya no se sienten satisfechos,
- el servicio no cumple las expectativas,
- problemas financieros,
- bloqueos por protección antifraude en los pagos de los clientes.
Una tasa de churn alta supone un problema serio para cualquier empresa por varias razones:
- Se correlaciona con la pérdida de ingresos.
- Cuesta mucho más captar nuevos clientes que retener a los que ya tienes. Esto es especialmente cierto en mercados muy competitivos.
- Si el abandono se debe a un mal servicio, la reputación de la empresa puede verse muy dañada por reseñas negativas en redes sociales o sitios de opinión por parte de exclientes insatisfechos.
La retención es un pilar estratégico para todos los servicios basados en suscripción. Para predecir el churn y tomar medidas preventivas, hay que recopilar y analizar información sobre el comportamiento del cliente (intervalos de compra, tiempo total como cliente, cancelaciones, llamadas y mensajes de seguimiento, actividad online) y detectar qué atributos y combinaciones son característicos de quienes están en riesgo de irse. Saber con antelación qué clientes pueden abandonar pronto, sobre todo si aportan altos ingresos o llevan mucho tiempo, permite centrar los esfuerzos en ellos y diseñar una estrategia eficaz para intentar que se queden. La táctica puede incluir una llamada con una oferta especial: un regalo, un descuento, mejorar la suscripción al mismo precio u otra experiencia personalizada.
Técnicamente, la predicción de churn es un problema típico de clasificación en machine learning, donde etiquetamos a los clientes como "sí" o "no" en cuanto a su riesgo de abandono. Vamos a explorar este caso de uso en Python con datos reales.
Modelaremos el churn en un contexto de telecomunicaciones, donde los clientes pueden tener varios servicios con una operadora bajo un mismo contrato marco. El conjunto de datos incluye características de la actividad de los clientes (limpiadas) y una etiqueta que indica si hubo churn o no.
Veamos los datos y la distribución del churn:
import pandas as pd
telcom = pd.read_csv('telco.csv')
print(f'Number of customers: {telcom.shape[0]:,}\n'
f'Churn values: {set(telcom['Churn'])}\n\n'
f'Churn distribution, %:\n{round(telcom.groupby(['Churn']).size()/telcom.shape[0]*100).convert_dtypes()}')
Number of customers: 7,032
Churn values: {0, 1}
Churn distribution, %:
Churn
0 73
1 27
dtype: float64
Un 27% de clientes abandonaron, una cifra bastante alta. A diferencia del caso de uso anterior, este dataset no parece tener un problema grave de desbalanceo de clases.
Ahora vamos a preprocesar los datos para aplicar técnicas de machine learning y predecir el churn. Esto incluye dividir los datos en conjuntos de entrenamiento y prueba y extraer variables de entrada y objetivo:
from sklearn.model_selection import train_test_split
target = ['Churn']
custid = ['customerID']
cols = [col for col in telcom.columns if col not in custid + target]
X = telcom[cols]
y = telcom[target]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
El primer algoritmo que usaremos para predecir las etiquetas de churn y estimar la precisión es un modelo sencillo de regresión logística:
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
lr = LogisticRegression()
lr.fit(X_train, y_train)
predictions = lr.predict(X_test)
print(f'Test accuracy: {round(accuracy_score(y_test, predictions), 4)}')
Test accuracy: 0.8009
A continuación, añadimos una funcionalidad más a nuestra regresión logística: ejecutarla con regularización L1 para hacer selección de variables a la vez que entrenamos el modelo. Diferentes valores del parámetro C (inverso de la fuerza de regularización) afectan a la precisión del modelo. De momento, fijamos C en 0.025:
lr = LogisticRegression(penalty='l1', C=0.025, solver='liblinear')
lr.fit(X_train, y_train)
predictions = lr.predict(X_test)
print(f'Test accuracy: {round(accuracy_score(y_test, predictions), 4)}')
Test accuracy: 0.7969
Ahora vamos a ajustar el parámetro C de la regularización L1 para encontrar el valor óptimo que reduzca la complejidad del modelo manteniendo un buen rendimiento. Para ello, iteraremos sobre distintos valores de C, construiremos instancias de regresión logística y calcularemos las métricas.
La lista C se ha creado previamente con posibles valores del parámetro. El array l1_metrics tiene 3 columnas: la primera con los valores de C, y las dos siguientes como marcadores de posición para el número de coeficientes no nulos y la precisión del modelo. Probemos este enfoque:
C Non-Zero Coeffs Accuracy
0 1.0000 23.0 0.801479
1 0.5000 22.0 0.799204
2 0.2500 21.0 0.802048
3 0.1000 20.0 0.802617
4 0.0500 18.0 0.802048
5 0.0250 13.0 0.796928
6 0.0100 5.0 0.790102
7 0.0050 3.0 0.783276
8 0.0025 2.0 0.745734
Vemos que los valores más bajos de C reducen el número de coeficientes distintos de cero (es decir, las variables del modelo), con lo que disminuye la complejidad, pero también cae la precisión. Parece que C = 0.05 es el valor óptimo: reduce las variables a 18 y ofrece una precisión ligeramente superior a la del modelo sin regularizar.
Probemos ahora otro algoritmo: un árbol de decisión:
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
print(f'Test accuracy: {round(accuracy_score(y_test, predictions), 4)}')
Test accuracy: 0.7275
Para elegir un modelo más preciso evitando el sobreajuste, podemos ajustar la profundidad del árbol (parámetro max_depth) e identificar su valor óptimo. Técnicamente, el proceso es muy similar al ajuste de C en la regresión logística: iteramos sobre varios valores de max_depth, entrenamos un árbol para cada uno y calculamos el rendimiento.
La lista depth_list se ha creado previamente con los posibles valores del parámetro. El array depth_tuning tiene 2 columnas: la primera con las profundidades candidatas y la segunda como marcador para la precisión. Apliquemos este enfoque para encontrar la profundidad óptima:
depth_list = list(range(2, 15))
depth_tuning = np.zeros((len(depth_list), 2))
depth_tuning[:, 0] = depth_list
for index in range(len(depth_list)):
clf = DecisionTreeClassifier(max_depth=depth_list[index])
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
depth_tuning[index, 1] = accuracy_score(y_test, predictions)
col_names = ['Max_Depth', 'Accuracy']
print(pd.DataFrame(depth_tuning, columns=col_names))
Max_Depth Accuracy
0 2.0 0.756542
1 3.0 0.783276
2 4.0 0.782708
3 5.0 0.791809
4 6.0 0.778157
5 7.0 0.780432
6 8.0 0.757110
7 9.0 0.762230
8 10.0 0.763936
9 11.0 0.752560
10 12.0 0.745165
11 13.0 0.732651
12 14.0 0.727531
La precisión aumenta a medida que crece la profundidad hasta cierto punto y luego empieza a caer. Con max_depth = 5, el árbol alcanza la mayor precisión, por lo que podemos considerar este valor como óptimo.
Tras identificar los mejores parámetros para la regresión logística y el árbol de decisión, reconstruyamos los modelos y detectemos e interpretemos los factores que hacen que el churn suba o baje.
Para la regresión logística, vamos a extraer y analizar los exponentes de los coeficientes resultantes:
# Reconstrucción del mejor modelo
lr = LogisticRegression(penalty='l1', C=0.05, solver='liblinear')
lr.fit(X_train, y_train)
predictions = lr.predict(X_test)
# Combinamos nombres de variables y coeficientes en un único dataframe
feature_names = pd.DataFrame(X_train.columns, columns=['Feature'])
log_coef = pd.DataFrame(np.transpose(lr.coef_), columns=['Coefficient'])
coefficients = pd.concat([feature_names, log_coef], axis=1)
# Calculamos los exponentes de los coeficientes
coefficients['Exp_Coefficient'] = np.exp(coefficients['Coefficient'])
# Eliminamos los coeficientes iguales a cero
coefficients = coefficients[coefficients['Coefficient']!=0]
print(coefficients.sort_values(by=['Exp_Coefficient']))
Feature Coefficient Exp_Coefficient
21 tenure -0.907750 0.403431
4 PhoneService_Yes -0.820517 0.440204
17 Contract_Two year -0.595271 0.551413
8 TechSupport_Yes -0.418254 0.658195
16 Contract_One year -0.414158 0.660896
5 OnlineSecurity_Yes -0.412228 0.662173
6 OnlineBackup_Yes -0.143100 0.866667
3 Dependents_Yes -0.039299 0.961463
7 DeviceProtection_Yes -0.017465 0.982687
11 PaperlessBilling_Yes 0.071389 1.073999
1 SeniorCitizen_Yes 0.097904 1.102857
19 PaymentMethod_Electronic check 0.188533 1.207477
22 MonthlyCharges 0.901454 2.463182
Vemos que la variable con mayor efecto en las probabilidades de abandono es tenure (antigüedad). En general, los exponentes de coeficientes menores que 1 reducen las probabilidades y los mayores que 1 las incrementan.
Para el árbol de decisión, vamos a extraer y representar las reglas if-else:
# Reconstrucción del mejor modelo
clf = DecisionTreeClassifier(max_depth=5)
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
from sklearn import tree
import graphviz
# Exportamos un objeto graphviz del árbol entrenado
exported = tree.export_graphviz(decision_tree=clf,
out_file=None,
feature_names=cols,
precision=1,
class_names=['Not churn', 'Churn'],
filled=True)
graph = graphviz.Source(exported)
display(graph)

Obtenemos una visualización clara del árbol de decisión que puede interpretarse como un conjunto de reglas if-else desde la raíz. Nuevamente vemos que la antigüedad del cliente (tenure) es la variable más importante que explica el churn. El árbol puede profundizarse con más capas para obtener más información sobre otras variables.
Como siguientes pasos, podemos seguir ajustando parámetros, probar distintos enfoques de partición train/test, aplicar y comparar otros algoritmos de machine learning y analizar diversos tipos de métricas para evaluar el rendimiento.
Si te apetece profundizar en la predicción de churn y otras aplicaciones de ciencia de datos en marketing, este curso de Machine Learning for Marketing in Python es un buen punto de partida.

