Chapter 03 of 10
ROI is sampled, beta is derived
The structural choice the rest of the engine hangs off: put the prior where a marketer has an opinion, and solve for the coefficient.
Most MMM implementations put a prior on the media coefficient. That prior is unusable: its scale depends on the KPI units, the spend units, the population divisor, the media scaler and the saturation point, so nobody can state one honestly and almost nobody tries. What gets shipped instead is a weakly-informative default that the analyst has no opinion about, and the elicitation step quietly does not happen.
This engine inverts the dependency. Average ROI is the sampled quantity; the coefficient is solved for.
The derivation
Incremental outcome for channel m, in currency, is the transformed media summed over every geo and period and converted back into KPI units:
I[m] = sum_g sum_t beta[g,m] * delta[g,t,m] * rpk[g,t] * kpi_scale * pop[g]
Collect the geo-and-time sum into A[g,m], substitute the non-centered hierarchy beta[g,m] = exp(log_beta[m]) * geo_factor[g,m], and require that incremental outcome equal ROI times spend. That pins the shared log coefficient exactly:
A[g,m] = sum_t delta[g,t,m] * rpk[g,t] * pop[g] * kpi_scale denom[m] = sum_g A[g,m] * geo_factor[g,m] log_beta[m] = log(roi[m] * total_spend[m]) - log(denom[m])
source · engine/mmm/model.py · _derive_log_beta
Why there is no Jacobian correction
This is a common place to get a change of variables wrong, so it is worth being explicit about why nothing is needed here. The prior is placed on roi itself, and log_beta is a deterministic function of that draw and of quantities already in the trace. No density is being transported from one parameterization to another; there is exactly one scalar per channel and its prior is stated directly in the space it is sampled in.
The consequence worth internalizing is that contribution[m] equals roi[m] * total_spend[m]identically, by construction, for every draw. That identity is what makes the prior studio’s implied-contribution check closed form and therefore fast enough to run on every keystroke rather than behind a button.
What the parameterization costs