← All Posts
Deep Learning · Data Preparation and Preprocessing · Handling Missing Data

Missingness Mechanisms: MCAR, MAR, MNAR

A blank tells us that a value is unavailable. It does not tell us what the value would have been, or why it is missing. Before choosing how to fill it, distinguish the missingness mechanism—the process that hides values—from the method used to analyze the incomplete data.

We will follow one possibly missing column, monthly usage $Y$, and one fully recorded column, account plan $Z$. The account plan is available even when usage is missing. In a simulation we can specify the chance of hiding usage; in a real dataset we usually have to reason about that chance from collection procedures and partial evidence.

Three rules for hiding the same kind of value

Imagine accounts with usage 10 or 100 on either a Basic or Pro plan. The figure specifies missingness probabilities for those four combinations. It shows the rules that generate gaps, not a way to classify gaps by their appearance.

Chance that usage is missing
MCAR · same chance

Usage 10 / usage 100

Basic25% / 25%
Pro25% / 25%

Neither plan nor usage changes the hiding probability.

MAR · recorded plan matters

Usage 10 / usage 100

Basic10% / 10%
Pro50% / 50%

Within the same plan, both usage values have the same chance.

MNAR · usage still matters

Usage 10 / usage 100

Basic10% / 60%
Pro10% / 60%

Even within a plan, high usage is more likely to be hidden.

These are constructed probability rules, not observed missing-value counts. The hidden usage is known to the simulator when it generates a gap; it is unavailable to the analyst afterward.

MCAR: missing completely at random

Under the first rule, each account’s usage has a 25% chance of being hidden, independently of its usage and plan. Repeatedly applying this rule does not systematically favor a particular usage value or plan among the observed cases.

A particular finite sample can still be unbalanced by chance. “Representative under the sampling mechanism” does not mean the observed mean must equal the complete-data mean in every run. You also lose information: discarding randomly missing cases leaves fewer observations for estimation.

A random collection failure can make MCAR plausible only if its chance is unrelated to the relevant data. A failure concentrated in a particular device version, location, or workload is a different mechanism even if it was unintentional.

MAR: random after accounting for recorded information

Under the second rule, Pro accounts lose usage more often than Basic accounts. But within either plan, a usage of 10 and a usage of 100 have the same hiding probability. Knowing the recorded plan removes the remaining dependence on the hidden usage.

“At random” here is conditional. The observed accounts can overrepresent Basic plans, so a pooled mean need not represent the original account population. An analysis that appropriately accounts for plan can use the observed cases within each plan under the MAR assumption.

If plan and usage are related, missingness can be associated with usage overall and still be MAR after conditioning on plan. Conversely, using plan in an imputer does not establish MAR. The assumption says that no additional dependence on the unseen usage remains once the recorded information is known.

MNAR: the unseen value still affects its chance of being seen

Under the third rule, high usage is more likely to be hidden within each plan. Among observed Basic accounts, high-usage cases are underrepresented; the same problem remains among observed Pro accounts. Conditioning on plan does not remove it.

An imputer that simply copies the observed within-plan distribution would inherit that distortion. A missingness indicator may help a prediction model use the fact that an input is absent, but it does not reveal the absent value or identify the missing-data distribution.

Possible responses include collecting additional information about the recording process, obtaining a follow-up sample of missing cases, and comparing conclusions under plausible alternative assumptions about those cases. That last approach is sensitivity analysis: ask how the result changes when the unseen values differ from the initial imputation model.

The three rules in probability notation

Let $M=1$ mean that $Y$ is missing and $M=0$ mean it is observed. The expression $P(M=1\mid Y,Z)$ is the chance that usage is hidden for an account with particular usage and plan values. For this one-missing-column setup, with $Z$ fully observed:

$$\begin{aligned} \text{MCAR: }&P(M=1\mid Y,Z)=P(M=1),\\ \text{MAR: }&P(M=1\mid Y,Z)=P(M=1\mid Z). \end{aligned}$$

Under MNAR, the second equality fails: dependence on $Y$ remains after conditioning on $Z$. MCAR is a special case of MAR. With several partially observed columns, the definitions must refer to the observed and missing values under each missingness pattern; Seaman and colleagues explain the precise formulation.

