Meridian Full-Funnel MMM Walkthrough

This is a walkthrough of full-funnel MMM in Meridian. It uses Branded Google Query Volume (bGQV) as an example of a brand equity variable. For more on when one might want to use a full-funnel model, see Full-funnel MMM: unifying upper-funnel and lower-funnel measurement.

Step 0: Install and Setup

Ensure you are running on a GPU-enabled runtime. We install the google-meridian package and import necessary modules.

# Install meridian: from PyPI @ latest release
!pip install --upgrade google-meridian[colab,and-cuda,scenarioplanner,schema]

import contextlib
import dataclasses
import time
from typing import Any, Mapping, Optional, Sequence
import altair as alt
import arviz as az
from google.colab import auth
from IPython.display import HTML
from meridian import backend
from meridian import constants
from meridian.analysis import analyzer
from meridian.analysis import optimizer
from meridian.analysis import tensors
from meridian.analysis import visualizer
from meridian.analysis.review import reviewer
from meridian.data import load
from meridian.model import equations
from meridian.model import model
from meridian.model import spec
from meridian.schema.processors import budget_optimization_processor
from meridian.schema.processors import marketing_processor
from meridian.schema.processors import model_fit_processor
from meridian.schema.processors import model_processor
from meridian.schema.utils import date_range_bucketing
from meridian.templates import formatter
import numpy as np
import pandas as pd
from scenarioplanner import mmm_ui_proto_generator as mmm_ui_gen
from scenarioplanner.converters import sheets
from scenarioplanner.converters.dataframe import dataframe_model_converter
from scenarioplanner.linkingapi import url_generator
import tensorflow as tf

# Verify GPU availability
print(
    "Num GPUs Available: ",
    len(tf.config.experimental.list_physical_devices("GPU")),
)
Collecting google-meridian[and-cuda,colab,scenarioplanner,schema]
  Downloading google_meridian-2.1.0-py3-none-any.whl.metadata (10 kB)
