Modelado de mezcla de marketing de nueva generación con Meridian | por Benjamin Etienne | Febrero de 2025

Ahora usemos la Biblioteca Meridian con datos. El primer paso es instalar Meridian con PIP o Poesía: pip install google-meridian o poetry add google-meridian

Luego obtendremos los datos y comenzaremos a definir columnas que nos interesen.

import pandas as pd

raw_df = pd.read_csv("https://raw.githubusercontent.com/sibylhe/mmm_stan/main/data.csv")

Para las variables de control, utilizaremos todas las variables de vacaciones en el conjunto de datos. Nuestro KPI será ventas, y la granularidad del tiempo será semanalmente.

A continuación, seleccionaremos nuestras variables de medios. Meridian hace una diferencia entre datos de medios y los medios gastan:

  • Medios de comunicación datos (o “ejecución“): Contiene la métrica de exposición por canal y tramo de tiempo (como impresiones por período de tiempo). Los valores de los medios no deben contener valores negativos. Cuando las métricas de exposición no estén disponibles, use lo mismo que en el gasto de los medios.
  • Medios de comunicación gastar : Que contiene el gasto de medios por canal y el tramo de tiempo. Los datos de los medios y el gasto de los medios deben tener las mismas dimensiones.

¿Cuándo debe usar los gastos frente a la ejecución?

Por lo general, se recomienda utilizar métricas de exposición como entradas directas en el modelo, ya que representan cómo la actividad de los medios ha sido consumida por los consumidores. Sin embargo, nadie planea un presupuesto utilizando datos de ejecución. Si usa MMM para optimizar la planificación del presupuesto, mi consejo sería usar los datos que controle, es decir, gasta.

Cargando los datos

En nuestro caso de uso, solo usaremos los gastos de 5 canales: periódico, radio, televisión, redes sociales y exhibición en línea.

# 1. control variables
CONTROL_COLS = [col for col in raw_df.columns if 'hldy_' in col]

# 2. media variables
spends_mapping = {
"mdsp_nsp": "Newspaper",
"mdsp_audtr": "Radio",
"mdsp_vidtr": "TV",
"mdsp_so": "Social Media",
"mdsp_on": "Online Display",
}
MEDIA_COLS = list(spends_mapping.keys())

# 3. sales variables
SALES_COL = "sales"

# 4. Date column
DATE_COL = "wk_strt_dt"
data_df = raw_df[[DATE_COL, SALES_COL, *MEDIA_COLS, *CONTROL_COLS]]
data_df[DATE_COL] = pd.to_datetime(data_df[DATE_COL])

Luego asignaremos las columnas a su tipo de datos para que Meridian pueda entenderlas. El CoordToColumns El objeto nos ayudará a hacer eso y requiere información obligatoria:

  • time : La columna de tiempo (generalmente una fecha, día o semana)
  • controls : las variables de control
  • kpi : La respuesta que queremos que el modelo predice. En nuestro caso, le daremos el valor revenue ya que queremos predecir las ventas.
  • media : Los datos de ejecución de medios (impresiones, clics, etc.) o los gastos si no tenemos datos de ejecución. En nuestro caso, pondremos los gastos.
  • media_spends : Los medios de comunicación gasta.

Hay varios otros parámetros que se pueden usar, a saber, el geo parámetro Si tenemos varios grupos (geografías para su ejemplo), population , reach , frequency . Los detalles sobre estos están fuera de este alcance, pero la documentación se puede encontrar aquí.

Por lo tanto, podemos crear nuestras asignaciones de columna:

from meridian.data import load

coord_to_columns = load.CoordToColumns(
time=DATE_COL,
controls=CONTROL_COLS,
kpi=SALES_COL,
media=MEDIA_COLS,
media_spend=MEDIA_COLS,
)

A continuación, utilizaremos nuestros mapeos DataFrame y las columnas para crear un objeto de datos que el modelo utilice.

loader = load.DataFrameDataLoader(
df=data_df,
kpi_type='revenue',
coord_to_columns=coord_to_columns,
media_to_channel=spends_mapping,
media_spend_to_channel=spends_mapping
)
data = loader.load()

Explorando los datos

Ventas

fig, ax = plt.subplots()
data_df.set_index("wk_strt_dt")[SALES_COL].plot(color=COLORS[1], ax=ax)
ax.set(title="Sales", xlabel='date', ylabel="sales");
fig.tight_layout();

Parece que hay una buena estacionalidad con picos alrededor de la Navidad. La tendencia es constante en general con un nivel oscilante entre 50 y 150 m.

Los medios gastan

fig, ax = plt.subplots(5, figsize=(20,30))

for axis, channel in zip(ax, spends_columns_raw):
data_df.set_index("wk_strt_dt")[channel].plot(ax=axis, color=COLORS[1])
axis.legend(title="Channel", fontsize=12)
axis.set(title=spends_mapping[channel], xlabel="Date", ylabel="Spend");
fig.tight_layout()