What can the observed table tell us?

You can inspect whether missingness is associated with recorded plan, date, device, region, or other available columns. Strong evidence of such an association can contradict MCAR relative to those variables.

It does not establish MAR. Missingness might depend on both plan and the still-unseen usage. Equally, similar missing rates across the observed groups do not prove MCAR: the third rule in the figure can hide large values at the same rate in both plans.

In general, MAR and MNAR cannot be distinguished from the incomplete observed data alone without additional assumptions or information. Treat the mechanism as a reasoned model of the collection process, not a label a generic missingness test can certify.

Two different mechanisms can produce the same observations

To see the limitation directly, fix one plan so there is no remaining recorded group to condition on. Consider a population in which 30% of accounts have recorded usage 10, 30% have recorded usage 100, and 40% have missing usage. These are exact probabilities in a constructed model, not noisy sample estimates. The mean among observed accounts is 55.

What fraction of the missing accounts have usage 100? Call that unknown fraction $q$. The recorded data give us no answer: all those accounts have the same blank in the usage column. Two choices illustrate the consequence:

Identical recorded data, different hidden distributions
Usage 10Usage 100
Model A · MCAR within this plan Model A: identical usage proportions among observed and missing accountsAmong observed accounts, 50 percent have usage 10 and 50 percent have usage 100. The assumed missing-account proportions are also 50 and 50 percent. Observed accounts · 60% of all accounts50%50% Missing accounts · 40% of all accounts50%50%The lower bar is an assumption.
Overall high-usage share: 50%
Population mean: 55
Model B · MNAR within this plan Model B: change only the unobserved usage distributionAmong observed accounts, 50 percent have usage 10 and 50 percent have usage 100. Among missing accounts, assume 20 percent have usage 10 and 80 percent have usage 100. Observed accounts · 60% of all accounts50%50% Missing accounts · 40% of all accounts20%80%The lower bar is an assumption.
Overall high-usage share: 62%
Population mean: 65.8
Model B hides 21.1% of low-usage accounts and 51.6% of high-usage accounts. Its recorded data are identical to Model A’s.
Each bar sums to 100% within its observed or missing group; the groups contain 60% and 40% of accounts respectively. Changing the lower-right bar changes an assumption, not a fitted quantity. At 50%, Model B coincides with Model A. Every setting preserves the same observable distribution.

In Model B, 40% × 80% = 32% of all accounts have high usage hidden. Add the 30% whose high usage is observed: 62% have high usage altogether. The full-population mean is therefore $0.38\times10+0.62\times100=65.8$, even though the observed mean is still 55.

Verify that Model B really has a different hiding mechanism

Of all accounts, 8% have hidden low usage and 30% have observed low usage. The hiding probability for a low-usage account is $8/(8+30)\approx21.1\%$. For high usage it is $32/(32+30)\approx51.6\%$. Those unequal probabilities establish MNAR within this fixed plan. In Model A, the corresponding probability is $20/(20+30)=40\%$ for both values.

This is stronger than saying two models could happen to generate the same small sample. Their entire observable probability distributions agree: recorded 10 with probability 0.3, recorded 100 with probability 0.3, or a blank with probability 0.4. More accounts measured in the same way cannot reveal which hidden distribution is correct.

Turn an untestable assumption into an explicit comparison

The slider is a small sensitivity analysis. Keep what was observed fixed, vary the unobserved distribution, and recalculate the quantity of interest. Here the hidden-group mean is $10(1-q)+100q=10+90q$, so

$$\begin{aligned} \text{population mean}&=0.6\times55+0.4\times(10+90q)\\ &=37+36q. \end{aligned}$$

A ten-percentage-point increase in $q$ raises the population mean by 3.6 usage units. If usage really can take only 10 or 100, the mathematical extremes $q=0$ and $q=1$ bound this mean between 37 and 73. This is a range across possible hidden distributions, not a confidence interval.

