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 Facebook TikTok Email 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 Facebook 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 [ ]: