Jin 2017 MMM - STAN Sampler Code and MCMC Implementation

Summary

The paper’s appendix gives a complete Stan program for the delayed-adstock + Hill MMM (Eq. 7) with the Table 3 priors. Lagged spend is pre-arranged as an array of row vectors. Each draw computes weights, normalises the adstock, applies the Hill function, and forms . The authors also wrote a custom C++ Gibbs sampler (slice-sampling updates using the BOOM library), which ran about 20 times faster in wall-clock terms on the simulated data (52 s for 10k iterations vs 1149 s for 1k). Strong posterior correlation among the transformation parameters makes HMC slow: hours to days on a few thousand data points.

Overview

Sec. 3 (pp. 6-7) describes two samplers.

  1. Gibbs sampling (Gelfand & Smith 1990; Geman & Geman 1984) splits the parameter vector into blocks and samples each low-dimensional conditional in turn. The non-conjugate conditionals are drawn with Neal’s (2003) slice sampler, in C++ on Scott’s BOOM library.
  2. Hamiltonian Monte Carlo via Stan (Carpenter et al. 2016; NUTS, Hoffman & Gelman 2014). The user writes the log density and Stan does the inference.

References cited for estimation and sampling in Sec. 3 (citation map).

TopicReference citedRole in the paper
Frequentist fit, Eq. 8Lehmann & Casella (1998), Theory of Point Estimation, p. 449variance and asymptotic distribution of the MLE . The paper calls the log likelihood
Posterior summariesHartigan (2012) Bayes Theory; Hoff (2009) A First Course in Bayesian Statistical Methodsquantile-based credible intervals as a fuller summary than the posterior mean, median or mode
Gibbs samplingGelfand & Smith (1990); Geman & Geman (1984)partition and draw low-dimensional conditionals
Slice samplingNeal (2003)non-conjugate updates inside the custom Gibbs sampler
SoftwareScott (2016) BOOM R/C++ library; Carpenter et al. (2016) StanGibbs implementation; HMC implementation
HMC / NUTSHoffman & Gelman (2014)the method Stan runs
PriorsGelman (2006), “Prior distribution”, Encyclopedia of Environmetrics, pp. 1634-1637priors with the same support give similar posteriors when the data are informative, and dominate when they are not
ConvergenceGelman & Rubin (1992) (potential scale reduction factor), reported as equal to or close to 1 for all four shampoo models (Table 8 caption)

High correlation between the transformation parameters (the near-unidentifiable ridge and the - coupling) is “a special challenge” for Stan. It finishes in reasonable time on a few hundred points but can take “quite a few hours or even a couple of days” on a few thousand points. The custom Gibbs sampler is “much more efficient” (p. 7).

Main Content

Stan program from the Appendix (pp. 28-30; transcribed from the source)

functions {
  // the Hill function
  real Hill(real t, real ec, real slope) {
    return 1 / (1 + (t / ec)^(-slope));
  }
  // the adstock transformation with a vector of weights
  real Adstock(row_vector t, row_vector weights) {
    return dot_product(t, weights) / sum(weights);
  }
}
 
data {
  // the total number of observations
  int<lower=1> N;
  // the vector of sales
  real<lower=0> Y[N];
  // the maximum duration of lag effect, in weeks
  int<lower=1> max_lag;
  // the number of media channels
  int<lower=1> num_media;
  // a vector of 0 to max_lag - 1
  row_vector[max_lag] lag_vec;
  // 3D array of media variables
  row_vector[max_lag] X_media[N, num_media];
  // the number of other control variables
  int<lower=1> num_ctrl;
  // a matrix of control variables
  row_vector[num_ctrl] X_ctrl[N];
}
 
parameters {
  // residual variance
  real<lower=0> noise_var;
  // the intercept
  real tau;
  // the coefficients for media variables
  vector<lower=0>[num_media] beta_medias;
  // coefficients for other control variables
  vector[num_ctrl] gamma_ctrl;
  // the retention rate and delay parameter for the adstock transformation of
  // each media
  vector<lower=0,upper=1>[num_media] retain_rate;
  vector<lower=0,upper=max_lag-1>[num_media] delay;
  // ec50 and slope for Hill function of each media
  vector<lower=0,upper=1>[num_media] ec;
  vector<lower=0>[num_media] slope;
}
 