Choose plausible assumptions using knowledge of collection failures or follow-up measurements, and show whether the substantive conclusion changes. Do not select the assumption that gives the preferred mean. With several plans, specify and combine the missing-group distributions within each plan. The pattern-mixture formulation develops this separation between observed information and assumptions about missing values.

Python: reproduce both observable distributions and means
from math import isclose

for q in [0.5, 0.8]:
    # Joint probabilities: all four cells sum to one.
    observed_low, observed_high = 0.3, 0.3
    missing_low, missing_high = 0.4 * (1 - q), 0.4 * q
    low = observed_low + missing_low
    high = observed_high + missing_high
    recorded = (observed_low, observed_high, missing_low + missing_high)
    assert all(isclose(a, b) for a, b in zip(recorded, (0.3, 0.3, 0.4)))
    mean = 10 * low + 100 * high
    assert isclose(mean, 37 + 36 * q)
    print(f"q={q:.1f}: mean={mean:.1f}; "
          f"P(missing | low)={missing_low / low:.3f}; "
          f"P(missing | high)={missing_high / high:.3f}")
# q=0.5: mean=55.0; P(missing | low)=0.400; P(missing | high)=0.400
# q=0.8: mean=65.8; P(missing | low)=0.211; P(missing | high)=0.516

C++: count the gaps without overinterpreting them

This small audit compares two recorded groups. It preserves the numerator and denominator so a rate based on four rows is not mistaken for a precise population estimate.

Run the group-count audit
#include <array>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <vector>

struct Counts { int missing = 0; int total = 0; };
std::array<Counts, 2> countMissing(
    const std::vector<double>& values, const std::vector<int>& group) {
    if (values.size() != group.size())
        throw std::invalid_argument("values and groups must align");
    std::array<Counts, 2> counts{};
    for (std::size_t i = 0; i < values.size(); ++i) {
        if (group[i] != 0 && group[i] != 1)
            throw std::invalid_argument("group must be 0 or 1");
        auto& c = counts[group[i]];
        ++c.total;
        c.missing += std::isnan(values[i]);
    }
    return counts;
}

int main() {
    const double NA = std::numeric_limits<double>::quiet_NaN();
    auto counts = countMissing({1, NA, 3, NA, 5, NA, 7, 8},
                              {0,  0, 0,  1, 1,  1, 1, 0});
    for (int g = 0; g < 2; ++g) {
        const auto& c = counts[g];
        std::cout << "group " << g << ": " << c.missing << "/" << c.total;
        if (c.total)
            std::cout << " = " << std::fixed << std::setprecision(2)
                      << double(c.missing) / c.total;
        else
            std::cout << " (no rows; rate unavailable)";
        std::cout << "\n";
    }
}
group 0: 1/4 = 0.25
group 1: 2/4 = 0.50

The sample rates differ, but one extra missing value among four rows creates the entire difference. This calculation describes the sample; it is not a test or proof of a mechanism. With larger data, quantify uncertainty and account for how many associations you explored. Follow up with the process that collected the values.

The code reports an absent group as “rate unavailable,” rather than as a 0% missing rate. It also rejects misaligned columns and invalid group labels, which would otherwise make a superficially plausible summary misleading.

Use the mechanism to guide the analysis question

Estimating a population quantity: if the goal is an overall mean or a regression effect, consider whether the observed cases support that quantity under the assumed mechanism. MCAR is a useful sufficient condition for many complete-case analyses; some conditional analyses can remain valid under weaker, model-specific conditions. The deletion chapter separates those cases.

Predicting new outcomes: compare complete preprocessing and prediction recipes on realistically incomplete held-out inputs. Keep imputation fitting inside the training split, and examine performance for different missingness patterns. A method can be a useful predictor without recovering the true missing values.

Representing uncertainty: filling every gap once does not turn it into an observed value. When uncertainty in population estimates matters, multiple imputation can propagate uncertainty under an explicit model. It still requires assumptions; it is not an automatic remedy for MNAR.

Start with the collection story, record the assumptions, and use simple imputation as a transparent predictive baseline when appropriate. MAR alone does not guarantee that any particular KNN or regression imputer is well specified.

References and further reading