Observamos una tendencia claramente decreciente para los periódicos correlacionados con una tendencia creciente para las redes sociales. Los gastos también parecen estar aumentando en o justo antes de Navidad.

Especificando el modelo

Construir el modelo y elegir los parámetros correctos puede ser bastante complejo, ya que hay muchas opciones disponibles. Compartiré aquí mis hallazgos, pero no dude en explorar solo.

La primera parte es elegir los anteriores para nuestros medios de comunicación. Usaremos el PriorDistribution clase que nos permite definir varias variables. Puede cambiar los antecedentes de casi cualquier parámetro del modelo (Mu, tau, gamma, beta, etc.), pero por ahora solo nos centraremos en el beta que son los coeficientes de nuestras variables de los medios. Mi recomendación es, si está utilizando solo gastos, para usar el beta_m . Puedes elegir el roi_m o mroi_m Pero deberá adaptar el código para usar un anterior diferente.

import tensorflow_probability as tfp
from meridian import constants
from meridian.model import prior_distribution

prior = prior_distribution.PriorDistribution(
beta_m=tfp.distributions.HalfNormal(
0.2,
name=constants.BETA_M,
# If you want to use the ROI vision instead of the coefficients approach
# roi_m=tfp.distributions.HalfNormal(
# 0.2,
# name=constants.ROI_M
)
)

Al definir las especificaciones del modelo, podrá definir:

  • los anteriores (cf arriba).
  • max_len : el número máximo de períodos de retraso (≥ `0`) para
    incluir en el cálculo de Adstock. Recomiendo elegir entre 2 y 6.
  • paid_media_prior_type : Si elige modelar el beta_m luego elige coefficient . Más, elige roi o mroi .
  • knots: Meridian aplica el ajuste automático de estacionalidad a través de un enfoque de intercepción variable en el tiempo, controlado por el knots valor. Puede establecer un valor de 1 (intersección constante, sin modelado de estacionalidad), o igual a un número dado que debe ser inferior a la longitud de los datos. Un valor bajo podría conducir a una línea de base baja, un valor alto podría conducir a un sobreajuste y conducir a una línea de base que se come todo. Recomiendo establecerlo en el 10% del número de puntos de datos

También es posible definir una división de prueba de tren para evitar el sobreajuste a través del holdout_id parámetro. No lo cubriré aquí, pero es una mejor práctica hacer esta división para la selección de modelos.

En una palabra:

from meridian.model import spec
from meridian.model import model

model_spec = spec.ModelSpec(
prior=prior,
max_lag=6,
knots=int(0.1*len(data_df)),
paid_media_prior_type='coefficient',
)
mmm = model.Meridian(input_data=data, model_spec=model_spec)

Ejecutando el modelo

Ajustar el modelo puede ser lento si tiene una gran cantidad de puntos de datos y variables. Recomiendo comenzar con 2 cadenas y dejar el número predeterminado de muestras:

mmm.sample_prior(500)
mmm.sample_posterior(n_chains=2, n_adapt=500, n_burnin=500, n_keep=1000)

Modelo de diagnóstico

Una vez que se termine el modelo, realizaremos una serie de cheques para asegurarnos de que podamos usarlo con confianza.

  1. R-Hat

R-Hat cerca de 1.0 indica convergencia. R-Hat <1.2 indica una convergencia aproximada y es un umbral razonable para muchos problemas.

La falta de convergencia generalmente tiene uno de los dos culpables. O el modelo está muy mal especificado para los datos, lo que puede estar en la probabilidad (especificación del modelo) o en el anterior. O no hay suficiente quemado, lo que significa que n_adapt + n_burnin no es lo suficientemente grande.

from meridian.analysis import visualizer

model_diagnostics = visualizer.ModelDiagnostics(mmm)
model_diagnostics.plot_rhat_boxplot()

Vemos que todos los valores R-HAT están por debajo de 1.02, lo que no indica divergencia o problema durante el entrenamiento.

2. Trace modelo

La traza del modelo contiene los valores de muestra de las cadenas. Un buen rastro es cuando las dos distribuciones posteriores (ya que tenemos 2 cadenas) para un parámetro dado se superponen muy bien. En el diagrama a continuación, puede ver esas líneas azules y negras en el lado izquierdo y superpuesto:

3. Distribuciones posteriores vs posteriores

Para saber si nuestro modelo ha aprendido durante el ajuste, compararemos la distribución previa frente a la posterior. Si se superponen perfectamente, esto significa que nuestro modelo no ha cambiado sus distribuciones anteriores y, por lo tanto, probablemente no ha aprendido nada, o que los antecedentes estaban mal especificados. Para asegurarnos de que nuestro modelo haya aprendido, nos gustaría ver un ligero cambio en las distribuciones:

Claramente, que los antecedentes y los posteriores no se superponen. Para la televisión y las redes sociales para EX, vemos que los priors de halfnormal naranja se han trasladado a las distribuciones casi normales.

4. R2 y ajuste del modelo

Finalmente, utilizaremos métricas para evaluar el ajuste de nuestro modelo. Probablemente sepa sobre métricas como R2, Mape, etc., así que echemos un vistazo a esos valores:

