A dataset for a model predicting bike-subsidy adoption has `distance_km` (home-to-work distance) missing entirely for 8% of commuters because of a GPS/onboarding data gap, and another column, `subsidy_code_used`, is missing for 60% of rows because most commuters simply never used a subsidy code. How would you handle each of these differently?
Click or press Enter to reveal the answerThe right approach depends on why the data is missing and how much of it is missing — a modest amount of plausibly-random missingness (like the 8% `distance_km` gap) is often reasonable to impute, while a large block of missingness that actually reflects a real state (like never using a subsidy code) usually means the missingness itself is meaningful and shouldn't be imputed away.
Why “missing” isn’t one thing
- Missing completely at random (MCAR): the missingness has nothing to do with any variable — e.g., a GPS/onboarding sync glitch that dropped
distance_kmfor a random slice of commuters. Safe to impute without introducing bias. - Missing at random (MAR): missingness relates to other observed variables — e.g., commuters who onboarded through a particular employer partner are less likely to have
distance_kmcaptured, but conditional on employer it’s random. Imputation using related features can work well. - Missing not at random (MNAR): the missingness itself carries information — e.g.,
subsidy_code_usedis missing because the commuter simply never redeemed one. Here, “missing” is the signal, and imputing a fake code destroys it.
Handling the distance_km example
With only 8% missing and no obvious reason to think it’s tied to the outcome, reasonable options include:
- Median/mean imputation (simple, but slightly biases variance downward and ignores relationships with other features like
home_city). - Model-based imputation — predicting
distance_kmfrom other features (home_city,employer_subsidy_enrolled, typical trip patterns) — usually more accurate than a single constant. - Adding a
distance_km_was_imputedbinary flag alongside the imputed value, so the model can learn if imputed rows behave differently.
Handling the subsidy_code_used example
Here, missing doesn’t mean “unknown” — it means “no subsidy code was ever used,” which is itself informative (a commuter who never touches the subsidy program looks very different from one who does). The right move is usually to encode it explicitly rather than impute:
df["subsidy_code_used_flag"] = df["subsidy_code_used"].notna().astype(int)
df["subsidy_code_used"] = df["subsidy_code_used"].fillna("none")
Risks of naive imputation
- Mean/median imputation shrinks variance and can weaken real relationships between features.
- Imputing MNAR data (like
subsidy_code_used) as if it were MCAR can inject a systematic bias that actively hurts model performance and interpretability. - Dropping all rows with any missing value can silently shrink your dataset and bias it toward “easy,” fully-observed commuters, especially if missingness correlates with the outcome (e.g., newer commuters who haven’t had time to redeem a subsidy code yet).