transformed parameters {
  // a vector of the mean response
  real mu[N];
  // the cumulative media effect after adstock
  real cum_effect;
  // the cumulative media effect after adstock, and then Hill transformation
  row_vector[num_media] cum_effects_hill[N];
  row_vector[max_lag] lag_weights;
 
  for (nn in 1:N) {
    for (media in 1 : num_media) {
      for (lag in 1 : max_lag) {
        lag_weights[lag] <- pow(retain_rate[media], (lag - 1 - delay[media]) ^ 2);
      }
     cum_effect <- Adstock(X_media[nn, media], lag_weights);
     cum_effects_hill[nn, media] <- Hill(cum_effect, ec[media], slope[media]);
    }
    mu[nn] <- tau +
              dot_product(cum_effects_hill[nn], beta_medias) +
              dot_product(X_ctrl[nn], gamma_ctrl);
  }
}
 
model {
  retain_rate ~ beta(3,3);
  delay ~ uniform(0, max_lag - 1);
  slope ~ gamma(3, 1);
  ec ~ beta(2,2);
  tau ~ normal(0, 5);
 
  for (media_index in 1 : num_media) {
    beta_medias[media_index] ~ normal(0, 1);
  }
  for (ctrl_index in 1 : num_ctrl) {
    gamma_ctrl[ctrl_index] ~ normal(0,1);
  }
 
  noise_var ~ inv_gamma(0.05, 0.05 * 0.01);
  Y ~ normal(mu, sqrt(noise_var));
}

How the code maps to the paper's notation

Stan namePaper symbolNotes
X_media[n, m] (row vector of length max_lag)lagged spend is pre-built outside Stan; element lag holds
lag_weights[lag] = retain_rate^((lag-1-delay)^2), delayed adstock, Eq. 3
AdstockEq. 1normalised weighted average
Hill(t, ec, slope), Eq. 4ec (“EC50”) , constrained to because spend is scaled to
beta_medias with <lower=0> + normal(0,1) half-normalthe constraint truncates the normal
delay in , uniform(0, max_lag-1) for
noise_var ~ inv_gamma(0.05, 0.05*0.01) IGTable 3
Y ~ normal(mu, sqrt(noise_var))Gaussian white-noise likelihoodEq. 7

lag_vec is declared but never used. Fixing slope at 1 gives the reach transformation (Models II/IV).

Geometric adstock is not delay = 0. With delay fixed at 0 the code computes retain_rate^((lag-1)^2), that is . This is a half-Gaussian kernel that decays much faster than geometric decay, (Eq. 2). For the lag-3 weight is , against for geometric. Models III/IV therefore need a different weight line, lag_weights[lag] <- pow(retain_rate[media], lag - 1);, and delay removed from the parameters and the model block. The two adstock families are not nested. See ^delayed-adstock.

The ec bound fixes the prior support on . ec is declared <lower=0,upper=1>, so the program can only run the beta and uniform priors of Sec. 7.2. To reproduce the uniform arm, or any setting where the true lies outside the scaled spend range (Scenario II, ), the declaration must change to upper=10 (with ec ~ uniform(0, 10)). Under the code as printed, a true has zero prior mass. See Jin 2017 MMM - Prior Sensitivity Experiments on Beta and Half-Saturation.

Running this code with a current Stan release (my notes, not from the paper)

The program uses syntax that has been removed from current Stan: the <- assignment operator (now =) and old array declarations such as real Y[N] (now array[N] real Y). It also declares the per-iteration scratch variables cum_effect and lag_weights in transformed parameters, so they get saved as outputs. Moving them into local blocks avoids that. Other possible speed-ups: vectorise the lag weights, and use a non-centred or orthogonalised parameterisation. The paper’s own Sec. 8 result is that orthogonalising correlated controls gave a 40-fold speed-up (3040 s to 75 s; see ^table8).

Gibbs-with-slice-sampling scheme (Sec. 3, as described)

  1. Partition into blocks.
  2. For each block in turn, draw from its full conditional given all other blocks. Linear-Gaussian blocks ( and, given the transforms, ) and (inverse-gamma prior) may have closed-form conditionals. Use slice sampling for the non-conjugate transformation parameters .
  3. Repeat for 10,000 iterations (Sec. 5.1) and discard burn-in.

The paper does not publish the Gibbs code. Steps 1-2 are the standard construction implied by its description, and which blocks are conjugate is my inference.

Examples

Timing summary across the paper

SettingSamplerIterationsTime
Simulated dataset (2 yr, 3 media)custom Gibbs10,00052 s
Simulated dataset (2 yr, 3 media)Stan10001149 s
Shampoo, Model I, 2 media, correlated controlsStan, 3 chains1000/chain3040 s
Shampoo, Model I, 2 media, orthogonal controlsStan1000/chain75 s
Shampoo, Models I-IV, 5 media, orthogonalStan1000/chain471 / 181 / 112 / 91 s

Connections

See Also