Surgical — R cross-check (lme4::glmer)¶

Companion to surgical_python.ipynb / surgical_pymc.ipynb. The random-intercept-only binomial GLMM via glmer(cbind(r, n-r) ~ 1 + (1|hospital), family=binomial), with the no-pooling raw rates for contrast.

In [1]:
suppressMessages(library(lme4))
d <- read.csv('surgical.csv'); d$hospital <- factor(d$hospital)
gm <- glmer(cbind(r, n-r) ~ 1 + (1|hospital), family=binomial, data=d)
mu <- fixef(gm)[['(Intercept)']]; sg <- sqrt(unlist(VarCorr(gm))[1])
cat(sprintf('glmer: population rate = %.1f%%   random-intercept sd = %.3f\n', 100/(1+exp(-mu)), sg))
shrunk <- 1/(1+exp(-(mu + ranef(gm)$hospital[,1])))           # partial-pooled hospital rates
raw <- d$r / d$n
print(data.frame(hosp=1:nrow(d), n=d$n, raw_pct=round(100*raw,1), shrunk_pct=round(100*shrunk,1)))
glmer: population rate = 7.3%   random-intercept sd = 0.357
   hosp   n raw_pct shrunk_pct
1     1  47     0.0        5.4
2     2 148    12.2       10.2
3     3 119     6.7        7.0
4     4 810     5.7        5.9
5     5 211     3.8        5.2
6     6 196     6.6        6.9
7     7 148     6.1        6.6
8     8 215    14.4       12.3
9     9 207     6.8        7.0
10   10  97     8.2        7.7
11   11 256    11.3       10.2
12   12 360     6.7        6.8
In [2]:
# shrinkage plot (inline): raw -> shrunk, by volume
options(repr.plot.width = 8, repr.plot.height = 5)
ord <- order(raw); pop <- 1/(1+exp(-mu))
plot(seq_along(ord), 100*raw[ord], pch=1, col='darkorange', ylim=c(0,16),
     xlab='hospital (sorted by raw rate)', ylab='mortality %', main='Surgical: shrinkage of hospital rates', xaxt='n')
axis(1, at=seq_along(ord), labels=ord, cex.axis=0.7)
points(seq_along(ord), 100*shrunk[ord], pch=19, col='steelblue', cex=0.5+d$n[ord]/300)
segments(seq_along(ord), 100*raw[ord], seq_along(ord), 100*shrunk[ord], col='grey')
abline(h=100*pop, col='firebrick', lty=2)
legend('topleft', legend=c('raw r/n','shrunk (glmer)','population'), pch=c(1,19,NA), lty=c(NA,NA,2),
       col=c('darkorange','steelblue','firebrick'), bty='n', cex=0.85)
No description has been provided for this image

Results¶

  • glmer matches the Bayesian fits: population rate ≈ 7.3%, random-intercept SD ≈ 0.3, and the same shrunk hospital rates (hospital 1's 0% → ~5–6%, the high hospitals pulled in, large-volume hospital 4 barely moving).
  • Three engines agree (from-scratch Seeds engine ≈ PyMC ≈ glmer): the hierarchical binomial stabilises the league table. The small frequentist-vs-Bayesian gap in the random-effect SD is the usual weakly-identified variance component with only 12 groups.