The Empirical-Bayes Workflow in gdpar

Operational recipe: gdpar_eb() end-to-end, four path regimes, EB-vs-FB comparison and troubleshooting (Sub-phase 8.6.E)

José Mauricio Gómez Julián

2026-07-06

1. What this vignette covers

This is an operational recipe. It walks you through gdpar_eb() end to end across the four path regimes canonized in Sub-phase 8.6 of gdpar:

Regime \(K\) \(p\) Stan template pair
Base 1 1 amm_eb_marginal.stan + amm_eb_conditional.stan
Path A 1 >1 amm_eb_marginal_multi.stan + amm_eb_conditional_multi.stan
Path B >1 1 amm_eb_marginal_K.stan + amm_eb_conditional_K.stan
Path C >1 >1 amm_eb_marginal_KxP.stan + amm_eb_conditional_KxP.stan

It then shows how to compare an EB fit against a Fully-Bayes (FB) fit on the same data via gdpar_compare_eb_fb(), how to read the numerical diagnostics that surface when the marginal Hessian is poorly conditioned, and when to give up on EB and fall back to FB.

The theoretical canonization of EB-vs-FB — the asymptotic equivalence (Theorem 7A and its multivariate extension Theorem 7A*), the higher-order coverage discrepancy (Proposition 7B scalar / 7B* matricial / 7B* tensorial), the finite-sample compound decision bound (Theorem 7C / 7C* compound multi-slot), and the four discrepancy conditions of Proposition 7D — lives in the canonical vignettes vignette("v07_eb_vs_fb") (scalar) and vignette("v07b_eb_multivariate") (multivariate extension). Read those if you want to know why a given choice was made; read this one if you want to do it.

The chunks below default to eval = FALSE because they compile Stan models and take several minutes per fit; re-enable evaluation on a per-chunk basis or globally via knitr::opts_chunk$set(eval = TRUE) if you want to reproduce the runs locally.

2. Setup

library(gdpar)
# These two are Suggests; gdpar_eb() and gdpar_compare_eb_fb() require
# them at runtime.
library(cmdstanr)
library(posterior)

We will work with a synthetic dataset of size n = 150 on a single continuous outcome with one covariate, then enrich it to multivariate and multi-slot variants as we walk through the four regimes.

set.seed(20260526L)
n  <- 150L
df <- data.frame(x = stats::rnorm(n))
df$y_scalar <- 1.0 + 0.4 * df$x + stats::rnorm(n, sd = 0.3)
# Multivariate (p = 2) outcome for Path A.
df$y_p2 <- cbind(
  1.0 + 0.4 * df$x + stats::rnorm(n, sd = 0.3),
  -0.5 + 0.2 * df$x + stats::rnorm(n, sd = 0.4)
)
# Same dataset is fine for Path B (K > 1, p = 1) and Path C (K > 1,
# p > 1) by reusing y_scalar / y_p2 with a multi-slot family below.

3. Minimal gdpar_eb() call (K = 1 + p = 1)

The base regime mirrors the canonical gdpar() signature; the only new arguments are eb_correction = TRUE (default; applies the Proposition 7B scalar inflation to the conditional credible intervals) and laplace_control = list(...) (controls the multi-start Laplace maximizer of v07 Section 11.1 step (i)).

fit_eb <- gdpar_eb(
  formula        = y_scalar ~ x,
  family         = gdpar_family("gaussian"),
  amm            = amm_spec(a = ~ x),
  data           = df,
  iter_warmup    = 500L,
  iter_sampling  = 500L,
  chains         = 2L,
  refresh        = 0L,
  seed           = 1L,
  laplace_control = list(multi_start_M = 5L)
)
print(fit_eb)

The output reports the EB plug-in point estimate \(\widehat\theta_{\text{ref}}^{\text{EB}}\) (from the marginal Laplace), its marginal standard error from the Laplace covariance, the marginal Hessian condition number \(\kappa(H)\), the multi-start dispersion across the multi_start_M = 5 independent inits, and the conditional HMC convergence diagnostics (\(\widehat R\), ESS, divergences).

