Más sobre ciencia de datos: cienciadedatos.net
En este artículo exploraremos cómo el uso de modelos predictivos y prescriptivos nos permiten mejorar la intención de compra de los clientes detectando los factores (drivers) más influyentes en su decisión y optimizando el reparto del presupuesto (budget) para actividades de mejora de percepción del cliente.
Tras el análisis descriptivo inicial, utilizaremos un modelo de regresión logística para la etapa predictiva y optimización lineal para la prescriptiva.
# Librerías
# ==============================================================================
from sinfo import sinfo
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random
import shap
import random
import pyomo.environ as pyo
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.model_selection import train_test_split
random.seed(1211)
sinfo()
Para maximizar el rendimiento de las campañas de marketing, es muy importante ser capaz de valorar qué características de nuestros productos son las que más valoran nuestros clientes. Disponer de esta información permite dirigir las futuras inversiones y esfuerzos.
Uno de los instrumentos para conectar con los clientes potenciales son las encuestas, cada vez más sencillas de llevar a cabo y con un alcance mayor en el nuevo paradigma digital que estamos viviendo.
A lo largo de este documento, veremos cómo utilizar las respuestas de los entrevistados para identificar qué factores determinan su intención de compra y cuantificar su importancia relativa. Una vez obtenida esa importancia, es importante optimizar la asignación del presupuesto a cada factor (palanca) de modo que, la mejora de la percepción del cliente, se traduzca en el mayor incremento posible de intención de compra global.
Para contextualizarlo, supondremos que trabajamos en el sector de la automoción y queremos averiguar de cara al lanzamiento de un nuevo vehículo qué características son las que influyen más en nuestros clientes.
Se ha planteado a distintas personas que han entrado en nuestra web una pequeña encuesta, pidiéndoles que cifren de 0 a 10 cuánto valoran en los productos de nuestra marca las siguientes características:
Diseño y estética
Consumo de combustible
Fiabilidad de la mecánica
Innovación
Precio
Además, se les ha preguntado por su intención de compra en los próximos 6 meses con valores en el mismo rango.
Tras haber obtenido un número razonable de encuestas (~10000), tenemos un set de datos con el siguiente aspecto:
pd.DataFrame.from_dict(
dict(zip(['diseño', 'consumo', 'fiabilidad', 'innovación', 'precio', 'intención_compra'],
[np.random.randint(0,10,5) for i in range(6)])
)
)
Escalaremos los distintos drivers entre 0 y 1 (dividiendo por 10) y consideraremos únicamente como compras potenciales aquellas con una probabilidad de intención superior al 95%.
Vamos a suponer que nuestros clientes le dan una importancia base de:
1 a la innovación y al diseño.
2 al consumo, a la interacción de precio y fiabilidad.
3 al precio, que además está relacionado cuadráticamente con la intención de compra.
Teniendo en cuenta esto, su utilidad vendrá dada por la expresión:
$$z=Innovacion+Diseño+2*Consumo+2*(Precio\times Fiabilidad) + 3*(Precio²)$$n_samples = 10000
# variables explicativas -------------------------------------------------------
diseño = np.random.randint(0,10,n_samples)/10
consumo = np.random.randint(0,10,n_samples)/10
fiabilidad = np.random.randint(0,10,n_samples)/10
innovacion = np.random.randint(0,10,n_samples)/10
precio = np.random.randint(0,10,n_samples)/10
X = np.array([diseño, consumo, fiabilidad, innovacion, precio]).transpose()
feature_labels = ['diseño', 'consumo', 'fiabilidad', 'innovacion', 'precio']
# respuesta --------------------------------------------------------------------
z = (innovacion + diseño + 2*consumo + 2*precio*fiabilidad +3*((precio)**2))
pi_x = np.exp(z)/(1+np.exp(z))
y = 1*(pi_x>0.95)
pd.Series(pi_x).hist();
A continuación, exploraremos diferentes opciones de modelado y cómo extraer insights de ellas. En este caso, lo formularemos como un problema de clasificación y nuestra respuesta será binaria:
1: fuerte intención de compra.
0: sin intención de compra.
Para no llegar a conclusiones equivocadas y en caso de tratar con datos reales, sería importante verificar que la variable respuesta del el dataset esté balanceada. Podemos comprobar que es así y cada clase tiene aproximadamente un 50% de los datos. En caso contrario, podríamos utilizar diversas estrategias, como subsampling de la clase dominante o bien hacer oversampling de la minoritaria.
El subsampling es menos deseable, ya que perdemos información y no tenemos la garantía de que la muestra de datos que extraemos sea representativo del resto. Por otro lado, hacer oversampling de la clase minoritaria, recurriendo por ejemplo a SMOTE o utilizar k-fold cross validation, de modo que en cada subset de los datos de entrenamiento tomemos la clase minoritaria y un número de muestras equivalente de la dominante nos permite utilizar todos los datos de que disponemos.
pd.Series(y).hist()
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size = 0.8, stratify = y)
Aunque en este caso sabemos que la regresión logística no va a ser suficiente para modelar de manera totalmente precisa nuestro problema, debido a las relaciones cuadráticas con el precio y la interacción precio-fiabilidad, es interesante utilizarla como baseline frente a la que comparar el resto de modelos, al ser sencilla de implementar y explicable.
clf = LogisticRegression(random_state=0).fit(X_train, y_train)
Evaluamos la calidad del modelo utilizando la matriz de confusión y observamos que tanto para train como para test, los resultados son homogéneos y de una calidad aceptable. En este caso, somos capaces de determinar con una precisión (accuracy) superior al 95% si un cliente va a comprar o no.
confusion_matrix(y_train, clf.predict(X_train), normalize='true')
print(classification_report(y_train, clf.predict(X_train)))
CM_test = confusion_matrix(y_test, clf.predict(X_test), normalize='true')
print(classification_report(y_test, clf.predict(X_test)))
A continuación, vamos a utilizar los shap values para determinar, tanto la importancia relativa de las distintas variables exógenas (nuestros drivers de compra), como para cuantificar el impacto (utilidad marginal) que tiene cada driver sobre la percepción. Para esto último se recurre a gráficos de dependencias parciales (PDP).
df_test = pd.DataFrame(X_test, columns = feature_labels)
explainer = shap.LinearExplainer(clf, X_train, feature_perturbation="interventional")
shap_values = explainer.shap_values(df_test)
shap.summary_plot(shap_values, df_test, plot_type='bar')
En este caso, vemos que el modelo es capaz de captar la importancia relativa de los distintos drivers de compra, aunque la interacción de fiabilidad y precio se ve enmascarada por la importancia de éste.
Esto es muy interesante pero insuficiente a la hora de determinar el impacto positivo de acciones de marketing específicas para cada driver, para ello y en el caso concreto de la regresión logística, tendremos que extraer los coeficientes del modelo para conectar cómo la variación de la percepción de cada driver afectaría a la intención de compra.
Usando los gráficos de dependencia parcial, podemos ganar una intuición de cómo cada driver afectaría a la intención antes de pasar a modelar el problema de optimización del presupuesto.
shap.partial_dependence_plot("precio", clf.predict, df_test,
model_expected_value=True, feature_expected_value=True)
shap.partial_dependence_plot("consumo", clf.predict, df_test,
model_expected_value=True, feature_expected_value=True)
shap.partial_dependence_plot("diseño", clf.predict, df_test,
model_expected_value=True, feature_expected_value=True)
shap.partial_dependence_plot("fiabilidad", clf.predict, df_test,
model_expected_value=True, feature_expected_value=True)
shap.partial_dependence_plot("innovacion", clf.predict, df_test,
model_expected_value=True, feature_expected_value=True)
Observamos cómo los drivers principales son efectivamente el precio y el consumo.
Si bien una mejora en la percepción de cualquier driver conlleva un aumento de la intención de compra, la innovación, diseño y fiabilidad tienen una contribución marginal mucho menor que los otros dos, que serán en los que nos interesará centrarnos en primer término.
dict(zip(feature_labels, np.log(clf.coef_).reshape(-1)))
Por último, podemos utilizar shap para analizar la decisión de compra de cada individuo de la muestra.
shap.initjs()
random_ok = np.random.choice(np.where(y_test == 1)[0].reshape(-1), 1)[0]
random_ko = np.random.choice(np.where(y_test == 0)[0].reshape(-1), 1)[0]
random_ok, random_ko
shap.force_plot(
explainer.expected_value, shap_values[random_ok,:], X_test[random_ok,:],
feature_names=feature_labels
)
shap.force_plot(
explainer.expected_value, shap_values[random_ko,:], X_test[random_ko,:],
feature_names=feature_labels
)
La interpretación que haríamos es que el primer cliente, que se plantea la adquisición en los próximos 6 meses, actúa movido principalmente por el precio pero también con una contribución equivalente a su decisión de los drivers de diseño y fiabilidad.
Por otro lado, el cliente que lo ha descartado, a pesar de tener una buena percepción de nuestro producto a nivel consumo, la del driver principal, el precio, así como la de la fiabilidad son tan bajas que desplazan la decisión a descartar la compra.
plt.scatter(np.sum(shap_values, axis = 1), y_test);
Vamos a suponer ahora que queremos mejorar la percepción del cliente de cada uno de los drivers y para ello disponemos de un presupuesto de 1000 unidades monetarias (u.m.).
Cada mejora porcentual en la percepción de los drivers tendrá un coste asociado y un impacto en la intención de compra, que es lo que buscamos maximizar. Utilizaremos los datos de las encuestas y el modelo de regresión logística que hemos calculado previamente para construir el modelo de optimización.
Posteriormente, con los datos de validación, mediremos el impacto de nuestra asignación con la nueva intención de compra. Es importante tener en cuenta que, es gracias a tener una precisión adecuada en nuestro modelo predictivo (la regresión logística), es decir, el modelo capturar de manera bastante acertada la relación entre drivers e intención de compra, lo que nos permite pasar a la siguiente etapa, la prescriptiva. De no ser así, deberíamos buscar un modelo mejor ya que, si no, cualquier optimización posterior estaría basada en unas premisas que no son reflejo de la realidad del negocio.
Utilizamos los datos de las encuestas para saber el punto de partida y el margen de mejora:
np.mean(X, axis = 0)
X_train.shape
Todos los drivers tienen una percepción de en torno al 45%, consideraremos que el máximo margen de mejora por tanto sería del 50%, llevando la percepción de cada driver hasta un teórico máximo del 95%.
A la hora de diseñar la función objetivo, tenemos que pensar que nuestra meta global es, mediante las acciones de marketing correspondientes, aumentar las ventas totales y ello se reflearía en el número de encuestados que habrían contestado afirmativamente a la intención de compra. Es decir :
$$ max\;z= \sum_{i\in sEncuestados}\pi(x)=\;\frac{1}{1+e^{-\beta_0 + \sum \beta_j x_j}} $$Como hemos considerado que la intención de compra es una variable modelada como una distribución de Bernouilli y llamando $p$ a la probabilidad $P(Y=1)$, tenemos que:
$$ l=\frac{p}{1-p}=\beta_0 + \sum \beta_j x_j $$Al ser las intenciones de compra de cada encuestado sucesos independientes, podemos entonces decir que será equivalente a maximizar la suma de todas las intenciones de compra.
$$ max\;z= \sum_{i\in\; sEncuestados}\sum_{j\in\; sDrivers} \beta_0 + \beta_j x_j $$Y por último, modificamos la variable independiente para tener en cuenta el valor inicial de percepción de los encuestados y su incremento:
$$ max\;z= \sum_{i\in\; sEncuestados}\sum_{j\in\; sDrivers} \beta_j (pPercepcion_{i,j} + vIncrementoPercepcion_j) $$Los coeficientes $\beta$ del modelo de regresión logística los llamaremos $pBeta$ en el de optimización.
Sets:
$sDrivers$: cardinalidad 5 {precio, combustible, innovación, fiabilidad, diseño}
$sEncuestados$: cardinalidad 8000, el número de encuestados de nuestro set
Parámetros:
$pPrecioIncremento_{\:sDrivers}$: precio asociado al incremento porcentual de cada driver.
$pPercepcion_{\:sEncuestados,\; sDrivers}$: percepción de cada driver por cada encuestado.
$pBetas_{sDrivers}$: coeficiente de cada driver de compra.
Variable de decisión:
# Definición del problema
model = pyo.ConcreteModel()
# sets
model.sDrivers = pyo.Set(initialize = feature_labels)
model.sEncuestados = pyo.Set(initialize = ["p_{}".format(i) for i in range(X_train.shape[0])])
df_percepcion=pd.DataFrame(X_train, columns = feature_labels, index = ["p_{}".format(i) for i in range(X_train.shape[0])])
df_percepcion
# parametros
model.pBetas = pyo.Param(model.sDrivers, initialize = dict(zip(feature_labels, clf.coef_.reshape(-1))))
def load_perception(model, encuesta, driver):
return df_percepcion[driver][encuesta]
model.pPercepcion = pyo.Param(model.sEncuestados, model.sDrivers, initialize = load_perception)
model.pBetas.display()
for i in model.pPercepcion.items():
break
i, model.pPercepcion[('p_7998','innovacion')]
A continuación, vamos a integrar en el modelo los costes de las actividades de marketing. Recordemos que tenemos un presupuesto de 1000 u.m. y potencialmente podemos mejorar 50 puntos por driver hasta un total de 250 puntos.
El coste de mejora de 1 punto de percepción será para cada driver:
Diseño: 10 u.m.
Consumo: 21 u.m.
Fiabilidad: 12 u.m.
Innovación: 15 u.m.
Precio: 36 u.m.
Recordemos que nuestros driver están escalados entre 0 y 1, por lo que un incremento unitario, escalado sería de 0.01.
model.pPrecioIncremento = pyo.Param(
model.sDrivers,
initialize = dict(zip(feature_labels, [10,21,12,15,36]))
)
model.pPrecioIncremento.display()
Es momento de integrar nuestra variable de decisión, que recordemos podrá ir desde 0 (no mejoramos ese driver) hasta una mejora máxima de 50 puntos. Además, supondremos que las mejoras serán en unidades completas, por lo que la variable de decisión será de tipo entero.
model.vIncrementoPercepcion = pyo.Var(model.sDrivers, domain=pyo.NonNegativeIntegers, bounds=(0,50))
model.vIncrementoPercepcion.display()
Del mismo modo, tenemos que integrar la restricción presupuestaria, es decir, que el coste total de las mejoras incrementales no supere nuestro budget de 1000 u.m.
max_budget = 1000
model.ctBudget = pyo.Constraint(
expr= pyo.quicksum(model.vIncrementoPercepcion[i]*model.pPrecioIncremento[i] \
for i in model.sDrivers)<=max_budget
)
Por último, implementamos la función objetivo descrita anteriormente.
model.obj = pyo.Objective(
sense = pyo.maximize,
expr = pyo.quicksum(
(0.01*model.vIncrementoPercepcion[j]+model.pPercepcion[(i,j)])*model.pBetas[j] \
for i in model.sEncuestados for j in model.sDrivers
)
)
opt = pyo.SolverFactory('cbc')
opt.solve(model)
model.vIncrementoPercepcion.display()
model.ctBudget.display()
Tras comprobar la validez de los resultados, el resultado de la optimización nos indica que la mejor estrategia consiste en mejorar la percepción del diseño prácticamente al máximo, la del consumo de manera marginal y la del precio en 13 puntos para maximizar la intención de compra.
Para finalizar, vamos a comparar el impacto de nuestras iniciativas de marketing con los resultados iniciales de las encuestas. Para ello, modificaremos las percepciones de los encuestados con los incrementos logrados:
Precio: +13
Consumo: +2
Diseño: +49
Es importante prestar atención ya que ninguna percepción debería superar el valor del 100%.
feature_labels
increments = [0.49, 0.02, 0.00, 0.00, 0.13]
X_impact = X_test + increments
X_impact
X_impact_corregido = np.where(X_impact > 1.0, 1.0, X_impact)
X_impact_corregido
Para analizar el impacto, tenemos que tener en cuenta la incertidumbre asociada al propio modelo predictivo, ya que independientemente del resultado de la optimización, partimos de una tasa inicial de falsos positivos y negativos, que en este caso es cercana al 4% en cada caso.
Podríamos optar por una perspectiva pesimista, reduciendo en ese 4% las intenciones positivas de compra tras la optimización como una buena referencia del impacto de la iniciativa.
# Cálculo de las decisiones previas y posteriores a la optimización
decisiones_optimizadas = clf.predict(X_impact_corregido)
decisiones_iniciales = clf.predict(X_test)
decisiones_reales = y_test
# Obtención del factor de corrección como las discrepancias entre las decisiones reales y las del modelo de
# regresión logística, teniendo en cuenta falsos positivos y falsos negativos
correccion = sum(decisiones_iniciales!=decisiones_reales)/len(decisiones_iniciales)
# Cálculo del impacto
impacto = (sum(decisiones_optimizadas)/sum(decisiones_iniciales))*(1-correccion)
print("Se obtuvo una mejora del {:.2f}%".format(100*(impacto-1)))
En este artículo hemos explorado cómo, partiendo de encuestas de clientes potenciales y gracias a las analíticas predictiva y prescriptiva, podemos ser capaces de aumentar la intención de compra en un 43.85%.
En casos de negocio reales, la primera etapa de obtención de un modelo predictivo es crucial, ya que la ideoneidad de dicho modelo es determinante en el enfoque de la etapa de optimización posterior.
¿Cómo citar este documento?
Optimización del presupuesto en campañas de marketing con analítica predictiva y prescriptiva by Francisco Espiga, available under a CC BY-NC-SA 4.0 at https://www.cienciadedatos.net/documentos/py31-optimizacion-presupuesto-campañas-marketing.html
¿Te ha gustado el artículo? Tu ayuda es importante
Mantener un sitio web tiene unos costes elevados, tu contribución me ayudará a seguir generando contenido divulgativo gratuito. ¡Muchísimas gracias! 😊
This work by Francisco Espiga is licensed under a Creative Commons Attribution 4.0 International License.