
CS4.1 — Multiple Imputation & Rubin’s Rules
When data are missing, the simplest fix is to drop every incomplete row and analyse what remains. This complete case analysis (CC) is unbiased only when the data are missing completely at random — a strong assumption that is rarely met in practice. If older patients are more likely to miss a follow-up blood pressure measurement, dropping them removes a systematic slice of the data and biases every downstream estimate.
Multiple imputation (MI) addresses this by creating M complete versions of the dataset, each with the gaps filled by plausible values drawn from the observed patterns. The same analysis is run M times — once per completed dataset — producing M slightly different estimates. Rubin’s rules then pool these into a single result that accounts for both ordinary sampling uncertainty and the extra uncertainty that came from the missing data.
The pooling is the key challenge. Averaging the M estimates handles the point estimate, but the standard error needs more care: it must reflect two distinct sources of variation — uncertainty within each imputed dataset, and variation between the M datasets. Ignoring the second source gives confidence intervals (CIs) that are too narrow.
This page covers the complete pipeline: classifying your missing data mechanism (MCAR, MAR, or MNAR), running MI with the mice package, and working through Rubin’s pooling rules one step at a time — from the simple average of estimates to the full variance decomposition. By the end you should be able to reproduce pool() output by hand.
Work through each step in order. The concept section builds the formulas piece by piece. The worked example shows the full mice pipeline with a manual verification. Guided practice lets you reproduce the calculations in your browser without mice.
1 Concept: Missing Data and Rubin’s Rules
1.1 Step 0 — Classify the missingness
Before imputing, ask why data are missing. The answer determines which methods are valid.
| Mechanism | What it means | Example | Can MI fix it? |
|---|---|---|---|
| MCAR | Missingness unrelated to any variable | Lab machine fails randomly | Not needed, but MI is unbiased |
| MAR | Missingness depends on observed variables, not the missing value | Older patients skip follow-up; age is recorded | Yes — mice is valid under MAR |
| MNAR | Missingness depends on the missing value itself | Patients with very high BP avoid measurement | No guarantee — sensitivity analyses needed |
MAR is the working assumption behind mice and Rubin’s rules. It becomes more plausible when the variables that drive missingness are included in the imputation model.
1.2 Step 1 — Pool the point estimates
Suppose you imputed M = 3 datasets and fitted the same regression to each, obtaining three estimates (Heymans & Eekhout, Formula 9.1):
\[\hat{\theta}_1 = 2.174, \quad \hat{\theta}_2 = 1.965, \quad \hat{\theta}_3 = 1.774\]
The pooled estimate is the average:
\[\bar{\theta} = \frac{1}{M}\sum_{i=1}^M \hat{\theta}_i = \frac{2.174 + 1.965 + 1.774}{3} = 1.971\]
That is the easy part. The challenge is the standard error.
1.3 Step 2 — Within-imputation variance V_W
Each analysis also produced a standard error:
\[SE_1 = 0.896, \quad SE_2 = 0.882, \quad SE_3 = 0.898\]
The within-imputation variance is the average of the squared standard errors (Formula 9.2):
\[V_W = \frac{1}{M}\sum_{i=1}^M SE_i^2 = \frac{0.896^2 + 0.882^2 + 0.898^2}{3} = 0.7957\]
Think of \(V_W\) as the variance you would have if every imputed dataset gave exactly the same estimate — the baseline sampling variance, as if there were no missing data problem at all.
1.4 Step 3 — Between-imputation variance V_B
The three estimates (2.174, 1.965, 1.774) differ because each imputed dataset filled the gaps differently. This spread is the extra uncertainty due to missing data (Formula 9.3):
\[V_B = \frac{1}{M-1}\sum_{i=1}^M (\hat{\theta}_i - \bar{\theta})^2 = \frac{(2.174-1.971)^2 + (1.965-1.971)^2 + (1.774-1.971)^2}{2} = 0.040\]
\(V_B = 0\) when all M estimates are identical — missingness had no effect on the result. \(V_B\) is large when the imputed values pulled the analysis in different directions — the missing data were influential.
The plot below shows both sources of uncertainty. Each row is one imputed analysis: the dot is the estimate and the bar is the 95% CI. The pooled row combines both.
The three blue dots occupy different positions — that spread is \(V_B\). The bars are similar in length — that average width is \(\sqrt{V_W}\). The pooled red bar is slightly wider than any single blue bar, because \(V_T = V_W + V_B(\text{adjusted})\) exceeds \(V_W\) alone. The extra width is the price of missing data.
1.5 Step 4 — Total variance V_T
The two components are combined with a small correction for using only M imputations rather than infinitely many (Formula 9.4):
\[V_T = V_W + V_B + \frac{V_B}{M} = V_W + \left(1 + \frac{1}{M}\right)V_B\]
For our numbers:
\[V_T = 0.7957 + 0.040 + \frac{0.040}{3} = 0.7957 + 0.040 + 0.0133 = 0.8490\]
\[SE_{pooled} = \sqrt{V_T} = \sqrt{0.8490} = 0.921\]
The \(V_B/M\) term shrinks as M grows. When M is large, \((1 + 1/M) \approx 1\) and the correction becomes negligible — one reason to prefer M = 20 or more in practice rather than the older default of M = 5.
1.6 Step 5 — Fraction of missing information λ
The fraction of missing information (FMI) summarises how much of the total uncertainty is due to the missingness:
\[\lambda = \frac{(1 + 1/M)\cdot V_B}{V_T} = \frac{0.040 + 0.0133}{0.8490} = 0.063\]
\(\lambda = 0.063\) means 6% of the total variance in this estimate comes from missing data. When \(\lambda\) is small, MI and CC will agree closely; when \(\lambda\) is large (above 0.30), the missing data are heavily influencing the result and more imputations are needed.
The stacked bar below decomposes \(V_T\) visually:

