Notebook (3) – Google Meridian Media Mix Model.¶
In this notebook we train a Bayesian Media Mix Model (MMM) using Google's Meridian framework.
Objectives
- Load cleaned data
- Prepare Meridian inputs
- Configure media channels
- Fit Bayesian MMM
- Inspect posterior convergence
- Generate predictions
In [16]:
# %pip install google-meridian
In [17]:
# Import Libraries
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
In [18]:
# meridian functions
from meridian.data.data_frame_input_data_builder import DataFrameInputDataBuilder
from meridian.model.model import Meridian
from meridian.analysis import optimizer
from meridian.analysis import summarizer
from meridian.analysis import visualizer
from meridian.model import model
from meridian.model import prior_distribution as pdist, spec as specmod
In [19]:
# Load data
orig_df = pd.read_csv(
"../data/mmm_clean.csv",
parse_dates=["Week"]
)
df = orig_df[["Week","Trend","Seasonality","Promotion","Temperature","Competitor_Spend","TV","Search","Facebook","TikTok","Email","Sales"]]
df.head()
Out[19]:
| Week | Trend | Seasonality | Promotion | Temperature | Competitor_Spend | TV | Search | TikTok | Sales | |||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2022-01-03 | 100.000000 | 5.000000 | 0 | 63.428468 | 78221.896187 | 322669.659493 | 83109.586507 | 52502.177068 | 43445.562766 | 3 | 158.31 |
| 1 | 2022-01-10 | 100.193548 | 6.060076 | 1 | 64.666533 | 95545.241691 | 168430.712890 | 77252.334458 | 60313.092603 | 29281.450303 | 2 | 190.15 |
| 2 | 2022-01-17 | 100.387097 | 6.820437 | 0 | 67.159409 | 92388.716347 | 239223.726622 | 102455.037248 | 66211.908246 | 37580.713162 | 4 | 184.85 |
| 3 | 2022-01-24 | 100.580645 | 7.288603 | 0 | 64.363935 | 82797.397474 | 278701.690232 | 77158.742611 | 51291.074242 | 32246.628314 | 2 | 190.75 |
| 4 | 2022-01-31 | 100.774194 | 7.487555 | 0 | 73.502846 | 90837.625020 | 310094.282084 | 91073.493558 | 62241.201174 | 35201.213761 | 2 | 206.21 |
In [20]:
# all columns in the dataframe
all_cols = df.columns.tolist()
all_cols
Out[20]:
['Week', 'Trend', 'Seasonality', 'Promotion', 'Temperature', 'Competitor_Spend', 'TV', 'Search', 'Facebook', 'TikTok', 'Email', 'Sales']
In [21]:
# the dependent variable
kpi_name = "Sales"
# the media variables
media_cols = [
'TV',
'Search',
'Facebook',
'TikTok',
'Email',
]
# the control variables
exclude = set(media_cols + ["Week", kpi_name])
control_cols = [c for c in all_cols if c not in exclude]
In [22]:
builder = DataFrameInputDataBuilder(kpi_type="revenue") \
.with_kpi(df, kpi_col=kpi_name, time_col="Week") \
.with_media(
df,
media_cols=media_cols,
media_spend_cols=media_cols,
media_channels=media_cols,
time_col="Week"
)\
.with_non_media_treatments(
df,
non_media_treatment_cols=control_cols,
time_col="Week"
)
In [23]:
input_data = builder.build()
model_spec = specmod.ModelSpec(
knots=1, # single coefficient over time -> constant baseline
)
In [24]:
mmm = Meridian(input_data=input_data)
mmm.sample_prior(500)
mmm.sample_posterior(
n_chains=2, n_adapt=2000, n_burnin=500, n_keep=1000, seed=0
)
In [25]:
model_diagnostics = visualizer.ModelDiagnostics(mmm)
model_diagnostics.plot_rhat_boxplot()
Out[25]:
In [26]:
model_fit = visualizer.ModelFit(mmm)
model_fit.plot_model_fit()
Out[26]:
In [27]:
media_summary = visualizer.MediaSummary(mmm)
media_summary.plot_channel_contribution_area_chart()
Out[27]:
In [28]:
media_summary.plot_spend_vs_contribution()
Out[28]:
In [29]:
contrib = media_summary.contribution_metrics(
include_non_paid=True,
aggregate_times=False
)
contrib.head()
Out[29]:
| time | channel | incremental_outcome | pct_of_contribution | |
|---|---|---|---|---|
| 156 | 2022-01-03 | TV | 110.057762 | 0.725109 |
| 159 | 2022-01-03 | TikTok | 82.938873 | 0.546438 |
| 158 | 2022-01-03 | 75.047333 | 0.494444 | |
| 157 | 2022-01-03 | Search | 61.746777 | 0.406815 |
| 162 | 2022-01-03 | Seasonality | 26.499048 | 0.174587 |
In [30]:
budget_optimizer = optimizer.BudgetOptimizer(mmm)
optimization_results = budget_optimizer.optimize()
optimization_results.plot_response_curves()
Out[30]:
In [ ]: