Mmm modeling
Skill afelipeg/Anthropic-Skills-for-enterprise-marketing-os/skills/mmm-modeling
Bayesian Media Mix Modeling with PyMC-Marketing + custom fallback. Acid-test validation (Holt-Winters, Granger, VIF), industry-calibrated priors, trade marketing decomposition, Meridian scenario planning, budget optimization. TRIGGERS: MMM, marketing mix model, media mix, ROI, marketing attribution, budget optimization, channel contribution, adstock, saturation curve, incrementality, ROAS, scenario planning, lift test, sensitivity analysis, marginal ROI, diminishing returns, media-driven sales, trade marketing Use when: analyzing channel ROI, optimizing budgets, measuring incremental contribution, building saturation curves, scenario planning, decomposing KPI drivers (base+media+trade), calibrating with lift tests, geo-hierarchical modeling, validating MMM integrityFrom its SKILL.md
npx -y skills add afelipeg/Anthropic-Skills-for-enterprise-marketing-os --skill mmm-modelingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 2 commands, including `python scripts/data_validator.py data.csv` and 1 more.
SKILL.md
17.7 KB, ~4.1k tokens by cl100k_base, as published. Nobody here has run it
Media Mix Modeling (MMM) Skill
Bayesian Media Mix Modeling using PyMC-Marketing (primary) with a custom scipy-based fallback for restricted environments. Includes Meridian-inspired scenario planning patterns for forward-looking budget optimization.
Architecture
| Layer | Engine | When |
|---|---|---|
| Primary | pymc-marketing MMM class | Default. Full Bayesian MCMC, geo-hierarchical, lift calibration, HSGP TVP |
| Fallback | Custom scipy (scripts/) | When pymc-marketing unavailable. MLE + bootstrap CIs |
| Scenario | Adapted from Meridian patterns | Budget sweeps, fixed/flexible optimization, flighting |
Detection logic — try import pymc_marketing first; if ImportError, fall back to custom scripts.
Agency Growth OS Integration
This skill operates within the 17-skill Agency Growth OS. Its position in the three cycles:
Execution Cycle
media-routing-planner → mmm-modeling → measurement-incrementality
↘ budget_optimization → media-routing-planner (re-optimize)
Intelligence Cycle
weekly-control-tower → performance-diagnosis → mmm-modeling (if root cause is channel mix)
mmm-modeling → qbr-generator (quarterly decomposition + scenario slides)
mmm-modeling → client-memory-synthesizer (store model parameters + integrity score)
Chain Routing
After mmm-modeling completes, suggest the next skill via sendPrompt():
| Output Produced | Suggest Next |
|---|---|
| Channel ROAS + contribution share | measurement-incrementality (validate claims) |
| Budget optimization allocation | media-routing-planner (implement in platform) |
| Integrity report with flags | performance-diagnosis (investigate anomalies) |
| Scenario planning deck | qbr-generator (embed in quarterly review) |
| Context brief + model params | client-memory-synthesizer (persist to tenant) |
Data Ingestion
Digital media data → Adspirer MCP (automated)
When the model needs digital campaign spend data (Google Ads, Meta, LinkedIn, TikTok), call the Adspirer MCP tools already connected:
# Claude calls Adspirer tools to pull spend data:
# - get_campaign_performance → spend by channel × date
# - get_campaign_structure → channel taxonomy
# - analyze_search_terms → search query volume (control variable)
Use tool_search for Adspirer tools when the user mentions digital spend data. The skill does NOT embed Adspirer tool calls directly — Claude resolves them at runtime via the connected MCP.
Non-digital data → Human batch upload (CSV/Excel)
Data the human must provide (no MCP available):
| Data Type | Format | Used As |
|---|---|---|
| Sales / KPI | CSV with date + geo + value | Target variable (y) |
| TV / Radio / OOH spend | CSV with date + channel + value | Media channels |
| Trade marketing (promo, price, ACV) | CSV/Excel | Control + trade decomposition |
| Lift test results | CSV (channel, x, delta_x, delta_y, sigma) | Calibration |
| Distribution / sell-out (Nielsen/Kantar) | CSV/Excel | Control variables |
Workflow: Human uploads → data_validator.py validates → context layer resolves priors → model fits.
Visualization Strategy
This skill ALWAYS produces visual deliverables. The strategy controls WHERE they render to avoid crashing the app.
In-chat: Tables + lightweight SVG (always)
Every MMM run produces formatted tables in chat as immediate output:
- Channel ROAS with HDI ranges (table)
- Integrity scorecard pass/warn/fail (table)
- Contribution share % breakdown (table)
- Scenario comparison (table)
For visual charts in chat, use the Visualizer with lightweight SVG only:
- Max 6 channels × 50 data points per chart
- No animations, no JS interactivity
- Keep total SVG under 50KB
- One chart per Visualizer call (never stack multiple)
Suitable for inline SVG:
- Horizontal bar: contribution waterfall, ROAS comparison
- Line chart: saturation curves (≤6 lines), efficient frontier
- Donut: four-way decomposition (base/media/trade/interaction)
- Status indicators: integrity scorecard with color-coded pass/warn/fail
File deliverables: Full charts + dashboards (always generated)
After in-chat summary, ALWAYS generate file artifacts in /mnt/user-data/outputs/:
| File | Content | When |
|---|---|---|
mmm_results.html | Full interactive dashboard: contributions over time, saturation curves, waterfall, scenario sliders | Every MMM run |
integrity_report.html | Acid-test results with expandable detail per test | Every MMM run |
mmm_scenarios.xlsx | Scenario comparison data + allocation tables | When optimization runs |
mmm_executive.pptx | Slides: integrity, decomposition, ROAS, scenarios (via pptx skill) | When user requests deck |
integrity_report.json | Machine-readable results for client BI integration | Every MMM run |
Chart generation in HTML files
HTML dashboards use inline <svg> or lightweight Chart.js (CDN) — NOT matplotlib, NOT plotly, NOT heavy React state. This ensures they open fast in browser without crashing.
<!-- Pattern for HTML dashboard charts -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<canvas id="saturation_chart"></canvas>
<script>
new Chart(document.getElementById('saturation_chart'), {
type: 'line',
data: { /* from mmm results JSON */ },
options: { responsive: true, animation: false }
});
</script>
PPTX charts
Use the pptx skill to embed static chart images in slides. Generate chart as PNG via matplotlib in script → embed in slide. Charts render server-side, no app crash risk.
Quick Start (PyMC-Marketing)
import arviz as az
import numpy as np
import pandas as pd
from pymc_extras.prior import Prior
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_marketing.mmm.multidimensional import MMM
data_df = pd.read_csv("data.csv", parse_dates=["date"])
X = data_df.drop(columns=["y"])
y = data_df["y"]
mmm = MMM(
date_column="date",
channel_columns=["tv", "radio", "social"],
target_column="y",
adstock=GeometricAdstock(l_max=6),
saturation=LogisticSaturation(),
yearly_seasonality=5,
)
# CRITICAL: Always fit on FULL dataset. Train/test splits are ONLY for stability assessment.
mmm.build_model(X, y)
mmm.fit(X=X, y=y, nuts_sampler="nutpie", target_accept=0.9, random_seed=42)
mmm.sample_posterior_predictive(X=X, random_seed=42)
Quick Start (Custom Fallback)
# Validate data
python scripts/data_validator.py data.csv
# Generate report from results JSON
python scripts/report_generator.py model.json --roi roi.json --html
Reference Architecture
| Reference File | Content | Read When |
|---|---|---|
references/model_specification.md | MMM constructor, adstock/saturation, priors, dims, scaling, prior predictive | Specifying a new model |
references/data_analysis.md | EDA patterns, data format, spend shares, long format for geo | Preparing data |
references/model_fit.md | Fitting, diagnostics checklist, TimeSliceCrossValidator, common issues | Fitting and diagnosing |
references/media_deep_dive.md | Contributions, ROAS, saturation curves, sensitivity, incrementality | Post-fit media analysis |
references/budget_optimization.md | MultiDimensionalBudgetOptimizerWrapper, bounds, sweeps, constraints | Optimizing budgets |
references/scenario_planning.md | Meridian-inspired scenario planning adapted for PyMC-Marketing | Forward-looking what-ifs |
references/lift_test_calibration.md | add_lift_test_measurements, data format, sigma estimation | Calibrating with experiments |
references/time_varying_parameters.md | HSGPKwargs, time-varying intercept/media, when to use TVP | Non-stationary effects |
references/custom_model.md | Standalone components with plain PyMC, spline baselines, custom likelihoods | Beyond MMM class |
references/plot_api.md | Complete mmm.plot namespace with exact signatures | Any visualization |
references/diagnostics_benchmarks.md | Convergence thresholds, industry ROI benchmarks, business logic guards | Validating results |
references/acid_test_validation.md | Pre/post-model integrity tests (Holt-Winters, Granger, VIF, permutation) | Validating model truthfulness |
references/context_layer.md | Industry/category/market prior calibration, benchmark resolution | Setting informed priors |
references/trade_marketing_decomposition.md | Trade vs media vs base demand separation, flexible schema | CPG/FMCG/Retail decomposition |
Scripts
| Script | Purpose | Depends On |
|---|---|---|
scripts/data_validator.py | Pre-modeling EDA, quality scoring, synthetic data generation | pandas, numpy, scipy |
scripts/report_generator.py | CoTA-formatted HTML/MD reports with embedded charts | matplotlib (optional) |
scripts/acid_test.py | Model integrity validation (Holt-Winters, Granger, VIF, absorption check) | statsmodels |
scripts/context_resolver.py | Industry benchmark resolution → calibrated priors | json (no deps) |
Model Specification (PyMC-Marketing)
Transformations
GeometricAdstock — exponential decay carryover:
from pymc_marketing.mmm import GeometricAdstock
adstock = GeometricAdstock(l_max=6, normalize=True)
# Default prior: alpha ~ Beta(1, 3) — favors fast decay
# Custom: priors={"alpha": Prior("Beta", alpha=2, beta=5, dims="channel")}
LogisticSaturation — S-shaped diminishing returns:
from pymc_marketing.mmm import LogisticSaturation
saturation = LogisticSaturation()
# Default: lam ~ Gamma(3, 1), beta ~ HalfNormal(2)
# Informed: priors={"beta": Prior("HalfNormal", sigma=spend_shares, dims="channel")}
Full Model Config
from pymc_extras.prior import Prior
model_config = {
"intercept": Prior("Normal", mu=0.2, sigma=0.05),
"saturation_beta": Prior("HalfNormal", sigma=spend_shares, dims="channel"),
"gamma_control": Prior("Normal", mu=0, sigma=1, dims="control"),
"gamma_fourier": Prior("Laplace", mu=0, b=1, dims="fourier_mode"),
"likelihood": Prior("TruncatedNormal", lower=0, sigma=Prior("HalfNormal", sigma=1)),
}
See references/model_specification.md for constructor reference, all saturation alternatives (Hill, MichaelisMenten, Tanh, Root, etc.), hierarchical prior patterns, and scaling config.
Multidimensional (Geo-Hierarchical)
Activate with dims=("geo",). Use partial pooling (default recommendation):
from pymc_marketing.special_priors import LogNormalPrior
model_config = {
"saturation_beta": LogNormalPrior(
mean=Prior("Gamma", mu=1.0, sigma=1.0),
std=Prior("HalfNormal", sigma=1.0),
dims=("channel", "geo"), centered=False,
),
}
Workflow
Client Brief (industry, category, market, channels, KPI)
↓
Context Resolution (context_resolver.py → calibrated priors)
↓
EDA & Data Prep (data_validator.py)
↓
Acid-Test Pre-Model (acid_test.py → integrity baseline)
↓
Model Specification (priors from context layer)
↓
Build Model (mmm.build_model)
↓
Prior Predictive Checks
↓
[Optional] Add Lift Test Calibration
↓
[Optional] Add Trade Marketing Variables (see trade_marketing_decomposition.md)
↓
Fit on FULL Dataset (mmm.fit with nutpie)
↓
Diagnostics (divergences=0, R-hat<1.01, ESS>400)
↓
Acid-Test Post-Model (absorption check, ROI plausibility, permutation)
↓
Media Deep Dive (contributions, ROAS, saturation, sensitivity)
↓
Four-Way Decomposition (base + media + trade + interaction)
↓
Budget Optimization + Scenario Planning
↓
Report Generation (HTML dashboard + PPTX deck + JSON API)
Key APIs
Incrementality (preferred for ROAS/CAC)
roas = mmm.incrementality.contribution_over_spend(frequency="all_time")
marginal_roas = mmm.incrementality.marginal_contribution_over_spend(frequency="all_time", spend_increase_pct=0.01)
cac = mmm.incrementality.spend_over_contribution(frequency="quarterly")
Summary DataFrames
mmm.summary.posterior_predictive() # mean, median, HDI, observed
mmm.summary.contributions() # per-channel contributions
mmm.summary.roas() # ROAS with HDI
mmm.summary.saturation_curves() # saturation response
mmm.summary.adstock_curves() # decay profiles
Budget Optimization
from pymc_marketing.mmm.multidimensional import MultiDimensionalBudgetOptimizerWrapper
optimizer = MultiDimensionalBudgetOptimizerWrapper(model=mmm, start_date=..., end_date=...)
allocation, result = optimizer.optimize_budget(budget=1_000_000, budget_bounds=bounds)
response = optimizer.sample_response_distribution(allocation_strategy=allocation, include_carryover=True)
See references/budget_optimization.md for bounds setup, channel fixing, custom constraints, and budget sweeps.
Scenario Planning
See references/scenario_planning.md for Meridian-inspired patterns adapted for PyMC-Marketing:
- Fixed budget optimization (maximize ROI at given budget)
- Flexible budget optimization (find max budget at target ROI)
- Budget sweeps with efficient frontier
- Flighting / temporal distribution
- Multi-scenario comparison (conservative/moderate/aggressive)
- Cost-per-media-unit sensitivity
Save/Load
mmm.save("mmm_model.nc", engine="h5netcdf")
loaded = MMM.load("mmm_model.nc")
YAML Specification
from pymc_marketing.mmm.builders.yaml import build_mmm_from_yaml
mmm = build_mmm_from_yaml("model_spec.yaml", X=X, y=y)
Layer 1: Acid-Test Validation
Runs pre/post-model integrity checks to verify MMM truthfulness. This is how you expose holdco manipulation.
from scripts.acid_test import AcidTestValidator
# Pre-model (before fitting)
validator = AcidTestValidator(df, "date", "sales", ["tv", "social", "search"])
pre_report = validator.run_pre_model_tests()
# Post-model (after fitting, with MMM results)
post_report = validator.run_post_model_tests({
"r2": 0.82,
"channel_contributions": {"tv": 500000, "social": 200000, "search": 300000},
"channel_roas": {"tv": 2.1, "social": 1.5, "search": 3.2},
"baseline_pct": 0.55,
})
print(validator.to_summary())
validator.to_json("integrity_report.json")
Key tests: Holt-Winters baseline, Granger causality, VIF, baseline absorption check, ROI plausibility. See references/acid_test_validation.md.
Layer 2: Context Layer
Resolves industry/category/market benchmarks into calibrated priors:
from scripts.context_resolver import ContextResolver
resolver = ContextResolver(
industry="CPG", category="Beverages", market="Mexico",
channels=["tv", "social", "search", "ooh"],
distribution_model="indirect", trade_marketing_share=0.45,
)
brief = resolver.to_json("context_brief.json")
print(resolver.to_summary())
# Use resolved priors in model config
# brief.suggested_model_config → ready-to-use Prior strings
# brief.roi_benchmarks → feeds acid-test ROI plausibility check
# brief.adstock_priors → calibrated Beta distributions per channel
See references/context_layer.md for full benchmark databases and adaptation rules.
Layer 3: Trade Marketing Decomposition
Four-way decomposition: base demand + media-driven + trade-driven + interaction.
Add trade variables as controls with domain-informed priors:
control_columns = ["promo_depth", "price_index", "distribution_acv", "feature_flag"]
model_config = {
"saturation_beta": Prior("HalfNormal", sigma=spend_shares, dims="channel"),
"gamma_control": Prior("Normal", mu=trade_prior_means, sigma=trade_prior_sds, dims="control"),
}
For sophisticated decomposition with custom trade response curves, use a custom PyMC model. See references/trade_marketing_decomposition.md for flexible data schema (Tier 1-4), functional forms, and the holdco exposure play.
Critical Rules
- Always fit on FULL dataset — train/test splits are ONLY for
TimeSliceCrossValidatorstability checks - Zero divergences required — any divergences invalidate the posterior
- R-hat < 1.01 for all parameters before proceeding
- Call
add_original_scale_contribution_variablebeforesample_posterior_predictiveto get*_original_scalevariables - Use
mmm.incrementalityfor ROAS (accounts for adstock carryover), not element-wise division - Use
MultiDimensionalBudgetOptimizerWrappernotBudgetOptimizerdirectly (handles geo allocation) - Never present platform ROAS as incremental without caveating (Agency Growth OS rule)
- Search web for current industry benchmarks before comparing ROI results
Diagnostics Quick Reference
| Metric | Target | Critical |
|---|---|---|
| Divergences | 0 | Must be 0 |
| R-hat | < 1.01 | All params |
| ESS (bulk) | > 400 | > 800 preferred |
| Posterior R² | > 0.70 | > 0.80 preferred |
| Baseline % | 20-80% | Flags over/under-attribution |
See references/diagnostics_benchmarks.md for full checklist and industry ROI ranges.
What ships with it: 18 files
144.9 KB alongside SKILL.md, 4 of them executable
references/
- acid_test_validation.md8.6 KB
- budget_optimization.md3.7 KB
- context_layer.md9.2 KB
- custom_model.md1.4 KB
- data_analysis.md2.5 KB
- diagnostics_benchmarks.md3.7 KB
- lift_test_calibration.md1.9 KB
- media_deep_dive.md3.8 KB
- model_fit.md2.6 KB
- model_specification.md8.2 KB
- plot_api.md1.7 KB
- scenario_planning.md7.8 KB
- time_varying_parameters.md1.5 KB
- trade_marketing_decomposition.md12.7 KB
scripts/
- acid_test.pyruns23.0 KB
- context_resolver.pyruns15.3 KB
- data_validator.pyruns17.5 KB
- report_generator.pyruns19.9 KB