Concept note, no raw source: it extends the frequentist FH model of Molina & Marhuenda (2015) to the full-Bayes version used in the PyMC example E112 (county child poverty), adding uncertainty in A, a logit link, estimated variances, and “surprise” maps. Literature: Fay & Herriot (1979, JASA); Rao & Molina (2015, Small Area Estimation, 2nd ed.); Gelman et al. (BDA3, eight-schools); Wolter and Fay-Herriot on generalised variance functions.
Overview
EBLUP plugs in A^ and ignores its uncertainty (hence MSE corrections). A hierarchical Bayes (HB) FH puts priors on β and A and returns full posteriors of δd for any area-level question.
Main Content
Hierarchical Bayes FH
yd∣δd∼N(δd,ψd); δd∣β,A∼N(xd⊤β,A); β∼N(0,weak), A∼Half-Normal (or half-Cauchy). Posterior of δd given A is N(γdyd+(1−γd)xd⊤β,γdψd), so the EBLUP is its mean.
Logit variant (proportions). For a rate pd with direct estimate p^d and margin of error, model p^d∼N(pd,ψd) with logit(pd)=xd⊤β+ud so shrinkage respects [0,1]. Counties with zero direct poverty and zero variance are left-censored data: use a split likelihood (censored term for zeros, Normal for positive) rather than discarding them (E112’s “censored zeros”).
Variances are estimates. FH assumes ψd known. ACS margins of error are themselves noisy; a generalised variance function (GVF) smooths them: logψ^d=a+blog{p^d(1−p^d)/nd}+ϵd, or treat published variances as Gamma data with a model for the mean (E112 does the latter). Two independent surveys of the same year (ACS 2014-18 vs 2019-23) test whether stated margins of error are calibrated: standardised differences should be N(0,1).
Surprise map
Map the standardised departure of a posterior from its prior-model expectation, e.g. zd=(p^ddirect−E[pd∣x])/Var or Pr(pd>national rate∣data). It highlights areas that are unusual given their covariates and sampling noise, rather than areas that merely have noisy estimates. Posterior-predictive checks (mixed predictive checks that resample the area effect) calibrate it.
Examples
PyMC sketch (untested)
import pymc as pmwith pm.Model(): beta = pm.Normal("beta", 0, 2, shape=X.shape[1]) A = pm.HalfNormal("A", 1.0) u = pm.Normal("u", 0, 1, shape=D) logit_p = X @ beta + A * u # non-centred p = pm.Deterministic("p", pm.math.sigmoid(logit_p)) pos = y > 0 pm.Normal("y_pos", p[pos], sigma=se[pos], observed=y[pos]) # zeros: left-censored at 0 (illustrative)