summary(fit_eb) returns a tidy table of EB credible intervals with the Proposition 7B scalar inflation applied:

summary(fit_eb)

4. Path A (K = 1, p > 1): multivariate outcome

For a \(p\)-dimensional outcome, the amm spec uses dimwise() (or a plain list of length \(p\)) to declare the per-coordinate components, and the family is promoted to a gdpar_family_multi of dimension \(p\). The Stan template pair amm_eb_marginal_multi.stan + amm_eb_conditional_multi.stan (canonized in Sub-phase 8.6.C under decision D34) is dispatched automatically:

fit_eb_A <- gdpar_eb(
  formula        = y_p2 ~ x,
  family         = gdpar_family_multi("gaussian", p = 2L),
  amm            = amm_spec(p = 2L, dims = dimwise(a = ~ x)),
  data           = df,
  iter_warmup    = 500L,
  iter_sampling  = 500L,
  chains         = 2L,
  refresh        = 0L,
  seed           = 2L
)
print(fit_eb_A)

The corresponding correction is matricial, \(C^*_{g,\alpha} \in \mathbb{R}^{p\times p}\) (Proposition 7B* of v07b Section 5.1). It reduces algebraically to the scalar Proposition 7B at \(p = 1\), so the upgrade from base to Path A is transparent.

5. Path B (K > 1, p = 1): multi-parametric family

For a multi-slot distributional regression (e.g. modelling both mu and sigma of a Gaussian K=2, or mu and phi of a Negative Binomial K=2), the amm input is either a named list of amm_spec (one per slot) or a gdpar_formula_set via gdpar_bf(...). The Stan template pair amm_eb_marginal_K.stan + amm_eb_conditional_K.stan is dispatched automatically:

fs <- gdpar_bf(y_scalar ~ a(x), sigma ~ a(x))
fit_eb_B <- gdpar_eb(
  formula        = fs,
  family         = gdpar_family("gaussian"),
  data           = df,
  iter_warmup    = 500L,
  iter_sampling  = 500L,
  chains         = 2L,
  refresh        = 0L,
  seed           = 3L,
  skip_id_check  = TRUE
)
print(fit_eb_B)

Coverage of stan_ids per Sub-phase 8.6.C decision D33 (relaxed): {1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13} — Gaussian, Negative Binomial, Beta, Gamma, Lognormal-loc-scale, Student-t, Tweedie, ZIP, ZINB, Hurdle-Poisson, Hurdle-NB.

6. Path C (K > 1, p > 1): full K x p extension

Path C, canonized in Sub-phase 8.6.D under decision D36 = (alpha) + D37 = (i) + D38’’ = (h), composes Path A coordinate-wise factorization with Path B multi-parametric K-slot semantics: a single outcome matrix-column y[n, p] is shared across the K distributional slots, each carrying its own per-coordinate linear predictor.

The initial 8.6.D iteration restricts Path C to family$stan_id %in% c(1, 3) (Gaussian K=2, NB K=2) under decision D40’ to avoid the numerical caveat of Section 6.1 of the opening handoff (HMC condicional bajo plug-in EB cerca del borde de soporte logit/log links + warmup corto). Beta / Gamma / Lognormal / Student-t / Tweedie / mixtures are deferred to a later iteration.

y_p2_int <- matrix(
  rnbinom(n * 2L, size = 5, mu = exp(0.5 + 0.2 * df$x)),
  n, 2L
)
df$y_p2_int <- y_p2_int
fit_eb_C <- gdpar_eb(
  formula        = y_p2_int ~ x,
  family         = gdpar_family("neg_binomial_2"),
  amm            = list(
    mu  = amm_spec(p = 2L, dims = dimwise(a = ~ x)),
    phi = amm_spec(p = 2L, dims = dimwise(a = ~ x))
  ),
  data           = df,
  iter_warmup    = 500L,
  iter_sampling  = 500L,
  chains         = 2L,
  refresh        = 0L,
  seed           = 4L,
  skip_id_check  = TRUE
)
print(fit_eb_C)