Collecting arviz<0.20.0 (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading arviz-0.19.0-py3-none-any.whl.metadata (8.9 kB)
Requirement already satisfied: altair>=5 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (5.5.0)
Requirement already satisfied: babel in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.18.0)
Collecting bidict (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading bidict-0.24.1-py3-none-any.whl.metadata (8.7 kB)
Requirement already satisfied: immutabledict in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.3.1)
Requirement already satisfied: jax<1.0.0,>=0.7.2 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.11.1)
Requirement already satisfied: jaxlib<1.0.0,>=0.7.2 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.11.1)
Requirement already satisfied: joblib in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.6.0)
Requirement already satisfied: matplotlib<3.11.0 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.10.0)
Collecting natsort<8,>=7.1.1 (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading natsort-7.1.1-py3-none-any.whl.metadata (22 kB)
Collecting numpy<2.4.0,>=2.0.2 (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (62 kB)
Requirement already satisfied: pandas<3,>=2.2.2 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.2.3)
Requirement already satisfied: scipy<2,>=1.13.1 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.16.3)
Collecting statsmodels<0.15,>=0.14.5 (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (9.5 kB)
Requirement already satisfied: tensorflow<2.22,>=2.21.0 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.21.0)
Collecting tfp-nightly==0.26.0.dev20260130 (from tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading tfp_nightly-0.26.0.dev20260130-py2.py3-none-any.whl.metadata (13 kB)
Requirement already satisfied: tf-keras<2.22,>=2.21.0 in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.21.0)
Requirement already satisfied: xarray in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2025.12.0)
Requirement already satisfied: psutil in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (5.9.5)
Collecting python-calamine (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading python_calamine-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.1 kB)
Collecting mmm-proto-schema>=1.4.0 (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading mmm_proto_schema-1.6.0-py3-none-any.whl.metadata (1.7 kB)
Collecting semver (from google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading semver-3.1.0-py3-none-any.whl.metadata (6.9 kB)
Requirement already satisfied: google-api-python-client in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.201.0)
Requirement already satisfied: google-auth in /usr/local/lib/python3.13/dist-packages (from google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.49.0)
Requirement already satisfied: absl-py in /usr/local/lib/python3.13/dist-packages (from tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.0.0)
Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.13/dist-packages (from tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.17.0)
Requirement already satisfied: decorator in /usr/local/lib/python3.13/dist-packages (from tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.4.2)
Requirement already satisfied: cloudpickle>=1.3 in /usr/local/lib/python3.13/dist-packages (from tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.1.2)
Requirement already satisfied: gast>=0.3.2 in /usr/local/lib/python3.13/dist-packages (from tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.7.0)
Requirement already satisfied: dm-tree in /usr/local/lib/python3.13/dist-packages (from tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.1.10)
WARNING: tfp-nightly 0.26.0.dev20260130 does not provide the extra 'substrates-jax'
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.13/dist-packages (from arviz<0.20.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (80.10.2)
Requirement already satisfied: packaging in /usr/local/lib/python3.13/dist-packages (from arviz<0.20.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (26.3)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.13/dist-packages (from arviz<0.20.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.8.2)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.13/dist-packages (from arviz<0.20.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.16.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.13/dist-packages (from arviz<0.20.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.11.0)
Requirement already satisfied: ml_dtypes>=0.5.0 in /usr/local/lib/python3.13/dist-packages (from jax<1.0.0,>=0.7.2->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.6.0)
Requirement already satisfied: opt_einsum in /usr/local/lib/python3.13/dist-packages (from jax<1.0.0,>=0.7.2->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.4.0)
Collecting jax-cuda12-plugin<=0.11.1,>=0.11.1 (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading jax_cuda12_plugin-0.11.1-cp313-cp313-manylinux_2_27_x86_64.whl.metadata (2.0 kB)
Collecting jax-cuda12-pjrt==0.11.1 (from jax-cuda12-plugin<=0.11.1,>=0.11.1->jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading jax_cuda12_pjrt-0.11.1-py3-none-manylinux_2_27_x86_64.whl.metadata (580 bytes)
Requirement already satisfied: nvidia-cublas-cu12>=12.1.3.1 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.9.2.10)
Requirement already satisfied: nvidia-cuda-cupti-cu12>=12.1.105 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.9.79)
Requirement already satisfied: nvidia-cuda-nvcc-cu12>=12.6.85 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.9.86)
Requirement already satisfied: nvidia-cuda-runtime-cu12>=12.1.105 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.9.79)
Requirement already satisfied: nvidia-cudnn-cu12<10.0,>=9.8 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (9.27.0.42)
Requirement already satisfied: nvidia-cufft-cu12>=11.0.2.54 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (11.4.1.4)
Requirement already satisfied: nvidia-cusolver-cu12>=11.4.5.107 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (11.7.5.82)
Requirement already satisfied: nvidia-cusparse-cu12>=12.1.0.106 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.5.10.65)
Requirement already satisfied: nvidia-nccl-cu12>=2.18.1 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.32.3)
Requirement already satisfied: nvidia-nvjitlink-cu12>=12.1.105 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.9.86)
Requirement already satisfied: nvidia-cuda-nvrtc-cu12>=12.1.55 in /usr/local/lib/python3.13/dist-packages (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (12.9.86)
Collecting nvidia-nvshmem-cu12>=3.2.5 (from jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading nvidia_nvshmem_cu12-3.8.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.2 kB)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.4.0)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.68.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.5.1)
Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (11.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.3.3)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.13/dist-packages (from matplotlib<3.11.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.13/dist-packages (from pandas<3,>=2.2.2->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.13/dist-packages (from pandas<3,>=2.2.2->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2026.6)
Requirement already satisfied: patsy>=0.5.6 in /usr/local/lib/python3.13/dist-packages (from statsmodels<0.15,>=0.14.5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.0.3)
Requirement already satisfied: astunparse>=1.6.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.6.3)
Requirement already satisfied: flatbuffers>=25.9.23 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (25.12.19)
Requirement already satisfied: google_pasta>=0.1.1 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.2.0)
Requirement already satisfied: libclang>=13.0.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (18.1.1)
Requirement already satisfied: protobuf<8.0.0,>=6.31.1 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (6.33.6)
Requirement already satisfied: requests<3,>=2.21.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.32.4)
Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.3.0)
Requirement already satisfied: wrapt>=1.11.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.5.1)
Requirement already satisfied: grpcio<2.0,>=1.24.3 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.85.0)
Requirement already satisfied: keras>=3.12.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.13.2)
Requirement already satisfied: h5py<3.15.0,>=3.11.0 in /usr/local/lib/python3.13/dist-packages (from tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.14.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.13/dist-packages (from requests<3,>=2.21.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.4.9)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.13/dist-packages (from requests<3,>=2.21.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.20)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.13/dist-packages (from requests<3,>=2.21.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.5.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.13/dist-packages (from requests<3,>=2.21.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2026.10.23)
Requirement already satisfied: nvidia-curand-cu12<11.0,>=10.3.6.82 in /usr/local/lib/python3.13/dist-packages (from tensorflow[and-cuda]<2.22,>=2.21.0; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema]) (10.3.10.19)
Requirement already satisfied: jinja2 in /usr/local/lib/python3.13/dist-packages (from altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.1.6)
Requirement already satisfied: jsonschema>=3.0 in /usr/local/lib/python3.13/dist-packages (from altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.26.0)
Requirement already satisfied: narwhals>=1.14.2 in /usr/local/lib/python3.13/dist-packages (from altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.28.0)
Requirement already satisfied: wheel<1.0,>=0.23.0 in /usr/local/lib/python3.13/dist-packages (from astunparse>=1.6.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.48.0)
Requirement already satisfied: attrs>=18.2.0 in /usr/local/lib/python3.13/dist-packages (from dm-tree->tfp-nightly==0.26.0.dev20260130->tfp-nightly[substrates-jax]==0.26.0.dev20260130->google-meridian[and-cuda,colab,scenarioplanner,schema]) (26.1.0)
Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.13/dist-packages (from jsonschema>=3.0->altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2025.9.1)
Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.13/dist-packages (from jsonschema>=3.0->altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.37.0)
Requirement already satisfied: rpds-py>=0.25.0 in /usr/local/lib/python3.13/dist-packages (from jsonschema>=3.0->altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2026.9.1)
Requirement already satisfied: rich in /usr/local/lib/python3.13/dist-packages (from keras>=3.12.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (13.9.4)
Requirement already satisfied: namex in /usr/local/lib/python3.13/dist-packages (from keras>=3.12.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.1.0)
Requirement already satisfied: optree in /usr/local/lib/python3.13/dist-packages (from keras>=3.12.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.20.0)
Requirement already satisfied: googleapis-common-protos in /usr/local/lib/python3.13/dist-packages (from mmm-proto-schema>=1.4.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.76.0)
Collecting nvidia-cuda-cccl-cu12 (from nvidia-nvshmem-cu12>=3.2.5->jax-cuda12-plugin[with-cuda]<=0.11.1,>=0.11.1; extra == "cuda12"->jax[cuda12]<1.0.0,>=0.7.2; extra == "and-cuda"->google-meridian[and-cuda,colab,scenarioplanner,schema])
  Downloading nvidia_cuda_cccl_cu12-12.9.27-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (1.7 kB)
Requirement already satisfied: httplib2<1.0.0,>=0.19.0 in /usr/local/lib/python3.13/dist-packages (from google-api-python-client->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.32.1)
Requirement already satisfied: google-auth-httplib2<1.0.0,>=0.2.0 in /usr/local/lib/python3.13/dist-packages (from google-api-python-client->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.4.4)
Requirement already satisfied: google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0,>=1.31.5 in /usr/local/lib/python3.13/dist-packages (from google-api-python-client->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.30.3)
Requirement already satisfied: uritemplate<5,>=3.0.1 in /usr/local/lib/python3.13/dist-packages (from google-api-python-client->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.2.0)
Requirement already satisfied: proto-plus<2.0.0,>=1.22.3 in /usr/local/lib/python3.13/dist-packages (from google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0,>=1.31.5->google-api-python-client->google-meridian[and-cuda,colab,scenarioplanner,schema]) (1.29.0)
Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.13/dist-packages (from google-auth->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.4.2)
Requirement already satisfied: cryptography>=38.0.3 in /usr/local/lib/python3.13/dist-packages (from google-auth->google-meridian[and-cuda,colab,scenarioplanner,schema]) (50.0.2)
Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/lib/python3.13/dist-packages (from google-auth->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.9.1)
Requirement already satisfied: pyasn1>=0.1.3 in /usr/local/lib/python3.13/dist-packages (from rsa<5,>=3.1.4->google-auth->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.6.5)
Requirement already satisfied: cffi>=2.0.0 in /usr/local/lib/python3.13/dist-packages (from cryptography>=38.0.3->google-auth->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.1.1)
Requirement already satisfied: pycparser in /usr/local/lib/python3.13/dist-packages (from cffi>=2.0.0->cryptography>=38.0.3->google-auth->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.12)
Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.13/dist-packages (from jinja2->altair>=5->google-meridian[and-cuda,colab,scenarioplanner,schema]) (3.0.4)
Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.13/dist-packages (from rich->keras>=3.12.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (4.2.0)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.13/dist-packages (from rich->keras>=3.12.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (2.21.0)
Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.13/dist-packages (from markdown-it-py>=2.2.0->rich->keras>=3.12.0->tensorflow<2.22,>=2.21.0->google-meridian[and-cuda,colab,scenarioplanner,schema]) (0.1.2)
Downloading google_meridian-2.1.0-py3-none-any.whl (635 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 635.9/635.9 kB 7.8 MB/s  0:00:00
Downloading tfp_nightly-0.26.0.dev20260130-py2.py3-none-any.whl (7.0 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.0/7.0 MB 43.7 MB/s  0:00:00
Downloading arviz-0.19.0-py3-none-any.whl (1.7 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 28.3 MB/s  0:00:00
Downloading jax_cuda12_plugin-0.11.1-cp313-cp313-manylinux_2_27_x86_64.whl (8.6 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.6/8.6 MB 40.7 MB/s  0:00:00
Downloading jax_cuda12_pjrt-0.11.1-py3-none-manylinux_2_27_x86_64.whl (184.9 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 184.9/184.9 MB 53.5 MB/s  0:00:03
Downloading natsort-7.1.1-py3-none-any.whl (35 kB)
Downloading numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (16.6 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.6/16.6 MB 91.5 MB/s  0:00:00
Downloading statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (10.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 10.3/10.3 MB 85.6 MB/s  0:00:00
Downloading mmm_proto_schema-1.6.0-py3-none-any.whl (76 kB)
Downloading nvidia_nvshmem_cu12-3.8.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (280.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 280.8/280.8 MB 30.3 MB/s  0:00:08
Downloading bidict-0.24.1-py3-none-any.whl (36 kB)
Downloading nvidia_cuda_cccl_cu12-12.9.27-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (3.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.2/3.2 MB 35.1 MB/s  0:00:00
Downloading python_calamine-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (934 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 935.0/935.0 kB 27.4 MB/s  0:00:00
Downloading semver-3.1.0-py3-none-any.whl (18 kB)
Installing collected packages: jax-cuda12-pjrt, semver, python-calamine, nvidia-cuda-cccl-cu12, numpy, natsort, jax-cuda12-plugin, bidict, nvidia-nvshmem-cu12, tfp-nightly, statsmodels, mmm-proto-schema, arviz, google-meridian
  Attempting uninstall: numpy
    Found existing installation: numpy 2.4.6
    Uninstalling numpy-2.4.6:
      Successfully uninstalled numpy-2.4.6
  Attempting uninstall: natsort
    Found existing installation: natsort 8.4.0
    Uninstalling natsort-8.4.0:
      Successfully uninstalled natsort-8.4.0
  Attempting uninstall: statsmodels
    Found existing installation: statsmodels 0.15.0
    Uninstalling statsmodels-0.15.0:
      Successfully uninstalled statsmodels-0.15.0
  Attempting uninstall: arviz
    Found existing installation: arviz 0.22.0
    Uninstalling arviz-0.22.0:
      Successfully uninstalled arviz-0.22.0
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14/14 [google-meridian]
Successfully installed arviz-0.19.0 bidict-0.24.1 google-meridian-2.1.0 jax-cuda12-pjrt-0.11.1 jax-cuda12-plugin-0.11.1 mmm-proto-schema-1.6.0 natsort-7.1.1 numpy-2.3.5 nvidia-cuda-cccl-cu12-12.9.27 nvidia-nvshmem-cu12-3.8.0 python-calamine-0.9.0 semver-3.1.0 statsmodels-0.14.6 tfp-nightly-0.26.0.dev20260130
Num GPUs Available:  1

Step 1: Load and Inspect Simulated Full-Funnel Data

We load a simulated dataset (geo_full_funnel_data.csv) that models the full-funnel marketing environment:

  • Geos and Time: DMA-level weekly data across geos, times, and population.
  • Paid Media Channels:
    • AwarenessVideo (impressions and spend): Brand awareness channel.
    • PerformanceNative (impressions and spend): Performance conversion channel.
    • PerformanceDisplay (impressions and spend): Performance conversion channel.
  • KPI: conversions and revenue_per_conversion.
  • Controls & Mediators:
    • GenericGQV_control: Confounder affecting all media channels and conversions.
    • BrandedGQV_control: Mediator driven by AwarenessVideo and confounding performance channels.

Causal Relationships in the Data:

The simulated data is generated with the following relationships:

DAG

We see that BrandedGQV is a mediator for AwarenessVideo, but a confounder for PerformanceNative and PerformanceDisplay. This DAG is a special case of the full-funnel causal graph, making full-funnel MMM appropriate.

# Load dataset
DATA_URL = "https://raw.githubusercontent.com/google/meridian/refs/heads/main/meridian/data/simulated_data/csv/geo_full_funnel_data.csv"
df = pd.read_csv(DATA_URL)

HTML(f'<div style="overflow-x: auto;">{df.head().to_html()}</div>')

Step 2: Fitting the Full-Funnel Models

We now fit the two-stage model:

  1. Stage 1 (mmm1s): Models BrandedGQV as the response variable driven by AwarenessVideo.
  2. Stage 2 (mmm2s): Models conversions as the final KPI driven by all media channels. It includes GenericGQV as a control, and BrandedGQV as an organic_media channel with saturation_spec={'BrandedGQV_control': 'none'}.

Each of the Stage 1 and Stage 2 models are Meridian models and instantiations of the Meridian class.

# Shared sampling configuration.
SAMPLING_KWARGS = dict(
    n_chains=4,
    n_adapt=1000,
    n_burnin=1000,
    n_keep=500,
)

Stage 1 MMM: Brand Marketing → Brand Equity

Just like a single-stage Meridian model, each Meridian model in this two-stage setup needs all confounders between its treatments and the response variable.

In the Stage 1 model, we model the causal effect of channels expected to cause brand equity ("Brand marketing") on the brand equity variable itself. To model this causal effect, we must include confounding variables between Brand marketing and the Brand equity variable. In this illustrative example, we do not simulate such a variable for simplicity.

stage1_media = ["AwarenessVideo_impression"]
stage1_media_spend = ["AwarenessVideo_spend"]
loader1s = load.DataFrameDataLoader(
    df,
    kpi_type="non_revenue",
    # Notice that `revenue_per_kpi` argument is not used.
    coord_to_columns=load.CoordToColumns(
        time="time",
        geo="geo",
        population="population",
        kpi="BrandedGQV_control",
        media=stage1_media,
        media_spend=stage1_media_spend,
    ),
    media_to_channel={c: c.replace("_impression", "") for c in stage1_media},
    media_spend_to_channel={
        c: c.replace("_spend", "") for c in stage1_media_spend
    },
)

mmm1s = model.Meridian(
    input_data=loader1s.load(),
    model_spec=spec.ModelSpec(
        # One knot per month
        knots=36,
    ),
)

mmm1s.sample_prior(SAMPLING_KWARGS["n_keep"])
mmm1s.sample_posterior(**SAMPLING_KWARGS)
/usr/local/lib/python3.13/dist-packages/meridian/data/input_data.py:605: UserWarning: Consider setting custom priors, as kpi_type was specified as `non_revenue` with no `revenue_per_kpi` being set. Otherwise, the total media contribution prior will be used with `p_mean=0.4` and `p_sd=0.2`. Further documentation available at https://developers.google.com/meridian/docs/advanced-modeling/unknown-revenue-kpi-custom#set-total-paid-media-contribution-prior
  warnings.warn(
ERROR:jax._src.xla_bridge:Jax plugin configuration error: Exception when calling jax_plugins.xla_cuda13.initialize()
Traceback (most recent call last):
  File "/usr/local/lib/python3.13/dist-packages/jax/_src/xla_bridge.py", line 495, in discover_pjrt_plugins
    plugin_module.initialize()
    ~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.13/dist-packages/jax_plugins/xla_cuda13/__init__.py", line 373, in initialize
    c_api = xb.register_plugin(
        'cuda', priority=500, library_path=str(path), options=options
    )
  File "/usr/local/lib/python3.13/dist-packages/jax/_src/xla_bridge.py", line 637, in register_plugin
    c_api = xla_client.load_pjrt_plugin_dynamically(plugin_name, library_path)
  File "/usr/local/lib/python3.13/dist-packages/jaxlib/xla_client.py", line 115, in load_pjrt_plugin_dynamically
    return _xla.load_pjrt_plugin(plugin_name, library_path, c_api=None)
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
jax.errors.JaxRuntimeError: ALREADY_EXISTS: PJRT_Api already exists for device type cuda
/usr/local/lib/python3.13/dist-packages/meridian/model/context.py:1447: UserWarning: Consider setting custom ROI priors, as kpi_type was specified as `non_revenue` with no `revenue_per_kpi` being set. Otherwise, the total media contribution prior will be used with `p_mean=0.4` and `p_sd=0.2`. Further documentation available at  https://developers.google.com/meridian/docs/advanced-modeling/unknown-revenue-kpi-custom#set-total-paid-media-contribution-prior
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/arviz/data/inference_data.py:157: UserWarning: trace group is not defined in the InferenceData scheme
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/arviz/data/inference_data.py:1647: UserWarning: trace group is not defined in the InferenceData scheme
  warnings.warn(

Stage 2 MMM: Marketing → KPI

In the Stage 2 model, we model the causal effect of all marketing treatment variables and the brand equity variable on the KPI. In this second stage, the brand equity variable is also a treatment variable (modeled as organic media). This allows us to measure the incremental effect of brand marketing on the KPI via brand equity. Like a single-stage MMM, we must include any confounding variables between our treatment variables and the KPI.

stage2_media = [
    "PerformanceNative_impression",
    "PerformanceDisplay_impression",
    "AwarenessVideo_impression",
]
stage2_media_spend = [
    "PerformanceNative_spend",
    "PerformanceDisplay_spend",
    "AwarenessVideo_spend",
]

loader2s = load.DataFrameDataLoader(
    df,
    kpi_type="non_revenue",
    coord_to_columns=load.CoordToColumns(
        time="time",
        geo="geo",
        population="population",
        kpi="conversions",
        revenue_per_kpi="revenue_per_conversion",
        media=stage2_media,
        media_spend=stage2_media_spend,
        controls=["GenericGQV_control"],
        organic_media=["BrandedGQV_control"],
    ),
    media_to_channel={c: c.replace("_impression", "") for c in stage2_media},
    media_spend_to_channel={
        c: c.replace("_spend", "") for c in stage2_media_spend
    },
)

mmm2s = model.Meridian(
    input_data=loader2s.load(),
    model_spec=spec.ModelSpec(
        # One knot per month
        knots=36,
        # Required for the brand equity variable
        saturation_spec={"BrandedGQV_control": "none"},
        # Controls are not population-scaled by default. Meridian recommends
        # population scaling GQV.
        population_scaled_controls=["GenericGQV_control"],
    ),
)

mmm2s.sample_prior(SAMPLING_KWARGS["n_keep"])
mmm2s.sample_posterior(**SAMPLING_KWARGS)
/usr/local/lib/python3.13/dist-packages/arviz/data/inference_data.py:157: UserWarning: trace group is not defined in the InferenceData scheme
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/arviz/data/inference_data.py:1647: UserWarning: trace group is not defined in the InferenceData scheme
  warnings.warn(

Model Health Diagnostics

Before proceeding to combined post-modeling analysis, we verify that both MCMC models are converged and healthy (health scores \(> 70\)). A comprehensive negative baseline check that accounts for the expected outcome of the full-funnel model will be presented in Step 5.

# Diagnostic checks for Stage 1 Model
reviewer.ModelReviewer(mmm1s).run()
/tmp/ipykernel_4566/2495845249.py:2: DeprecationWarning: The `meridian` argument is deprecated. Please use `model_context` and `inference_data` instead.
  reviewer.ModelReviewer(mmm1s).run()
/usr/local/lib/python3.13/dist-packages/meridian/analysis/analyzer.py:375: UserWarning: Revenue analysis is not available when `revenue_per_kpi` is unknown. Defaulting to KPI analysis.
  warnings.warn(
========================================
Model Quality Checks
========================================
Overall Status: PASS
Summary: Passed: No major quality issues were identified.
Health Score: 96.0

Check Results:
----------------------------------------
Convergence Check:
  Status: PASS
  Recommendation: The model has likely converged, as all parameters have R-hat values < 1.2.
----------------------------------------
Baseline Check:
  Status: PASS
  Recommendation: The posterior probability that the baseline is negative is 0.00. We recommend visually inspecting the baseline time series in the Model Fit charts to confirm this.
----------------------------------------
BayesianPPP Check:
  Status: PASS
  Recommendation: The Bayesian posterior predictive p-value is 0.94. The observed total outcome is consistent with the model's posterior predictive distribution.
----------------------------------------
GoodnessOfFit Check:
  Status: PASS
  Recommendation: R-squared = 0.5435, MAPE = 0.0499, and wMAPE = 0.0497. These goodness-of-fit metrics are intended for guidance and relative comparison.
----------------------------------------
PriorPosteriorShift Check:
  Status: PASS
  Recommendation: The model has successfully learned from the data. This is a positive sign that your data was informative.
reviewer.ModelReviewer(mmm2s).run()
/tmp/ipykernel_4566/4250511027.py:1: DeprecationWarning: The `meridian` argument is deprecated. Please use `model_context` and `inference_data` instead.
  reviewer.ModelReviewer(mmm2s).run()
/usr/local/lib/python3.13/dist-packages/altair/utils/core.py:264: UserWarning: I don't know how to infer vegalite type from 'empty'.  Defaulting to nominal.
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/altair/utils/core.py:264: UserWarning: I don't know how to infer vegalite type from 'empty'.  Defaulting to nominal.
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/altair/utils/core.py:264: UserWarning: I don't know how to infer vegalite type from 'empty'.  Defaulting to nominal.
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/altair/utils/core.py:264: UserWarning: I don't know how to infer vegalite type from 'empty'.  Defaulting to nominal.
  warnings.warn(
/usr/local/lib/python3.13/dist-packages/meridian/analysis/review/plots.py:588: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
  df[constants.IS_MAX] = df[constants.IS_MAX].fillna(False)
========================================
Model Quality Checks
========================================
Overall Status: PASS
Summary: Passed: No major quality issues were identified.
Health Score: 92.8

Check Results:
----------------------------------------
Convergence Check:
  Status: PASS
  Recommendation: The model has likely converged, as all parameters have R-hat values < 1.2.
----------------------------------------
Baseline Check:
  Status: PASS
  Recommendation: The posterior probability that the baseline is negative is 0.00. We recommend visually inspecting the baseline time series in the Model Fit charts to confirm this.
----------------------------------------
BayesianPPP Check:
  Status: PASS
  Recommendation: The Bayesian posterior predictive p-value is 0.99. The observed total outcome is consistent with the model's posterior predictive distribution.
----------------------------------------
GoodnessOfFit Check:
  Status: PASS
  Recommendation: R-squared = 0.4693, MAPE = 0.0563, and wMAPE = 0.0555. These goodness-of-fit metrics are intended for guidance and relative comparison.
----------------------------------------
PriorPosteriorShift Check:
  Status: PASS
  Recommendation: The model has successfully learned from the data. This is a positive sign that your data was informative.

===================================================================================================================
Channel Calibration Recommendation
===================================================================================================================
Channel              | Calibration Score  | High ROI        | Low ROI         | High Variance ROI | Potential Bias
-------------------------------------------------------------------------------------------------------------------
PerformanceNative    | 91.2               | Non-Driver      | Non-Driver      | Non-Driver        | Non-Driver    
PerformanceDisplay   | 84.9               | Non-Driver      | Non-Driver      | Non-Driver        | Non-Driver    
AwarenessVideo       | 90.3               | Non-Driver      | Non-Driver      | Non-Driver        | Non-Driver    
-------------------------------------------------------------------------------------------------------------------

Before analyzing total downstream conversions, evaluate the efficiency of brand advertising (AwarenessVideo) in driving brand search query volume (BrandedGQV_control) in the Stage 1 model (mmm1s).

Because the Stage 1 outcome (BrandedGQV_control) is a non-revenue metric, evaluate physical query units by passing use_kpi=True. This section examines the following:

  • Response curves: diminishing marginal returns and Hill saturation of brand ad spend on query volume.
  • Cost per incremental query (CPIK): cost-efficiency of incremental query generation with credible intervals.
  • Search volume decomposition: contribution of organic baseline demand versus advertising lift.
# 1. Response Curves on Search Volume
visualizer.MediaEffects(meridian=mmm1s).plot_response_curves()
/usr/local/lib/python3.13/dist-packages/meridian/analysis/analyzer.py:375: UserWarning: Revenue analysis is not available when `revenue_per_kpi` is unknown. Defaulting to KPI analysis.
  warnings.warn(
# 2. Cost Per Incremental Query (CPIK)
visualizer.MediaSummary(meridian=mmm1s).plot_cpik(include_ci=True)
/usr/local/lib/python3.13/dist-packages/meridian/analysis/analyzer.py:375: UserWarning: Revenue analysis is not available when `revenue_per_kpi` is unknown. Defaulting to KPI analysis.
  warnings.warn(
# 3. Search Volume Decomposition Waterfall Chart
visualizer.MediaSummary(
    meridian=mmm1s
).plot_contribution_waterfall_chart()
/usr/local/lib/python3.13/dist-packages/meridian/analysis/analyzer.py:375: UserWarning: Revenue analysis is not available when `revenue_per_kpi` is unknown. Defaulting to KPI analysis.
  warnings.warn(

Step 4: Custom AnalyzerFullFunnel Definition

This step defines class a AnalyzerFullFunnel class which extends Meridian's Analyzer class for full-funnel inference. It combines the stage 1 and stage 2 models allowing for the stage 1 model to correct for the bias induced in stage 2 by included the mediating brand equity variable. It does the following,

  • Incremental Outcome: Combines the effect estimated in the second model with the indirect effect through the brand equity variable.
  • Expected Outcome: Computes expected outcomes across both stages.
  • Model Compatibility Verification: Enforces matching geos, timepoints, MCMC trace lengths, and verifies that the brand equity variable is specified in Stage 2 as an organic_media variable with saturation_spec='none'.

Since downstream causal estimates, including ROI, marginal ROI, response curves, baseline, and budget optimization, rely on these, they are all updated for full-funnel MMM.

Step 5: Post-Modeling Analysis with AnalyzerFullFunnel

We instantiate the AnalyzerFullFunnel class which is fully compatible with Meridian's downstream optimization, scenario planning, and visualization modules. It enables lots of post-modeling analysis and it can be passed to the MediaEffects and BudgetOptimizer classes. Here are some examples of the analysis you can do while accounting for the full effect according to the full-funnel model:

  1. ROI estimation
  2. Marginal ROI estimation
  3. Response curve estimation and visualization
  4. Budget optimization and visualization
  5. Negative baseline checks

We will also plot the following full-funnel specific visualizations:

  • ROI decomposed into indirect and remaining effects
  • Compounded adstock decay curves
# Initialize AnalyzerFullFunnel
analyzer_full_funnel = AnalyzerFullFunnel(
    # Second stage goes here
    meridian=mmm2s,
    # First stage model goes here
    mediator_models={"BrandedGQV_control": mmm1s},
)

# Check the negative baseline probability
prob = analyzer_full_funnel.negative_baseline_probability()
print(f"Negative baseline probability: {prob:.4f}")
/tmp/ipykernel_4566/1719713641.py:112: DeprecationWarning: The `meridian` argument is deprecated and will be removed in a future version. Use `model_context` instead.
  super().__init__(meridian=meridian, inference_data=inference_data)
/tmp/ipykernel_4566/1719713641.py:120: DeprecationWarning: The `meridian` argument is deprecated and will be removed in a future version. Use `model_context` instead.
  name: analyzer.Analyzer(
Negative baseline probability: 0.0000

Full-Funnel MMM Results:

  • AwarenessVideo: Includes its indirect effect via BrandedGQV, estimating the full ROI.
  • PerformanceNative & PerformanceDisplay: Protected from confounding bias by properly conditioning on BrandedGQV in the second stage model.
# Plot Media Response Curves
media_effects = visualizer.MediaEffects(analyzer=analyzer_full_funnel)
media_effects.plot_response_curves()
# Run Fixed Budget Optimization
budget_optimizer = optimizer.BudgetOptimizer(analyzer=analyzer_full_funnel)
fixed_optimizer = budget_optimizer.optimize()

# Plot Incremental Outcome Delta
fixed_optimizer.plot_spend_delta()
fixed_optimizer.plot_incremental_outcome_delta()

Custom functions for ROI decomposed into indirect and remaining effects

\[\text{Total Effect} = \text{Indirect Effect} + \text{Remaining Effect}\]

  • Indirect effect: The impact of brand marketing in Stage 1 propagated through the brand equity mediator (BrandedGQV) into final conversions.
  • Remaining effect: The effect estimated directly in the primary Stage 2 model after controlling for the brand equity mediator.

These custom functions let you compute and visualize how total ROI estimates decompose into each component.

Custom functions to visualize compounded adstock decay

In a full-funnel model, upper-funnel channels are affected by adstock decay in both models (for more information, see Compounded adstock decay in full-funnel MMM).

To visualize the effect of compounded adstock decay, simulate incremental KPI for a counterfactual scenario with non-zero paid and organic media at a single time point (time 0). This lets you understand how the incremental outcome decays over time. To minimize the impact of saturation, media at time 0 is set to 1% of the average weekly value.

Step 6: Meridian Scenario Planner (MSP)

Export the full-funnel model and scenario planning optimization results to Google Sheets to populate the interactive Looker Studio Meridian Scenario Planner dashboard.