model_diagnostics = visualizer.ModelDiagnostics(mmm)
model_diagnostics.predictive_accuracy_table()
Imagen del autor

Obviamente, un R2 de 0.54 no es excelente en absoluto. Podríamos mejorar eso agregando más nudos en la línea de base, o más datos al modelo, o jugar con los Priors para tratar de capturar más información.

Ahora trazemos el modelo:

model_fit = visualizer.ModelFit(mmm)
model_fit.plot_model_fit()

Contribuciones de medios a ventas

Recuerde que uno de los objetivos de MMM es proporcionarle contribuciones de los medios frente a sus ventas. Esto es lo que veremos con un diagrama de cascada:

media_summary = visualizer.MediaSummary(mmm)
media_summary.plot_contribution_waterfall_chart()

Lo que generalmente esperamos es tener una línea de base entre el 60 y el 80%. Tenga en cuenta que este valor puede ser muy sensible y depender de la especificación y los parámetros del modelo. Te animo a que juegues con diferentes knots valores y antecedentes y ver el impacto que puede tener en el modelo.

Gasta vs contribuciones

La tabla de gasto versus contribución compara el gasto y los ingresos incrementales o la división de KPI entre los canales. La barra verde destaca el retorno de la inversión (ROI) para cada canal.

media_summary.plot_roi_bar_chart()

Vemos que el ROI más alto proviene de las redes sociales, seguido de la televisión. Pero aquí también es donde el intervalo de incertidumbre es el más grande. MMM no es una respuesta exacta: le da valores e incertidumbre asociadas a ellos. Mi opinión aquí es que los intervalos de incertidumbre son muy grandes. Tal vez deberíamos usar más pasos de muestreo o agregar más variables al modelo.

Optimización de nuestro presupuesto

Recuerde que uno de los objetivos del MMM es proponer una asignación óptima de gastos para maximizar los ingresos. Esto se puede hacer primero mirando lo que llamamos Curvas de respuesta. Las curvas de respuesta describen la relación entre el gasto en marketing y los ingresos incrementales resultantes.

Podemos ver allí que:

  1. Los ingresos incrementales aumentan a medida que aumenta el gasto
  2. Para algunos puntos de contacto como el periódico, el crecimiento es más lento, lo que significa que un aumento de 2x en el gasto no se traducirá en un ingreso incremental 2x.

El objetivo de la optimización será tomar esas curvas y navegar para encontrar la mejor combinación de valor que maximice nuestra ecuación de ventas. Sabemos que las ventas = f (medios, control, línea de base), y estamos tratando de encontrar los valores de los medios* que maximicen nuestra función.

Podemos elegir entre varios problemas de optimización, para Ej:

  • ¿Cómo puedo alcanzar el nivel de ventas de sames con menos presupuesto?
  • Dado el mismo presupuesto, ¿cuál es el ingreso máximo que puedo obtener?

Usemos Meridian para optimizar nuestro presupuesto y maximizar las ventas (Escenario 1). Usaremos los parámetros predeterminados aquí, pero es posible ajustar las restricciones en cada canal para limitar el alcance de la búsqueda.

from meridian.analysis import optimizer

budget_optimizer = optimizer.BudgetOptimizer(mmm)
optimization_results = budget_optimizer.optimize()

# Plot the response curves before and after
optimization_results.plot_response_curves()

Podemos ver que el optimizador recomienda disminuir los gastos para periódicos, exhibición en línea y recomienda aumentar los gastos de radio, redes sociales y televisión.

¿Cómo se traduce en términos de ingresos?

¡Aumento del 3% en los ingresos simplemente reequilibrando nuestro presupuesto! Por supuesto, esta conclusión es un poco apresurada. Primero, reproducir el pasado es fácil. No tiene garantía de que sus ventas de línea de base (60%) se comportarían lo mismo el próximo año. Piensa en Covid. En segundo lugar, nuestro modelo no tiene en cuenta las interacciones entre los canales. Lo que hemos usado aquí es un modelo adicional simple, pero algunos enfoques usan un modelo multiplicativo log-log para tener en cuenta las interacciones entre variables. Tercero, existe incertidumbre en nuestras curvas de respuesta que no maneja el optimizador, ya que solo toma la curva de respuesta promedio para cada canal. Las curvas de respuesta con incertidumbre parecen la imagen a continuación y la optimización bajo incertidumbre se vuelve mucho más compleja:

Sin embargo, todavía te da una idea de dónde estás tal vez sobre el gasto o bajo gasto.

MMM es una herramienta compleja pero poderosa que puede descubrir ideas de sus datos de marketing, ayudarlo a comprender su eficiencia de marketing y ayudarlo en la planificación del presupuesto. Los nuevos métodos que dependen de la inferencia bayesiana proporcionan una buena característica, como el modelado de Adstock y Saturación, la incorporación de datos a nivel geográfico, niveles de incertidumbre y capacidades de optimización. Feliz codificación.