Skip to content
Four Roads to a Posterior, home

Errata

What went wrong in 2023, and what changed

Re-running the submission in 2026 surfaced five problems. None of them is in the algorithms: Metropolis.fn, HMC.fn and EP.logit are sound. Each method was simply handed a different, mis-encoded version of the data. The corrected analysis calls the very same functions with the data the question describes.

01

Dose became a seven-level factor

  • Laplace
  • MH
  • HMC
  • EP
Original · Rmd lines 64-66 and 134
data <- data.frame(dose = as.factor(doses),
                   improvement = improvement_status,
                   count = counts)
X <- model.matrix(~ dose, data = data)   # 14 x 7
Corrected · scripts/r-reference.R
grouped <- data.frame(dose = 0:6,
                      y = c(3, 5, 4, 4, 7, 6, 8),
                      n = rep(10, 7))
X_c <- model.matrix(~ dose, data = grouped)  # 7 x 2

The model in the question is logit p = x′β with dose as the covariate. As a factor, every method instead estimates an intercept plus six separate dose offsets, a saturated model with no notion of “a bigger dose helps more”. The data were also kept in long format, one row per dose and outcome, which set up the encoding bugs below.

02

The GLM response counted patients twice

  • Laplace
Original · Rmd line 78
glm(cbind(count, count - ifelse(improvement == 1, count, 0)) ~ dose,
    family = binomial, data = data)
Corrected
glm(cbind(y, n - y) ~ dose, family = binomial, data = grouped)

The first column (successes) is the full count on every row, and non-improvers are added again as failures. At dose 0 that reads as 10 successes and 7 failures, so the fitted probability of improvement is 10/17 = 0.59 instead of the observed 0.30. This GLM also supplied the proposal covariance for Metropolis-Hastings.

03

The Metropolis likelihood used two of seven parameters

  • MH
Original · Rmd line 95
log_odds <- as.numeric(data$dose) * params[2] + params[1]
# ... while the sampler proposes all p = ncol(X) = 7 parameters
Corrected
loglik <- function(params, data) {
  p <- plogis(params[1] + params[2] * data$dose)   # dose = 0..6
  sum(dbinom(data$y, size = data$n, prob = p, log = TRUE))
}

Coefficients 3 to 7 never touch the likelihood, so under the flat prior they drift as an unconstrained random walk (posterior sd up to 54, effective sample size as low as 1.2 from 50,000 draws). They cannot change the acceptance ratio, though. The low acceptance rate of 5.0% comes from the proposal covariance, which was borrowed from the double-counted GLM above: its dose-1 standard error (0.74) gives a proposal sd of 1.25 for the second parameter, about 10 times the posterior sd of the slope it actually plays in the likelihood (0.13). Most jumps overshoot, which is why the six rows printed by head(results_mh) are identical. as.numeric() on a factor also gives the level index 1 to 7, not the dose 0 to 6, shifting the intercept. Interestingly, the success/trial encoding in this one function was right.

04

HMC and EP read the 0/1 label as the number improved

  • HMC
  • EP
Original · Rmd lines 211 and 306
HMC.fn(y = data$improvement, n = data$count, X = X, ...)
EP.logit(response = data$improvement, n = data$count, X = X, ...)
Corrected
HMC.fn(y = grouped$y, n = grouped$n, X = X_c, L = 20, ...)
EP.logit(response = grouped$y, n = grouped$n, X = X_c, ...)

Each long-format row says “1 success out of count trials” or “0 out of count”: seven improvements in seventy patients instead of 37. Both methods therefore agree with each other on a posterior centred near an intercept of -2.65 with dose effects near zero, which is a correct answer to the wrong question.

05

The comparison figure mislabelled two methods

  • HMC
  • EP
Original · Rmd line 342
legend = c('Metropolis Hasting', 'GLM', 'MHC', 'ABC')
Corrected
legend = c('Metropolis-Hastings', 'Laplace (GLM)', 'HMC', 'EP')

A cosmetic slip, but a telling one: “ABC” usually means approximate Bayesian computation, a different method. The figure also took its axis limits from the Metropolis density, which in this version was very wide for the drifting coefficients.

Before and after, in numbers

In the submitted version the four “estimates” of the second coefficient are not even estimates of the same quantity. After the fix they agree to within 0.02.

Second coefficient from each method: β₁ is the dose-1 offset in the submitted model (for MH, a slope on the level index) and the dose slope in the corrected model. Values from the original R functions, seed 90125.

  • Laplace approximation

    As submitted β₁
    0.336 ± 0.737
    Corrected β₁
    0.302 ± 0.129
  • Metropolis-Hastings

    As submitted β₁
    0.316 ± 0.130
    Corrected β₁
    0.317 ± 0.132
  • Hamiltonian Monte Carlo

    As submitted β₁
    -0.072 ± 1.947
    Corrected β₁
    0.315 ± 0.133
  • Expectation propagation

    As submitted β₁
    0.004 ± 1.707
    Corrected β₁
    0.314 ± 0.132

Reproducing both versions in R

The submitted Rmd hard-codes a setwd() to a folder on the original laptop. The repository adds a small wrapper that runs every chunk except that line, plus the script that generates the reference values the browser port is tested against. On R 4.6 both reproduce the printed 2023 output exactly.

From the repository root
Rscript -e 'install.packages(c("mvtnorm", "coda", "jsonlite"))'

# the 2023 analysis, unchanged
Rscript coursework/run.R

# both versions, written to web/src/lib/data/r-reference.json
Rscript scripts/r-reference.R