This reference covers inject_missingness(), a
transformer that takes a complete data frame and injects
NAs under a chosen missingness mechanism from the Rubin
taxonomy — MCAR (completely at random), MAR (at random, driven by an
observed predictor), and MNAR (not at random, driven by the missing
value itself). The returned object carries a "missing_info"
attribute recording exactly which cells were made missing.
inject_missingness()Inject missing values into target columns under a known mechanism.
MAR requires an observed predictor column (which is itself
never made missing); MNAR sets larger numeric values missing more often.
All three mechanisms are calibrated so the realized missing fraction is
approximately prop.
Signature
args(inject_missingness)
#> function (data, mechanism = c("MCAR", "MAR", "MNAR"), prop = 0.1,
#> cols = NULL, predictor = NULL, seed = NULL)
#> NULLWe start from a small complete data frame with a regression structure
so that x1 can serve as a MAR predictor of
y.
df <- simulate_prediction(
n = 500,
coefs = c("(Intercept)" = 1, x1 = 2, x2 = -1),
seed = 1
)$data
head(df)MCAR — missing completely at random
Every cell of the target columns is independently set missing with
probability prop.
mcar <- inject_missingness(df, mechanism = "MCAR", prop = 0.2, seed = 7)
colSums(is.na(mcar))
#> y x1 x2
#> 96 104 108
attr(mcar, "missing_info")$realized_prop
#> [1] 0.2053333
str(attr(mcar, "missing_info"), max.level = 1)
#> List of 6
#> $ mechanism : chr "MCAR"
#> $ prop : num 0.2
#> $ cols : chr [1:3] "y" "x1" "x2"
#> $ n_missing : int 308
#> $ realized_prop: num 0.205
#> $ indicator : logi [1:500, 1:3] FALSE FALSE TRUE TRUE FALSE FALSE ...
#> ..- attr(*, "dimnames")=List of 2MAR — missing at random (driven by observed
x1)
Here only y is targeted, and its missingness probability
depends on the observed predictor x1 — never on
y’s own value.
mar <- inject_missingness(df, mechanism = "MAR", prop = 0.2,
cols = "y", predictor = "x1", seed = 7)
colSums(is.na(mar))
#> y x1 x2
#> 96 0 0
attr(mar, "missing_info")$realized_prop
#> [1] 0.192The dependence on the predictor is visible: rows with missing
y have a systematically different mean x1 than
rows with observed y.
miss_y <- is.na(mar$y)
tapply(df$x1, ifelse(miss_y, "y missing", "y observed"), mean)
#> y missing y observed
#> 0.7204732 -0.1431767MNAR — missing not at random (driven by y
itself)
The probability that y is missing depends on
y’s own (latent) value: larger values are more likely to go
missing.
mnar <- inject_missingness(df, mechanism = "MNAR", prop = 0.2,
cols = "y", seed = 7)
colSums(is.na(mnar))
#> y x1 x2
#> 92 0 0
attr(mnar, "missing_info")$realized_prop
#> [1] 0.184The MNAR signature: the observed (non-missing)
y mean is shifted relative to the full-data mean, because
the largest values were preferentially removed.
An image() of the missing-cell indicator shows where the
NAs landed. Under MCAR they scatter uniformly across all
columns; under MAR/MNAR they concentrate in the single targeted column
y.
op <- par(mfrow = c(1, 3), mar = c(3, 4, 3, 1))
show_pattern <- function(x, ttl) {
m <- is.na(x[, c("y", "x1", "x2")])
image(seq_len(ncol(m)), seq_len(nrow(m)), t(m),
col = c("#ece7f2", "#d95f0e"), axes = FALSE,
xlab = "", ylab = "row", main = ttl)
axis(1, at = seq_len(ncol(m)), labels = colnames(m))
}
show_pattern(mcar, "MCAR")
show_pattern(mar, "MAR (y ~ x1)")
show_pattern(mnar, "MNAR (y ~ y)")