Missing Covariates in a Regression (R)ΒΆ

mice β€” and why the outcome must be in the imputation modelΒΆ

The R counterpart to misscov_python.ipynb. The Python notebook builds the joint-model sampler from scratch and shows the central rule: impute the missing predictors conditional on the outcome, or the coefficient attenuates. Here we make the same point with mice, whose predictor matrix controls exactly which variables inform each imputation β€” so we can include the outcome (correct) or exclude it (the attenuation trap) and watch the coefficient move. Data: the airquality regression Temp ~ Ozone + Solar.R + Wind, with Ozone and Solar.R the incomplete covariates.

InΒ [1]:
options(repr.plot.width=10, repr.plot.height=4.5)
.libPaths(c("C:/Users/user/R/win-library/4.6", .libPaths())); suppressMessages(library(mice))
BLUE<-"#2b6cb0"; RED<-"#c53030"; GREEN<-"#2f855a"; GREY<-"#718096"
aq<-read.csv("airquality.csv"); aq$Ozone<-log(aq$Ozone); aq$Solar.R<-log(aq$Solar.R)   # log the skewed concentrations
cat("Temp fully observed; missing covariates -- Ozone:", sum(is.na(aq$Ozone)), " Solar.R:", sum(is.na(aq$Solar.R)),
    "  complete cases:", sum(complete.cases(aq)), "of", nrow(aq), "\n")
Warning message:
"package 'mice' was built under R version 4.6.1"
Temp fully observed; missing covariates -- Ozone: 37  Solar.R: 7   complete cases: 111 of 153 

1. The attenuation trap in miceΒΆ

mice builds a predictor matrix saying which variables impute each incomplete one. Leaving the outcome Temp out of the rows that impute Ozone and Solar.R reproduces the classic mistake β€” filling predictors from the other predictors only β€” and attenuates the Ozone coefficient. Putting Temp back in fixes it. We fit Temp ~ Ozone + Solar.R + Wind both ways and pool.

InΒ [2]:
fitpool <- function(pm){ f<-with(pm, lm(Temp ~ Ozone + Solar.R + Wind)); summary(pool(f)) }
# CORRECT: outcome included (mice's default includes all other columns)
imp_ok <- mice(aq, m=30, printFlag=FALSE, seed=1)
# WRONG: remove Temp from the predictors of Ozone and Solar.R
pred <- make.predictorMatrix(aq); pred[c("Ozone","Solar.R"),"Temp"] <- 0
imp_no <- mice(aq, m=30, predictorMatrix=pred, printFlag=FALSE, seed=1)
ok<-fitpool(imp_ok); no<-fitpool(imp_no); cc<-summary(lm(Temp~Ozone+Solar.R+Wind, data=aq))$coefficients
oz<-function(tab,i) tab[tab[,1]=="Ozone" | rownames(tab)=="Ozone", "estimate"]
cat(sprintf("Ozone coefficient on Temp:\n  mice WITH outcome:    %.2f\n  mice WITHOUT outcome: %.2f   <- attenuated\n  complete-case:        %.2f\n",
    ok$estimate[ok$term=="Ozone"], no$estimate[no$term=="Ozone"], cc["Ozone","Estimate"]))
barplot(c("with\noutcome"=ok$estimate[ok$term=="Ozone"], "without\noutcome"=no$estimate[no$term=="Ozone"],
          "complete\ncase"=cc["Ozone","Estimate"]), col=c(GREEN,RED,BLUE), ylab="Ozone coefficient",
        main="mice: dropping the outcome from the imputation attenuates the coefficient")
cat("Excluding the outcome from the imputation model shrinks the Ozone-Temp association -- exactly the attenuation\n")
cat("the Python notebook shows. mice's default (all other variables predict each) already includes the outcome.\n")
Ozone coefficient on Temp:
  mice WITH outcome:    7.65
  mice WITHOUT outcome: 5.39   <- attenuated
  complete-case:        7.51
Excluding the outcome from the imputation model shrinks the Ozone-Temp association -- exactly the attenuation
the Python notebook shows. mice's default (all other variables predict each) already includes the outcome.
No description has been provided for this image

2. Pooled fit and the efficiency gainΒΆ

With the outcome correctly included, the pooled regression uses all 153 days where complete-case keeps only the fully observed ones. The pooled table reports the coefficients, their Rubin standard errors and the fraction of missing information.

InΒ [3]:
print(ok[,c("term","estimate","std.error","statistic")])
cat(sprintf("\ncomplete-case n = %d;  mice uses all %d rows.\n", sum(complete.cases(aq)), nrow(aq)))
cat("fraction of missing information per term:\n"); print(round(pool(with(imp_ok, lm(Temp~Ozone+Solar.R+Wind)))$pooled$fmi,2))
stripplot(imp_ok, Ozone + Solar.R ~ .imp, pch=20, cex=1.2)
         term   estimate std.error  statistic
1 (Intercept) 55.6242269 4.3921381 12.6644986
2       Ozone  7.6451532 0.9511139  8.0381047
3     Solar.R -0.1153143 0.8602777 -0.1340431
4        Wind -0.3348432 0.1846085 -1.8138020
complete-case n = 111;  mice uses all 153 rows.
fraction of missing information per term:
[1] 0.11 0.19 0.21 0.11
No description has been provided for this image

3. SummaryΒΆ

mice makes the missing-covariate rule operational and visible. Its predictor matrix decides which variables inform each imputation; keeping the outcome Temp in the models for the incomplete Ozone and Solar.R covariates recovers the coefficients, while removing it reproduces the attenuation the from-scratch joint model demonstrates. Because mice's default lets every variable predict every other, it includes the outcome automatically β€” the mistake takes deliberate effort, but it is a common one when people impute a "clean" predictor block separately from the outcome. With the outcome in, the pooled fit uses all 153 airquality days and reports the fraction of missing information.

This is the package view of Congdon's joint missing-covariate model (BMCD 11.3) and PyMC's masked-covariate imputation in misscov_python.ipynb. The rule β€” impute covariates conditional on the outcome β€” is why every serious multiple-imputation workflow includes the analysis outcome among the predictors (and why smcfcs exists, to make imputations substantive-model-compatible). Still under MAR; the non-ignorable case is handled by the selection and pattern-mixture projects next in the arc.