Bienvenido a la parte 3 de mi serie sobre modelos de marketing mix (MMM), una guía práctica que le ayudará a dominar MMM. A lo largo de esta serie, cubriremos temas clave como el entrenamiento de modelos, la validación, la calibración y la optimización del presupuesto, todo utilizando el poderoso marketing-pymc paquete de Python. Ya sea que sea nuevo en MMM o esté buscando perfeccionar sus habilidades, esta serie lo equipará con herramientas prácticas y conocimientos para mejorar sus estrategias de marketing.
Si te perdiste la parte 2, mírala aquí:
En la tercera entrega de la serie, cubriremos cómo podemos comenzar a obtener valor comercial de nuestros modelos de marketing mix cubriendo las siguientes áreas:
- ¿Por qué las organizaciones quieren optimizar sus presupuestos de marketing?
- ¿Cómo podemos utilizar los resultados de nuestro modelo de marketing mix para optimizar los presupuestos?
- Un tutorial de Python que demuestra cómo optimizar presupuestos usando marketing-pymc.
El cuaderno completo se puede encontrar aquí:
Esta famosa cita (¿creo que de John Wanamaker?) ilustra tanto el desafío como la oportunidad en marketing. Si bien los análisis modernos han recorrido un largo camino, el desafío sigue siendo relevante: comprender qué partes de su presupuesto de marketing generan valor.
Los canales de marketing pueden variar significativamente en términos de rendimiento y ROI debido a varios factores:
- Alcance y participación de la audiencia: Algunos canales son más eficaces para llegar a clientes potenciales específicos alineados con su público objetivo.
- Costo de adquisición – El costo de llegar a los clientes potenciales difiere según los canales.
- Saturación del canal — El uso excesivo de un canal de marketing puede generar rendimientos decrecientes.
Esta variabilidad crea la oportunidad de hacer preguntas críticas que pueden transformar su estrategia de marketing:
La optimización eficaz del presupuesto es un componente fundamental de las estrategias de marketing modernas. Al aprovechar los resultados de MMM, las empresas pueden tomar decisiones informadas sobre dónde asignar sus recursos para lograr el máximo impacto. MMM proporciona información sobre cómo los distintos canales contribuyen a las ventas generales, lo que nos permite identificar oportunidades de mejora y optimización. En las siguientes secciones, exploraremos cómo podemos traducir los resultados del MMM en estrategias viables de asignación presupuestaria.
2.1 Curvas de respuesta
Una curva de respuesta puede traducir los resultados de MMM en una forma integral, mostrando cómo las ventas responden al gasto en cada canal de marketing.
Las curvas de respuesta por sí solas son muy poderosas y nos permiten ejecutar escenarios hipotéticos. Usando la curva de respuesta anterior como ejemplo, podríamos estimar cómo cambia la contribución social a las ventas a medida que gastamos más. También podemos ver visualmente dónde empiezan a surtir efecto los rendimientos decrecientes. Pero, ¿qué pasa si queremos intentar responder a escenarios hipotéticos más complejos, como optimizar los presupuestos a nivel de canal dado un presupuesto general fijo? Aquí es donde entra en juego la programación lineal. ¡Exploremos esto en la siguiente sección!
2.2 Programación lineal
La programación lineal es un método de optimización que se puede utilizar para encontrar la solución óptima de una función lineal dadas algunas restricciones. Es una herramienta muy versátil del área de investigación de operaciones pero que no suele obtener el reconocimiento que merece. Se utiliza para resolver problemas de programación, transporte y asignación de recursos. Vamos a explorar cómo podemos utilizarlo para optimizar los presupuestos de marketing.
Intentemos comprender la programación lineal con un simple problema de optimización del presupuesto:
- Variables de decisión (x): Estas son las cantidades desconocidas para las que queremos estimar valores óptimos, por ejemplo, el gasto en marketing en cada canal.
- Función objetivo (Z): La ecuación lineal que estamos tratando de minimizar o maximizar, por ejemplo, maximizar la suma de la contribución de ventas de cada canal.
- Restricciones: Algunas restricciones en las variables de decisión, generalmente representadas por desigualdades lineales, por ejemplo, el presupuesto total de marketing es igual a £50 millones, los presupuestos a nivel de canal están entre £5 millones y £15 millones.
La intersección de todas las restricciones forma una región factible, que es el conjunto de todas las soluciones posibles que satisfacen las restricciones dadas. El objetivo de la programación lineal es encontrar el punto dentro de la región factible que optimice la función objetivo.
Dada la transformación de saturación que aplicamos a cada canal de marketing, optimizar los presupuestos a nivel de canal es en realidad un problema de programación no lineal. La programación de mínimos cuadrados secuenciales (SLSQP) es un algoritmo utilizado para resolver problemas de programación no lineal. Permite restricciones tanto de igualdad como de desigualdad, lo que la convierte en una opción sensata para nuestro caso de uso.
- Restricciones de igualdad Por ejemplo, el presupuesto total de marketing equivale a 50 millones de libras esterlinas.
- Restricciones de desigualdad por ejemplo, presupuestos a nivel de canal entre £ 5 y £ 15 millones
SciPy tiene una gran implementación de SLSQP:
El siguiente ejemplo ilustra cómo podríamos usarlo:
from scipy.optimize import minimizeresult = minimize(
fun=objective_function, # Define your ROI function here
x0=initial_guess, # Initial guesses for spends
bounds=bounds, # Channel-level budget constraints
constraints=constraints, # Equality and inequality constraints
method='SLSQP'
)
print(result)
Escribir código de optimización del presupuesto desde cero es un ejercicio complejo pero muy gratificante. Afortunadamente, el marketing-pymc El equipo ha hecho el trabajo pesado, proporcionando un marco sólido para ejecutar escenarios de optimización del presupuesto. En la siguiente sección, exploraremos cómo su paquete puede agilizar el proceso de asignación presupuestaria y hacerlo más accesible para los analistas.
Ahora que entendemos cómo podemos usar el resultado de MMM para optimizar los presupuestos, ¡veamos cuánto valor podemos generar usando nuestro modelo del último artículo! En este tutorial cubriremos:
- Simulando datos
- Entrenando el modelo
- Validando el modelo
- Curvas de respuesta
- Optimización del presupuesto
3.1 Simulación de datos
Vamos a reutilizar el proceso de generación de datos del primer artículo. Si desea un recordatorio sobre el proceso de generación de datos, eche un vistazo al primer artículo donde hicimos un recorrido detallado:
np.random.seed(10)# Set parameters for data generator
start_date = "2021-01-01"
periods = 52 * 3
channels = ["tv", "social", "search"]
adstock_alphas = [0.50, 0.25, 0.05]
saturation_lamdas = [1.5, 2.5, 3.5]
betas = [350, 150, 50]
spend_scalars = [10, 15, 20]
df = dg.data_generator(start_date, periods, channels, spend_scalars, adstock_alphas, saturation_lamdas, betas)
# Scale betas using maximum sales value - this is so it is comparable to the fitted beta from pymc (pymc does feature and target scaling using MaxAbsScaler from sklearn)
betas_scaled = [
((df["tv_sales"] / df["sales"].max()) / df["tv_saturated"]).mean(),
((df["social_sales"] / df["sales"].max()) / df["social_saturated"]).mean(),
((df["search_sales"] / df["sales"].max()) / df["search_saturated"]).mean()
]
# Calculate contributions
contributions = np.asarray([
round((df["tv_sales"].sum() / df["sales"].sum()), 2),
round((df["social_sales"].sum() / df["sales"].sum()), 2),
round((df["search_sales"].sum() / df["sales"].sum()), 2),
round((df["demand"].sum() / df["sales"].sum()), 2)
])
df[["date", "demand", "demand_proxy", "tv_spend_raw", "social_spend_raw", "search_spend_raw", "sales"]]
3.2 Entrenando el modelo
Ahora vamos a volver a entrenar el modelo del primer artículo. Prepararemos los datos de entrenamiento de la misma manera que la última vez:
- Dividir datos en características y destino.
- Creación de índices para tramos de tren y fuera de tiempo.
Sin embargo, como este artículo no se centra en la calibración del modelo, incluiremos la demanda como variable de control en lugar de demand_proxy. Esto significa que el modelo estará muy bien calibrado. Aunque esto no es muy realista, nos dará buenos resultados para ilustrar cómo podemos optimizar los presupuestos.
# set date column
date_col = "date"# set outcome column
y_col = "sales"
# set marketing variables
channel_cols = ["tv_spend_raw",
"social_spend_raw",
"search_spend_raw"]
# set control variables
control_cols = ["demand"]
# create arrays
X = df[[date_col] + channel_cols + control_cols]
y = df[y_col]
# set test (out-of-sample) length
test_len = 8
# create train and test indexs
train_idx = slice(0, len(df) - test_len)
out_of_time_idx = slice(len(df) - test_len, len(df))
mmm_default = MMM(
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation(),
date_column=date_col,
channel_columns=channel_cols,
control_columns=control_cols,
)
fit_kwargs = {
"tune": 1_000,
"chains": 4,
"draws": 1_000,
"target_accept": 0.9,
}
mmm_default.fit(X[train_idx], y[train_idx], **fit_kwargs)
3.3 Validando el modelo
Antes de pasar a la optimización, comprobemos que nuestro modelo se ajusta bien. Primero comprobamos las verdaderas contribuciones:
channels = np.array(["tv", "social", "search", "demand"])true_contributions = pd.DataFrame({'Channels': channels, 'Contributions': contributions})
true_contributions= true_contributions.sort_values(by='Contributions', ascending=False).reset_index(drop=True)
true_contributions = true_contributions.style.bar(subset=['Contributions'], color='lightblue')
true_contributions
Como era de esperar, nuestro modelo se alinea muy de cerca con las contribuciones reales:
mmm_default.plot_waterfall_components_decomposition(figsize=(10,6));
3.4 Curvas de respuesta
Antes de entrar en la optimización del presupuesto, echemos un vistazo a las curvas de respuesta. Hay dos formas de observar las curvas de respuesta en el marketing-pymc paquete:
- Curvas de respuesta directa
- Curvas de respuesta de costo compartido
Comencemos con las curvas de respuesta directa. En las curvas de respuesta directa simplemente creamos un diagrama de dispersión del gasto semanal frente a la contribución semanal para cada canal.
A continuación trazamos las curvas de respuesta directa:
fig = mmm_default.plot_direct_contribution_curves(show_fit=True, xlim_max=1.2)
[ax.set(xlabel="spend") for ax in fig.axes];
Las curvas de respuesta de costo compartido son una forma alternativa de comparar la efectividad de los canales. Cuando δ = 1,0, el gasto del canal permanece en el mismo nivel que los datos de capacitación. Cuando δ = 1,2, el gasto del canal aumenta en un 20%.
A continuación trazamos las curvas de respuesta de costo compartido:
mmm_default.plot_channel_contributions_grid(start=0, stop=1.5, num=12, figsize=(15, 7));
También podemos cambiar el eje x para mostrar valores de gasto absoluto:
mmm_default.plot_channel_contributions_grid(start=0, stop=1.5, num=12, absolute_xrange=True, figsize=(15, 7));
Las curvas de respuesta son excelentes herramientas para ayudar a pensar en la planificación de futuros presupuestos de marketing a nivel de canal. ¡A continuación, pongamoslos en práctica y ejecutemos algunos escenarios de optimización del presupuesto!
3.5 Optimización del presupuesto
Para empezar, establezcamos un par de parámetros:
- cambio_perc: Esto se utiliza para establecer la restricción en torno al gasto mínimo y máximo en cada canal. Esta restricción nos ayuda a mantener el escenario realista y significa que no extrapolamos las curvas de respuesta demasiado fuera de lo que el modelo ha visto en el entrenamiento.
- presupuesto_len: Ésta es la duración del escenario presupuestario en semanas.
Comenzaremos utilizando la duración deseada del escenario presupuestario para seleccionar el período de datos más reciente.
perc_change = 0.20
budget_len = 12
budget_idx = slice(len(df) - test_len, len(df))
recent_period = X[budget_idx][channel_cols]recent_period
Luego utilizamos este período reciente para establecer restricciones presupuestarias generales y restricciones de canal a nivel semanal:
# set overall budget constraint (to the nearest £1k)
budget = round(recent_period.sum(axis=0).sum() / budget_len, -3)# record the current budget split by channel
current_budget_split = round(recent_period.mean() / recent_period.mean().sum(), 2)
# set channel level constraints
lower_bounds = round(recent_period.min(axis=0) * (1 - perc_change))
upper_bounds = round(recent_period.max(axis=0) * (1 + perc_change))
budget_bounds = {
channel: [lower_bounds[channel], upper_bounds[channel]]
for channel in channel_cols
}
print(f'Overall budget constraint: {budget}')
print('Channel constraints:')
for channel, bounds in budget_bounds.items():
print(f' {channel}: Lower Bound = {bounds[0]}, Upper Bound = {bounds[1]}')
¡Ahora es el momento de ejecutar nuestro escenario! Introducimos los datos y parámetros relevantes y recuperamos el gasto óptimo. Lo comparamos con tomar el presupuesto total y dividirlo por las proporciones de división del presupuesto actual (que hemos llamado gasto real).
model_granularity = "weekly"# run scenario
allocation_strategy, optimization_result = mmm_default.optimize_budget(
budget=budget,
num_periods=budget_len,
budget_bounds=budget_bounds,
minimize_kwargs={
"method": "SLSQP",
"options": {"ftol": 1e-9, "maxiter": 5_000},
},
)
response = mmm_default.sample_response_distribution(
allocation_strategy=allocation_strategy,
time_granularity=model_granularity,
num_periods=budget_len,
noise_level=0.05,
)
# extract optimal spend
opt_spend = pd.Series(allocation_strategy, index=recent_period.mean().index).to_frame(name="opt_spend")
opt_spend["avg_spend"] = budget * current_budget_split
# plot actual vs optimal spend
fig, ax = plt.subplots(figsize=(9, 4))
opt_spend.plot(kind='barh', ax=ax, color=['blue', 'orange'])
plt.xlabel("Spend")
plt.ylabel("Channel")
plt.title("Actual vs Optimal Spend by Channel")
plt.legend(["Optimal Spend", "Actual Spend"])
plt.legend(["Optimal Spend", "Actual Spend"], loc='lower right', bbox_to_anchor=(1.5, 0.0))
plt.show()
Podemos ver que la sugerencia es trasladar el presupuesto de los canales digitales a la televisión. Pero ¿cuál es el impacto en las ventas?
Para calcular la contribución del gasto óptimo, debemos introducir el nuevo valor de gasto por canal más cualquier otra variable en el modelo. Solo tenemos demanda, por lo que para esto ingresamos el valor medio del período reciente. También calcularemos la contribución del gasto medio de la misma forma.
# create dataframe with optimal spend
last_date = mmm_default.X["date"].max()
new_dates = pd.date_range(start=last_date, periods=1 + budget_len, freq="W-MON")[1:]
budget_scenario_opt = pd.DataFrame({"date": new_dates,})
budget_scenario_opt["tv_spend_raw"] = opt_spend["opt_spend"]["tv_spend_raw"]
budget_scenario_opt["social_spend_raw"] = opt_spend["opt_spend"]["social_spend_raw"]
budget_scenario_opt["search_spend_raw"] = opt_spend["opt_spend"]["search_spend_raw"]
budget_scenario_opt["demand"] = X[budget_idx][control_cols].mean()[0]# calculate overall contribution
scenario_contrib_opt = mmm_default.sample_posterior_predictive(
X_pred=budget_scenario_opt, extend_idata=False
)
opt_contrib = scenario_contrib_opt.mean(dim="sample").sum()["y"].values
# create dataframe with avg spend
last_date = mmm_default.X["date"].max()
new_dates = pd.date_range(start=last_date, periods=1 + budget_len, freq="W-MON")[1:]
budget_scenario_avg = pd.DataFrame({"date": new_dates,})
budget_scenario_avg["tv_spend_raw"] = opt_spend["avg_spend"]["tv_spend_raw"]
budget_scenario_avg["social_spend_raw"] = opt_spend["avg_spend"]["social_spend_raw"]
budget_scenario_avg["search_spend_raw"] = opt_spend["avg_spend"]["search_spend_raw"]
budget_scenario_avg["demand"] = X[budget_idx][control_cols].mean()[0]
# calculate overall contribution
scenario_contrib_avg = mmm_default.sample_posterior_predictive(
X_pred=budget_scenario_avg , extend_idata=False
)
avg_contrib = scenario_contrib_avg.mean(dim="sample").sum()["y"].values
# calculate % increase in sales
print(f'% increase in sales: {round((opt_contrib / avg_contrib) - 1, 2)}')
¡El gasto óptimo nos da un aumento del 6% en las ventas! ¡Eso es impresionante, especialmente teniendo en cuenta que hemos fijado el presupuesto general!
Hoy hemos visto lo poderosa que puede ser la optimización del presupuesto. Puede ayudar a las organizaciones con la planificación y previsión del presupuesto mensual/trimestral/anual. Como siempre, la clave para hacer buenas recomendaciones es tener un modelo sólido y bien calibrado.