Missing Data
Missing data — NaN in NumPy, NaN / NA / None in Pandas — needs explicit handling. Detect with isna; drop, fill, or interpolate before downstream stats / ML.
Detect, drop, fill, interpolate, dtypes
EXAMPLE
import numpy as np
import pandas as pd
# 1) Detect
df = pd.DataFrame({
'name': ['Ada', 'Bo', None, 'Di'],
'age': [32, np.nan, 41, 22],
'score': [85, 90, np.nan, np.nan],
'city': ['Sydney', 'Sydney', None, 'Brisbane'],
})
df.isna() # boolean DataFrame
df.isna().sum() # per column
df.isna().sum(axis=1) # per row
df.isna().any(axis=1) # rows with any missing
df.notna().all(axis=1) # complete rows
# Visualise — quick scan
# df.isna().heatmap(...) # matplotlib / seaborn (or missingno library)
# 2) Drop
df.dropna() # rows with ANY missing
df.dropna(how='all') # rows ALL missing
df.dropna(subset=['age', 'city']) # only consider some cols
df.dropna(axis=1) # drop columns instead of rows
df.dropna(thresh=3) # keep rows with >= 3 non-NA values
# 3) Fill — constant
df.fillna(0)
df.fillna({'age': 0, 'score': 0, 'city': 'Unknown', 'name': 'Anonymous'})
# Fill in place
df.fillna({'age': df['age'].median()}, inplace=True)
# 4) Fill — forward / backward (great for time series)
df['age'].ffill() # propagate last valid value forward
df['age'].bfill() # propagate next valid backward
df['age'].ffill(limit=1) # cap propagation
# 5) Fill — interpolate
s = pd.Series([1, np.nan, np.nan, 4, 5])
s.interpolate() # [1, 2, 3, 4, 5] — linear
s.interpolate(method='time') # for time-indexed series
s.interpolate(method='polynomial', order=2) # quadratic fit
# 6) Fill with statistics
df['age'] = df['age'].fillna(df['age'].median())
df['score'] = df['score'].fillna(df['score'].mean())
df['city'] = df['city'].fillna(df['city'].mode().iloc[0])
# 7) Group-aware fill (impute within a group)
df['age'] = df.groupby('city')['age'].transform(lambda s: s.fillna(s.median()))
# 8) Sentinel values masquerading as missing
# e.g. -999 or 'N/A' from a CSV — coerce them first
df.replace({'age': -999}, np.nan, inplace=True)
df = pd.read_csv('data.csv', na_values=['', 'N/A', '?', '-999'])
# 9) Type-aware missing — nullable dtypes (Pandas 1.0+)
import pandas as pd
s = pd.array([1, 2, None], dtype='Int32') # nullable integer
b = pd.array([True, None, False], dtype='boolean') # nullable boolean
t = pd.array(['a', None, 'c'], dtype='string') # nullable string
# These use pd.NA instead of np.nan — cleaner type semantics.
# 10) sklearn — impute as part of a pipeline
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
pipe = Pipeline([
('impute', SimpleImputer(strategy='median')),
('scale', StandardScaler()),
('clf', RandomForestClassifier()),
])
pipe.fit(X_train, y_train)
# The impute parameters are LEARNED on train and APPLIED on test — no leakage.
# More sophisticated:
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
imp = IterativeImputer(random_state=42)
X_imp = imp.fit_transform(X)
# 11) Don't silently lose data
# `df['x'] + df['y']` propagates NaN. Sum / mean SKIP NaN by default; corr ignores rows with NaN.
# Reduce surprise: explicitly choose drop or fill BEFORE the downstream op.
# 12) When NaN actually means something — make it explicit
# Sometimes 'missing' carries information (the user didn't answer).
# Add an indicator column:
df['age_missing'] = df['age'].isna().astype(int)
df['age'] = df['age'].fillna(df['age'].median())
# 13) Time-series specific
# Resample first, then ffill within a tolerance — common for irregular timestamps:
ts = ts.resample('1H').mean().ffill(limit=2)
# 14) Tools
# - missingno : visualisation (matrix, bars, heatmap)
# - pandas-profiling / ydata-profiling: profile reports include missing-data summaries
# - great_expectations: assert missingness rates in data pipelines
Why it matters
Decide WHY a value is missing before deciding HOW to fill it. Sensor failure (drop / interpolate), user didn’t answer (add an indicator + impute), data not yet collected (forward fill) — each calls for a different strategy.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import pandas as pd df.isna().sum() # count missing per column df = df.dropna(subset=['email']) df['age'] = df['age'].fillna(df['age'].median())Try it Yourself »
Discussion
Loading…