Confidence Interval Coverage Simulation

Context: FIT2086_MOC · Studio 4 additional question · the empirical audit of Confidence Intervals — does a procedure actually cover of the time? · same simulate-and-count skeleton as Monte Carlo Estimator Comparison, but the thing counted is coverage, not bias/variance Problem it solves: measure how badly an approximate interval misses its advertised confidence level, and find the sample size at which the approximation becomes safe.

Quick Revision

  • 🎯 Trigger: “is this interval really ”, “how bad is the plug-in approximation”, “at what does it become acceptable” ➔ loop: generate from a known → build the interval → test containment → tally.
  • ⚠️ Key Constraint: the simulation only works because you chose the populationpop.mu/pop.lambda is the truth the interval is checked against, and it can never be estimated from the data inside the loop.

🔧 Minimal Working Example

testCIknownSigma2 <- function(pop.mu, pop.sigma2, n, niter) {
  retval = list(); retval$coverage = 0
  for (i in 1:niter) {
    y = rnorm(n, pop.mu, sqrt(pop.sigma2))        # rnorm takes sd, NOT variance
    mu.hat = mean(y)
    CI = mu.hat + c(-1.96*sqrt(pop.sigma2)/sqrt(n),
                     1.96*sqrt(pop.sigma2)/sqrt(n))
    if (pop.mu >= CI[1] && pop.mu <= CI[2]) {     # containment test
      retval$coverage = retval$coverage + 1
    }
  }
  retval$coverage = retval$coverage/niter         # count ➔ proportion
  return(retval)
}
testCIknownSigma2(pop.mu=0, pop.sigma2=1, n=5, niter=1e4)

Expected output: $coverage — and it stays for , , . Nothing moves it, because the known-variance interval is exact at every and every parameter value: the pivot is exactly regardless.

🔀 Variations

1. Plug-in vs exact — where the approximation breaks

Estimate inside the loop and build both intervals from the same sample:

se = sqrt(var(y))/sqrt(n)
CI.approx = mu.hat + c(-1.96*se, 1.96*se)                   # z with a plugged-in sigma
CI.t      = mu.hat + c(-qt(1-0.05/2, n-1)*se,               # exact, unknown-variance
                        qt(1-0.05/2, n-1)*se)

then sweep for (n in 3:100) and plot both coverage curves against . Expected output: the curve sits flat on for every ; the plug-in curve starts well below at and climbs toward it, the two becoming indistinguishable as grows. Reading ➔ substituting for ignores the uncertainty in that estimate, so the interval is too narrow and undercovers; the wider multiplier is exactly the repair (Student-t Distribution).

2. Poisson approximate interval — a two-dimensional grid

, and the approximate interval is (variance plugged in). Sweep both parameters with a nested loop into a matrix:

M = matrix(NA, 4, 5)                       # 4 lambdas x 5 sample sizes, prefilled NA
L = c(1,5,10,50); N = c(5,10,25,50,100)
for (i in 1:4) for (j in 1:5) M[i,j] = testCIlambda(L[i], N[j], 1e5)$coverage
results = data.frame(M)
row.names(results) <- paste0("lambda=", L)
names(results)     <- paste0("n=", N)

Expected output: coverage is essentially across the grid except at , where it is clearly poor, and , where it is a little low. Reading ➔ for a Poisson the CLT works twice — the approximation improves as grows and as grows (a is itself a sum of unit-rate pieces), so only the small-, small- corner fails.

When It Flips: exactness is a property of the pivot, not the sample size. Case 1 (known ) and the interval are exact at ; every interval that plugs an estimate into the variance for , for , for — is asymptotic and undercovers at small .

✍️ Practice

⚠️ Common Mistakes

  • 💡 Passing the variance to rnormrnorm(n, mu, sigma2) silently generates from the wrong population; the third argument is the sd, hence sqrt(pop.sigma2).
  • 💡 Checking containment against ➔ the test is pop.mu >= CI[1] && pop.mu <= CI[2]; the interval is built around , so testing returns coverage every time and measures nothing.
  • 💡 Reading a result as “my interval is correct” ➔ coverage at one proves nothing about others; the whole point of the sweep is that the failure lives in a corner of the parameter grid.
  • 💡 Too few iterations ➔ coverage is itself an estimated proportion with ; at niter=100 that is , wide enough to hide the effect being measured. The studio uses .
  • 💡 Growing the results vector inside the sweep ➔ preallocate with rep.int(0, 98) or matrix(NA, 4, 5); c() in a loop is .