--- title: "Meta-Analytic-Predictive (MAP) Priors with shrinkr and beastt" author: "Jacob M. Maronge" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Meta-Analytic-Predictive (MAP) Priors with shrinkr and beastt} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, fig.width = 7, fig.height = 4.5, dpi = 96 ) ``` ## Overview A **meta-analytic-predictive (MAP) prior** summarizes several historical control arms into a single prior for the control mean of a new trial (Neuenschwander et al., 2010). It is a random-effects meta-analysis: study-specific control means are treated as exchangeable draws from a common population, and the prior for the next trial is the posterior predictive distribution for a new, as-yet-unobserved study. To guard against prior-data conflict, the MAP is then *robustified* with a vague mixture component (Schmidli et al., 2014), so the historical data are automatically down-weighted when they disagree with the current trial. `shrinkr` and `beastt` split this work cleanly: - **`shrinkr`** runs the hierarchical meta-analysis across the historical studies and returns the MAP as a `distributional` object. - **`beastt`** robustifies that MAP (`robustify_norm()`), combines it with the internal control arm to form the posterior (`calc_post_norm()`), and reports the effective sample size (ESS). The hand-off between them is a single `dist_normal` object. We use a continuous outcome with **known** within-arm SD, which keeps every step conjugate. ```{r libraries} library(shrinkr) library(beastt) library(distributional) library(tibble) library(ggplot2) set.seed(1104) sigma_known <- 2 # known within-arm response SD (same in all arms) ``` ## The historical evidence A MAP prior starts from what the historical studies *reported*: a control-arm mean and its standard error. Here we have six prior studies whose control means cluster fairly tightly around 10. With a known SD, each standard error is just `sigma / sqrt(n)`. ```{r hist-data} n_hist <- c(58, 43, 62, 51, 47, 55) hist <- tibble( study = paste0("H", 1:6), n = n_hist, ybar = c(9.9, 10.3, 9.7, 10.1, 10.4, 9.8), se = sigma_known / sqrt(n_hist) ) hist ``` ```{r forest, fig.height = 3.4} ggplot(hist, aes(study, ybar)) + geom_hline(yintercept = mean(hist$ybar), linetype = "dashed", color = "grey50") + geom_pointrange(aes(ymin = ybar - 1.96 * se, ymax = ybar + 1.96 * se), color = "steelblue", linewidth = 0.8) + labs(x = NULL, y = "Control mean", title = "Historical control arms") + theme_minimal(base_size = 12) ``` The current trial contributes its own control arm of 70 patients: ```{r internal-data} n_int <- 70 int_ctrl <- tibble(y = rnorm(n_int, mean = 10, sd = sigma_known)) ``` ## Hierarchical meta-analysis with `shrinkr` With a known SD and a flat Stage-1 prior, each study's posterior for its control mean is exactly `N(ybar, se^2)` — so the reported summaries *are* the Stage-1 result, and we can hand them straight to `shrink()`. The hierarchical model is $$ \hat\theta_g \mid \theta_g \sim N(\theta_g,\, se_g^2), \qquad \theta_g \mid \mu, \tau \sim N(\mu, \tau^2), $$ with a vague prior on the population mean `mu` and a weakly-informative half-normal on the between-study SD `tau`. ```{r priors} hierarchical_priors <- list( mu = dist_normal(0, 100), tau = dist_truncated(dist_normal(0, sigma_known / 4), lower = 0) ) ``` It is worth checking what that `tau` prior implies about differences *between* study means before fitting. `sample_prior_predictive()` draws study effects and `prior_pairwise_differences()` summarizes the implied `|theta_i - theta_j|` (the location `mu` cancels, so this isolates heterogeneity even though `mu` is vague). ```{r prior-predictive} prior_pred <- sample_prior_predictive(hierarchical_priors, n_groups = nrow(hist), n_draws = 2000) plot(prior_pairwise_differences(prior_pred)) ``` If that spread looks unreasonable on the clinical scale, adjust the `tau` prior now. Then fit, passing the study summaries through `shrink()`'s `mle` / `var_matrix` interface. ```{r fit, results = "hide"} fit_map <- shrink( mle = hist$ybar, var_matrix = hist$se^2, hierarchical_priors = hierarchical_priors, chains = 4, iter = 4000, warmup = 1000, seed = 2026, refresh = 0, verbose = FALSE ) ``` ```{r fit-summary} summarize_mu_tau(fit_map) ``` ## Building the MAP prior The MAP is the posterior **predictive** distribution for a new study's control mean. Marginalizing over `(mu, tau)`, the Normal approximation has mean `E[mu]` and variance `Var(mu) + E[tau^2]` — the second term is the predictive spread from heterogeneity, which is what keeps a MAP honestly wider than a simple pooled mean. ```{r make-map} make_map <- function(fit) { d <- extract_mu_tau(fit) dist_normal(mean(d$mu), sqrt(var(d$mu) + mean(d$tau_squared))) } map_prior <- make_map(fit_map) map_prior ``` A convenient way to read the MAP's strength is its prior **effective sample size**: for a Normal prior on a mean with known SD, that is `sigma^2 / Var(prior)` — how many control patients the prior is "worth". ```{r map-ess} map_ess <- sigma_known^2 / variance(map_prior) map_ess ``` ## Robustify and form the posterior with `beastt` `robustify_norm()` mixes the MAP ("informative") with a vague component so the data can overrule the prior under conflict. Passing the MAP's prior ESS as `n` makes the vague component a unit-information prior (variance `sigma^2`); we put equal weight on the two components. ```{r robustify} rmp <- robustify_norm(map_prior, n = map_ess, weights = c(0.5, 0.5)) vague_prior <- dist_normal(mix_means(rmp)[["vague"]], mix_sigmas(rmp)[["vague"]]) plot_dist("MAP (informative)" = map_prior, "Vague component" = vague_prior, "Robust mixture" = rmp) ``` Now combine the robust mixture with the internal control arm. With a known SD the posterior is again a mixture of normals, and `beastt` updates the mixture weights automatically — down-weighting the informative component if the internal data disagree with it. The no-borrowing reference simply uses the vague component alone. ```{r posterior} post_borrow <- calc_post_norm(int_ctrl, response = y, prior = rmp, internal_sd = sigma_known) post_nobrrw <- calc_post_norm(int_ctrl, response = y, prior = vague_prior, internal_sd = sigma_known) plot_dist("No borrowing" = post_nobrrw, "Borrowing (robust MAP)" = post_borrow) ``` The effective sample size compares posterior variance with and without borrowing (Pennello & Thompson, 2008): a borrowed posterior with variance `Vb` is as informative as `n_int * V0 / Vb` patients. ```{r ess-table} ess_post <- n_int * variance(post_nobrrw) / variance(post_borrow) tibble( quantity = c("Posterior mean", "Posterior SD", "Effective sample size"), no_borrowing = round(c(mean(post_nobrrw), sqrt(variance(post_nobrrw)), n_int), 2), robust_map = round(c(mean(post_borrow), sqrt(variance(post_borrow)), ess_post), 2) ) ``` Because the historical and internal data are compatible, the robust MAP sharpens the control posterior and lifts the effective sample size well above the 70 internal controls. Under a prior-data conflict the informative component would lose weight and that gain would shrink toward zero — the self-correcting behavior the robust mixture is there to provide. ## Summary - **`shrinkr`** runs the hierarchical meta-analysis and builds the MAP as a `dist_normal`, taking study summaries through the `mle` / `var_matrix` interface. - **`beastt`** robustifies it (`robustify_norm()`), forms the internal control posterior (`calc_post_norm()`), and reports the ESS. - Check the heterogeneity prior with `sample_prior_predictive()` / `prior_pairwise_differences()` before fitting, and lean on the robust mixture so the data can overrule the historical evidence when they disagree. ## References Neuenschwander, B., Capkun-Niggli, G., Branson, M., & Spiegelhalter, D. J. (2010). Summarizing historical information on controls in clinical trials. *Clinical Trials*, 7(1), 5–18. Schmidli, H., Gsteiger, S., Roychoudhury, S., O'Hagan, A., Spiegelhalter, D., & Neuenschwander, B. (2014). Robust meta-analytic-predictive priors in clinical trials with historical control information. *Biometrics*, 70(4), 1023–1032. Pennello, G., & Thompson, L. (2008). Experience with reviewing Bayesian medical device trials. *Journal of Biopharmaceutical Statistics*, 18(1), 81–115.