# PyMC-Marketing Prior Sweep Blog Case Study

This repository is a complete, publishable case study showing how to diagnose and repair a difficult hierarchical MMM with **PyMC-Marketing 1.x** and the bundled `pymc-marketing-sweep` package.

The article is designed to read like a real modelling investigation rather than package documentation. It starts with the commercial data, explains why the model is difficult, shows what non-convergence looks like in traces and per-chain posterior densities, then introduces the prior sweep as a screening step before one final validation refit.

## Case study at a glance

The synthetic business contains:

- 3 brands
- 12 SKUs
- 5 retail customers
- 60 SKU/customer cells
- 156 weeks
- 9,360 panel rows
- 4 media channels
- price, promotion and distribution controls
- deliberately correlated discount, promotion and retailer-media activity
- true SKU/customer price elasticities that vary meaningfully across the portfolio

The story begins with a badly mixing centred hierarchy and ends with a clean validation fit whose SKU/customer price elasticities recover the synthetic truth while retaining real heterogeneity.

## Read the article

Open:

```text
BLOG.html
```

or read:

```text
BLOG.md
```

The HTML version is standalone apart from the relative `figures/` directory and is ready to host as a static page.

## What is included

```text
.
├── BLOG.md
├── BLOG.html
├── README.md
├── AGENT_PROMPT.md
├── AGENT_PROMPT.html
├── AGENTS.md
├── CLAUDE.md
├── SOURCES.md
├── configs/
│   └── sku_customer_brand_sweep.yaml
├── data/
│   ├── sku_customer_brand_weekly.csv
│   ├── true_parameters.csv
│   ├── sku_master.csv
│   └── future_media_price_plan.csv
├── figures/
├── outputs/story/
├── scripts/
└── pymc-marketing-sweep/
```

Important scripts:

- `scripts/generate_data.py`: builds the realistic synthetic retail panel.
- `scripts/summarise_data.py`: writes the EDA summary used in the article.
- `scripts/model_spec.py`: PyMC-Marketing 1.x SKU × customer × brand model.
- `scripts/fit_bad_model.py`: centred broad-prior hierarchy designed to expose difficult geometry.
- `scripts/fit_reference_model.py`: same broad prior distributions using non-centred hierarchies.
- `scripts/run_prior_sweep.py`: runs the PSIS candidate-prior screen.
- `scripts/fit_fixed_model.py`: performs the one real validation refit using the recommendation.
- `scripts/diagnose_fit.py`: convergence diagnostics.
- `scripts/extract_price_elasticities.py`: converts the price-control coefficient to conventional elasticity.
- `scripts/generate_story_artifacts.py`: deterministic posterior fixtures for an instant reproducible article.
- `scripts/make_figures.py`: renders all EDA, convergence, sweep and elasticity figures.
- `scripts/render_html.py`: renders the Markdown article and agent prompt to styled HTML.

## The fast reproducible version

The pre-rendered article uses deterministic posterior fixtures so you can recreate the complete story without waiting for MCMC:

```bash
python scripts/generate_data.py
python scripts/summarise_data.py
PYTHONPATH=pymc-marketing-sweep/src python scripts/generate_story_artifacts.py
python scripts/make_figures.py
python scripts/render_html.py
```

The bad-fit and final-fit chains in `outputs/story/` are synthetic fixtures created to demonstrate the failure and repair clearly.

The **PSIS sweep is genuinely run by the bundled sweep package** against the well-sampled synthetic reference posterior. The resulting Pareto-k values, reweighting ESS, feasibility decisions and recommended prior configuration are computed by the real package.

## Full PyMC-Marketing 1.x run

This repository targets:

```text
Python >= 3.12
PyMC-Marketing >= 1.0.0,<2
PyMC >= 6.0,<6.1
ArviZ >= 1.2,<2
```

Create an environment:

```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e ./pymc-marketing-sweep
```

Then:

```bash
./run_demo.sh
```

The real sequence is:

```text
realistic panel data
        ↓
centred broad-prior MMM
        ↓
diagnose divergences / R-hat / ESS / chain mixing
        ↓
non-centred broad-prior reference
        ↓
trustworthy reference_model.nc
        ↓
PSIS candidate-prior sweep
        ↓
recommended_model_config.py
        ↓
one real validation refit
        ↓
final convergence and elasticity checks
```

## Why a separate reference fit exists

Do not importance-reweight a clearly non-converged posterior.

The case study first changes the hierarchy from centred to non-centred **without changing the intended broad prior distribution**. That gives the sweep a posterior that has actually explored its target distribution.

Only then are alternative prior scales screened.

## Inputs for your own model

The normal sweep input is a saved PyMC-Marketing 1.x model:

```python
mmm.save("reference_model.nc")
```

Then inspect it:

```bash
pmm-sweep inspect reference_model.nc
```

The inspect command is important. It lets you discover the actual prior paths, posterior variable names and dimensions of a custom model rather than guessing them.

A sweep config can then target the relevant hierarchy:

```yaml
price_control_pooling:
  prior_path: gamma_control.sigma
  posterior_variable: gamma_control_sigma
  parameters:
    sigma:
      values: [0.50, 0.35, 0.25]
```

## Agent and skill support

The bundled sweep repository contains:

```text
pymc-marketing-sweep/AGENTS.md
pymc-marketing-sweep/CLAUDE.md
pymc-marketing-sweep/.agents/skills/pymc-marketing-sweep/SKILL.md
```

These instructions are deliberately model-agnostic. Codex, Claude or Cursor is told to inspect the fitted PyMC-Marketing 1.x model first, preserve native prior distributions and dimensions, edit YAML rather than engine code, protect user-selected business variables and emit a native PyMC-Marketing prior configuration for the validation refit.

That means the same workflow can be adapted to custom MMM dimensions such as customer, SKU, DMA, country, product or segment without hard-coding this case study into the sweep engine.

See `AGENT_PROMPT.md` for a full reusable prompt.

## Price elasticity definition

The case study models `log_units` and defines:

```text
price_discount_signal = -log(price / list_price)
```

If the posterior coefficient is `beta_price`, then:

```text
price elasticity = -beta_price
```

The target is not scaled in this example, so that coefficient remains interpretable on the log-demand scale.

## Publication assets

All article figures are in `figures/`. The source data for the EDA are in `data/`, and the story diagnostics and sweep outputs are in `outputs/story/`.

See `SOURCES.md` for the official PyMC-Marketing 1.0 references used to align the model and sweep implementation.