The fit object carries new Path C-specific slots:

7. Numerical diagnostics: how to read them

Every gdpar_eb_fit carries a diagnostics_numerical slot with four entries derived from the multi-start Laplace strategy of Charter Section 2.8:

8. EB vs FB comparison via gdpar_compare_eb_fb()

The companion function gdpar_compare_eb_fb() (canonized in Sub-phase 8.6.E) takes a gdpar_eb_fit and a gdpar_fit fitted on the same dataset and reports three operational diagnostics of the EB-vs-FB theory of v07:

fit_fb <- gdpar(
  formula        = y_scalar ~ x,
  family         = gdpar_family("gaussian"),
  amm            = amm_spec(a = ~ x),
  data           = df,
  iter_warmup    = 500L,
  iter_sampling  = 500L,
  chains         = 2L,
  refresh        = 0L,
  seed           = 1L
)
cmp <- gdpar_compare_eb_fb(fit_eb, fit_fb, level = 0.95,
                            tv_bins = 30L)
print(cmp)
summary(cmp)

The output carries three tables:

9. Troubleshooting

9.1. gdpar_eb_numerical_error: kappa = ...

The marginal Hessian is too ill-conditioned for Laplace to be reliable. Options:

  1. Tighten the prior on theta_ref via a stronger gdpar_prior(theta_ref = "normal(0, 0.5)").
  2. Move the anchor closer to the data via anchor = "empirical_y".
  3. Raise laplace_control$multi_start_M and rerun (the per-init seed offset will sample different unconstrained-space inits).
  4. Fall back to FB via gdpar() (the most reliable option when the marginal likelihood is genuinely flat).

9.2. gdpar_unsupported_feature_error on Path C

The initial 8.6.D iteration restricts Path C to family$stan_id %in% c(1, 3) (Gaussian K=2, NB K=2) per decision D40’. For other distributional families under \(K > 1 \wedge p > 1\), fall back to FB via gdpar() or split the multivariate outcome into \(p\) separate Path B fits as a workaround.

9.3. Multi-start dispersion warning

A multi_start_dispersion above 0.05 suggests multi-modality of the marginal likelihood (open question O5*-EBFB of v07b Section 9.5). The result returned by gdpar_eb() is the mode with the highest log marginal across the multi_start_M inits, which may differ from the mode picked by a single run. Options:

  1. Raise multi_start_M from the default 5 to 10–20.
  2. Use a stronger prior on theta_ref to break the multi-modality.
  3. Fall back to FB; HMC explores multi-modal posteriors better than single-mode Laplace.

9.5. Path C smoke validation

A representative end-to-end Path C smoke (Gaussian K=2 + p=2, \(n = 80\), iter_warmup = iter_sampling = 200L, chains = 2L) takes approximately 50 seconds on contemporary hardware (amm_eb_marginal_KxP.stan and amm_eb_conditional_KxP.stan each compile once and are cached by cmdstanr). If the smoke takes significantly longer or aborts with gdpar_eb_numerical_error, the geometry of the K × p marginal is likely the culprit; check kappa_per_slot and the slot dispositions returned by the correction tensor.

10. Where to go next

References

The canonical theoretical references for EB-vs-FB in the AMM are Petrone, Rousseau, and Scricciolo (2014) and Rousseau and Szabo (2017) for the asymptotic equivalence and merging results; Carlin and Gelfand (1990) for the higher-order coverage discrepancy and the inflation correction; and Robbins (1956) / Efron (2010) for the compound decision framework. Full bibliographic details are in vignette("v07_eb_vs_fb") Section “References Cited in This Block”.

mirror server hosted at Truenetwork, Russian Federation.