1.7 Step 6 — Degrees of freedom and the confidence interval
The pooled test statistic follows a t-distribution. Rubin’s original formula for the degrees of freedom was (Formula 9.8):
\[df_{Old} = \frac{M - 1}{\lambda^2} = \frac{2}{0.063^2} = 507\]
Barnard & Rubin (1999) proposed an adjusted formula for small samples (Formula 9.9):
\[df_{Adj} = \frac{df_{Old} \cdot df_{Obs}}{df_{Old} + df_{Obs}}\]
where \(df_{Obs}\) adjusts the observed degrees of freedom \((n - k)\) by \(\lambda\) (Formula 9.10):
\[df_{Obs} = \frac{n-k+1}{n-k+3} \cdot (n-k)(1-\lambda) = \frac{149}{151} \times 148 \times 0.937 = 136.9\]
\[df_{Adj} = \frac{507 \times 136.9}{507 + 136.9} = 107.8\]
The mice package uses the adjusted formula. R’s pool() and SPSS differ here — SPSS uses \(df_{Old}\), producing a larger df and a slightly smaller p-value. The 95% CI is (Formula 9.11):
\[\bar{\theta} \pm t_{df_{Adj},\,0.975} \times SE_{pooled} = 1.971 \pm 1.982 \times 0.921 = (0.15,\; 3.79)\]
2 Worked Example: MI on a Simulated Cohort
A cohort of n = 150 patients receives control or treatment, age is recorded, and systolic blood pressure (SBP) is measured at follow-up. Older patients (age > 60) have a 30% probability of missing the follow-up measurement; younger patients 15%. Missingness depends on observed age — a MAR mechanism.
2.1 Explore the missing data
cat("Total rows:", nrow(dat), "\n")Total rows: 150
cat("Missing SBP:", sum(is.na(dat$sbp)),
" (", round(mean(is.na(dat$sbp)) * 100), "%)\n\n")Missing SBP: 22 ( 15 %)
# Is age associated with missingness?
t.test(age ~ is.na(sbp), data = dat)
Welch Two Sample t-test
data: age by is.na(sbp)
t = -1.785, df = 26.778, p-value = 0.0856
alternative hypothesis: true difference in means between group FALSE and group TRUE is not equal to 0
95 percent confidence interval:
-9.1825696 0.6403821
sample estimates:
mean in group FALSE mean in group TRUE
54.12891 58.40000
Age is significantly higher among those with missing SBP — confirming the MAR pattern. Complete case analysis would systematically under-represent older patients.
2.2 Run mice
imp <- mice(dat[, c("sbp", "group", "age")],
m = 5, method = "pmm", seed = 42, printFlag = FALSE)
impClass: mids
Number of multiple imputations: 5
Imputation methods:
sbp group age
"pmm" "" ""
PredictorMatrix:
sbp group age
sbp 0 1 1
group 1 0 1
age 1 1 0
mice uses predictive mean matching (pmm) — for each missing value it draws a plausible observed value from the donors whose predicted value is closest. Age and group are used as predictors for the missing SBP.
2.3 Analyse and pool
fits <- with(imp, lm(sbp ~ group + age))
pooled <- pool(fits)
summary(pooled, conf.int = TRUE) term estimate std.error statistic df p.value
1 (Intercept) 106.5864498 2.33445482 45.657962 26.18058 1.718011e-26
2 groupTreatment -5.5147440 0.81743910 -6.746367 22.48094 7.891332e-07
3 age 0.2470007 0.04267344 5.788161 22.80351 6.979910e-06
2.5 % 97.5 % conf.low conf.high
1 101.7895195 111.3833801 101.7895195 111.3833801
2 -7.2079086 -3.8215793 -7.2079086 -3.8215793
3 0.1586819 0.3353195 0.1586819 0.3353195
The groupTreatment row is the primary quantity of interest: the pooled treatment effect, its pooled SE, the Barnard-Rubin degrees of freedom, the p-value, and the 95% CI.
2.4 Verify the formulas by hand
fit_list <- lapply(1:5, function(i) lm(sbp ~ group + age, data = complete(imp, i)))
ests <- sapply(fit_list, function(f) coef(f)["groupTreatment"])
ses <- sapply(fit_list, function(f)
summary(f)$coefficients["groupTreatment", "Std. Error"])
cat("Estimates per imputation:", round(ests, 3), "\n")Estimates per imputation: -5.415 -5.345 -5.027 -5.538 -6.249
cat("SEs per imputation: ", round(ses, 3), "\n\n")SEs per imputation: 0.637 0.646 0.657 0.655 0.658
M <- 5
Q_bar <- mean(ests)
V_W <- mean(ses^2)
V_B <- var(ests)
V_T <- V_W + (1 + 1/M) * V_B
SE_p <- sqrt(V_T)
lam <- (1 + 1/M) * V_B / V_T
cat("Pooled estimate Q_bar: ", round(Q_bar, 4), "\n")Pooled estimate Q_bar: -5.5147
cat("Within variance V_W: ", round(V_W, 4), "\n")Within variance V_W: 0.4232
cat("Between variance V_B: ", round(V_B, 4), "\n")Between variance V_B: 0.2041
cat("Total variance V_T: ", round(V_T, 4), "\n")Total variance V_T: 0.6682
cat("SE_pooled: ", round(SE_p, 4), "\n")SE_pooled: 0.8174
cat("FMI lambda: ", round(lam, 4), "\n")FMI lambda: 0.3666
These values match summary(pool(fits)) — the pooled estimate, SE, and FMI are identical.
2.5 Compare to complete case
fit_cc <- lm(sbp ~ group + age, data = dat[!is.na(dat$sbp), ])
cc_est <- coef(fit_cc)["groupTreatment"]
cc_se <- summary(fit_cc)$coefficients["groupTreatment", "Std. Error"]
cat("Complete case: estimate =", round(cc_est, 3), " SE =", round(cc_se, 3), "\n")Complete case: estimate = -5.647 SE = 0.712
cat("Pooled (MI): estimate =", round(Q_bar, 3), " SE =", round(SE_p, 3), "\n")Pooled (MI): estimate = -5.515 SE = 0.817
Under MAR with age included in both the imputation model and the analysis, the estimates are similar. The MI standard error is smaller — it recovers information from the rows that had missing SBP but observed age and group.
3 Guided Practice
Compute Rubin’s rules by hand for M = 5 imputations. The numbers are given; apply the formulas step by step.
R is loading — exercises become interactive once the status bar disappears.
- Task 1:
mean(ests) - Task 2:
V_W <- mean(ses^2)andV_B <- var(ests) - Task 3:
V_T <- V_W + (1 + 1/M) * V_B;SE_p <- sqrt(V_T);lambda <- (1 + 1/M) * V_B / V_T - Task 4:
qt(0.975, df = 50)≈ 2.009; CI =Q_bar ± qt(0.975, 50) * SE_p
4 Retrieval Challenge
M <- 10
Q_bar <- mean(ests_ch) # 0.839
V_W <- mean(ses_ch^2) # 0.0530
V_B <- var(ests_ch) # 0.00268
V_T <- V_W + (1 + 1/M) * V_B # 0.0559
SE_p <- sqrt(V_T) # 0.2365
lambda <- (1 + 1/M) * V_B / V_T # 0.0527
df_old <- (M - 1) / lambda^2 # 3241
df_obs <- ((n-k+1)/(n-k+3)) * (n-k) * (1 - lambda) # 71.1
df_adj <- df_old * df_obs / (df_old + df_obs) # 70.0
t_val <- Q_bar / SE_p # 3.55
p_val <- 2 * pt(-abs(t_val), df = df_adj) # 0.001
ci_lo <- Q_bar - qt(0.975, df_adj) * SE_p # 0.37
ci_hi <- Q_bar + qt(0.975, df_adj) * SE_p # 1.31
cat("Q_bar =", round(Q_bar, 3), " SE =", round(SE_p, 3),
"\n95% CI: (", round(ci_lo, 3), ",", round(ci_hi, 3), ")",
"\np-value:", round(p_val, 4))With λ = 0.053, missing data account for only 5% of total variance — M = 10 is more than adequate here.
5 Why-Questions
Under MAR, yes — if the variable driving missingness is included as a covariate, CC is unbiased. The worked example shows this: both CC and MI give similar estimates because age is in both the imputation model and the regression.
The advantage of MI is efficiency: MI uses all n observations (with imputed outcomes) rather than only the complete cases, reducing the SE. The gain is roughly proportional to the fraction missing and \(\lambda\). With 25% missing and \(\lambda = 0.10\), MI gives about an 8% narrower CI. With 40% missing and \(\lambda = 0.30\), the gain becomes substantial.
CC also fails when MAR is violated — when the missingness variable is the outcome itself. Dropping missing SBP rows biases the mean of SBP even after conditioning on age, because the conditional distribution changes.
Single imputation treats the imputed values as if they were observed data — the SE from the single complete analysis does not account for the uncertainty in those imputed values. The resulting CI is too narrow and the p-value too small.
Multiple imputation with M datasets quantifies this extra uncertainty through \(V_B\): the more the M estimates vary, the wider the pooled CI. With a single imputation, \(V_B\) is undefined (you cannot compute the variance of one number), so the between-imputation uncertainty is silently dropped. The analysis looks like a complete dataset when it is not.
Rubin’s rules apply to any quantity that is approximately normally distributed in large samples: regression coefficients, mean differences, and log odds ratios all qualify directly.
Quantities that are not normally distributed need transformation first:
- Odds ratios: pool on the log scale (log OR), then exponentiate the pooled estimate and CI back
- Correlation coefficients: apply Fisher’s z-transformation (\(z = \tanh^{-1}(r)\)), pool on z, then transform back
- Proportions: pool on the logit scale
The mice package handles this automatically for standard model classes — with(imp, glm(..., family = binomial)) followed by pool() pools log odds ratios internally. Manual transformation is only needed for derived quantities computed outside a model (for example, a predicted probability computed from a linear combination of pooled coefficients).
6 Spaced Review
Spaced review 1: \[V_T = V_W + \left(1 + \frac{1}{M}\right) V_B\]
Only the \((1 + 1/M)V_B\) term changes with M: - M = 5: \(V_T = 0.60 + 1.20 \times 0.08 = 0.696\) - M = 10: \(V_T = 0.60 + 1.10 \times 0.08 = 0.688\)
The reduction here is small (0.008) because \(V_B\) is modest relative to \(V_W\). When \(\lambda\) is high, the between component dominates and doubling M has a larger effect.
Spaced review 2:
- MCAR — sensor failure is random, unrelated to the glucose level or any other measured variable. CC is valid.
- MAR — dropout depends on PHQ-9, which is observed and included in the imputation model. MI under MAR is valid.
- MNAR — whether the patient attends depends on their current pain score, which is the missing value. Neither CC nor standard MI is guaranteed unbiased; sensitivity analyses are required.
| Quantity | Formula | Meaning |
|---|---|---|
| \(\bar{\theta}\) | \(\tfrac{1}{M}\sum \hat{\theta}_i\) | Pooled point estimate |
| \(V_W\) | \(\tfrac{1}{M}\sum SE_i^2\) | Typical within-dataset variance |
| \(V_B\) | \(\tfrac{1}{M-1}\sum(\hat{\theta}_i - \bar{\theta})^2\) | Extra variance from missing data |
| \(V_T\) | \(V_W + (1 + \tfrac{1}{M})V_B\) | Total pooled variance |
| \(\lambda\) | \((1+\tfrac{1}{M})V_B\;/\;V_T\) | Fraction of missing information |
| 95% CI | \(\bar{\theta} \pm t_{df_{Adj}} \cdot \sqrt{V_T}\) | Pooled confidence interval |
mice workflow in R:
imp <- mice(dat, m = 20, method = "pmm", seed = 42)
fits <- with(imp, lm(y ~ x1 + x2))
pooled <- pool(fits)
summary(pooled, conf.int = TRUE)Heymans, M.W. & Eekhout, I. (2024). Applied Missing Data Analysis with SPSS and R Studio. bookdown.org/mwheymans/bookmi/ — Chapter 9 for Rubin’s rules with full numerical examples.
van Buuren, S. (2018). Flexible Imputation of Missing Data (2nd ed.). Chapman & Hall/CRC. stefvanbuuren.name/fimd/ — Chapters 2–4 for theory, Chapter 5 for the mice workflow.