Skip to content

Method Selection Guide

Choosing the right imputation method depends on your data characteristics, computational resources, and use case. This guide helps you make an informed decision.

Decision Tree

graph TD
    A[Start: Need Imputation] --> B{Time Series Data?}
    B -->|Yes| C{Need Causality?}
    B -->|No| D{Dataset Size?}

    C -->|Forward| E[LOCF]
    C -->|Backward| F[NOCB]
    C -->|Both| G[KNN or MICE]

    D -->|Small <1K rows| H{Missing Pattern?}
    D -->|Medium 1K-100K| I{Computational Budget?}
    D -->|Large >100K| J{Resources Available?}

    H -->|MCAR| K[Mean/Median]
    H -->|MAR| L[KNN or Regression]
    H -->|Complex| M[MICE or MissForest]

    I -->|Low| N[KNN or Regression]
    I -->|Medium| O[MICE or PMM]
    I -->|High| P[MissForest or Autoencoder]

    J -->|Limited| Q[Mean or Median]
    J -->|Moderate| R[KNN]
    J -->|High| S[Autoencoder]

By Use Case

1. Quick Baseline / Prototyping

Recommended: Mean or Median Imputation

from imputation_methods import MeanImputer, MedianImputer

# For normally distributed data
imputer = MeanImputer()

# For skewed data or with outliers
imputer = MedianImputer()

df_imputed = imputer.impute(df)

When to use: - Initial data exploration - Establishing performance baseline - Computational resources are limited - Missingness is <10%

Limitations: - Poor accuracy for MAR/MNAR data - Reduces variance - Ignores feature relationships

2. Time Series / Sensor Data

Recommended: LOCF, NOCB, or KNN

from imputation_methods import LOCFImputer, NOCBImputer, KNNImputer

# For slowly changing variables
imputer = LOCFImputer()

# For backward filling
imputer = NOCBImputer()

# For better accuracy with multiple sensors
imputer = KNNImputer(n_neighbors=5)

df_imputed = imputer.impute(df)

Selection criteria: - LOCF: Slowly changing values, forward causality - NOCB: Rare, mainly for specialized applications - KNN (k=3-7): Multiple correlated sensors, higher accuracy needed

Example:

# Temperature sensor with 15-minute intervals
if time_interval <= '15min' and change_rate == 'slow':
    imputer = LOCFImputer()
elif num_sensors > 3:
    imputer = KNNImputer(n_neighbors=5)

3. Machine Learning Pipelines

Recommended: KNN, MICE, or MissForest

from imputation_methods import KNNImputer, MICEImputer, MissForestImputer

# General purpose - good balance
imputer = KNNImputer(n_neighbors=5)

# For complex relationships
imputer = MICEImputer(random_state=42)

# For non-linear patterns (slower)
imputer = MissForestImputer(random_state=42)

Selection criteria:

Method When to Use Pros Cons
KNN Linear/moderate relationships Fast, reliable Sensitive to scaling
MICE Multiple missing variables Principled, flexible Slower, may not converge
MissForest Non-linear relationships Very accurate Very slow

Pipeline example:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier

# Custom imputation step
class ImputationStep:
    def __init__(self, imputer):
        self.imputer = imputer

    def fit(self, X, y=None):
        return self

    def transform(self, X):
        return self.imputer.impute(X)

pipeline = Pipeline([
    ('impute', ImputationStep(KNNImputer(n_neighbors=5))),
    ('scale', StandardScaler()),
    ('classify', RandomForestClassifier())
])

pipeline.fit(X_train, y_train)

4. Statistical Analysis / Research

Recommended: MICE or PMM

from imputation_methods import MICEImputer, PMMImputer

# Standard approach in statistics
imputer = MICEImputer(random_state=42)

# More robust to model misspecification
imputer = PMMImputer(n_neighbors=5, random_state=42)

df_imputed = imputer.impute(df)

Why these methods: - Widely accepted in statistics literature - Preserve distributions - Account for uncertainty (in full MICE implementation) - PMM guarantees realistic values

Important: For proper statistical inference, use multiple imputation (create multiple imputed datasets). This library provides single imputation; for multiple imputation, consider the R mice package or the Python miceforest package.

5. High-Dimensional Data

Recommended: Bayesian PCA, SoftImpute, or Autoencoder

from imputation_methods import PPCAImputer, SoftImputeImputer, AutoencoderImputer

# For low-rank structure (e.g., recommendations)
imputer = SoftImputeImputer(max_iter=100)

# Probabilistic approach (n_components is capped at n_columns - 1)
imputer = PPCAImputer(n_components=10)

# For very large datasets with non-linearity
imputer = AutoencoderImputer(
    hidden_layer_sizes=(50, 20, 50),
    max_iter=200,
    random_state=42
)

df_imputed = imputer.impute(df)

Choosing components:

from sklearn.decomposition import PCA
from imputation_methods import MeanImputer

# PCA needs complete data: fill with the mean first. PPCAImputer
# standardizes columns, so standardize here too.
df_filled = MeanImputer().impute(df)
df_filled = (df_filled - df_filled.mean()) / df_filled.std()

# Determine good n_components
pca = PCA()
pca.fit(df_filled)

# Find number of components explaining 90% variance
cumsum = np.cumsum(pca.explained_variance_ratio_)
n_components = int(np.argmax(cumsum >= 0.90) + 1)

imputer = PPCAImputer(n_components=n_components)

6. Production Systems

Recommended: KNN or Mean/Median

from imputation_methods import KNNImputer, MeanImputer

# For real-time systems
imputer = MeanImputer()  # Fastest

# For batch processing with higher accuracy
imputer = KNNImputer(n_neighbors=5)

# Save the imputer configuration for consistency (imputers hold no fitted
# state: impute() re-estimates everything from the data it receives)
import joblib
joblib.dump(imputer, 'imputer_model.pkl')

Production considerations: - Latency: Mean/Median < KNN < MICE < MissForest - Memory: Most methods have low memory footprint - Consistency: Use the same imputer configuration for training and serving - Monitoring: Track missingness patterns over time

Production checklist:

import time
import logging

class ProductionImputer:
    def __init__(self, imputer, timeout=10.0):
        self.imputer = imputer
        self.timeout = timeout
        self.logger = logging.getLogger(__name__)

    def impute(self, df):
        start = time.time()

        try:
            # Validate input
            assert not df.empty, "Empty dataframe"
            missing_pct = df.isna().sum().sum() / df.size
            self.logger.info(f"Missing: {missing_pct:.2%}")

            # Impute with timeout protection
            result = self.imputer.impute(df)

            elapsed = time.time() - start
            if elapsed > self.timeout:
                self.logger.warning(f"Imputation took {elapsed:.2f}s")

            return result

        except Exception as e:
            self.logger.error(f"Imputation failed: {e}")
            # Fallback to mean imputation
            return MeanImputer().impute(df)

By Data Characteristics

Missing Data Pattern

MCAR (Missing Completely at Random)

Any method works, but simple ones are often sufficient:

from imputation_methods import MeanImputer, MedianImputer

# Choose based on distribution
if data_is_normal:
    imputer = MeanImputer()
else:
    imputer = MedianImputer()

MAR (Missing at Random)

Use methods that leverage relationships:

from imputation_methods import KNNImputer, MICEImputer, RegressionImputer

# For moderate datasets
imputer = KNNImputer(n_neighbors=5)

# For multiple variables with missingness
imputer = MICEImputer()

# For specific feature relationships
imputer = RegressionImputer()

MNAR (Missing Not at Random)

Most difficult; consider domain-specific approaches or models that account for missingness:

# Add missingness indicator
df['was_missing'] = df['feature'].isna().astype(int)

# Then impute
from imputation_methods import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df[['feature']] = imputer.impute(df[['feature']])

Data Size

Small (<1,000 rows):

# Can use any method, prefer simpler ones
from imputation_methods import MeanImputer, KNNImputer, MICEImputer

imputer = MICEImputer()  # Even complex methods are fast

Medium (1,000-100,000 rows):

# Balance accuracy and speed
from imputation_methods import KNNImputer, PMMImputer

imputer = KNNImputer(n_neighbors=5)  # Good default

Large (>100,000 rows):

# Prioritize speed
from imputation_methods import KNNImputer, MeanImputer, MedianImputer

imputer = MeanImputer()  # O(n) complexity

# Or use sampling for complex methods. Imputers keep no fitted state, so
# this imputes only the sampled rows (e.g. for a quick method comparison).
sample_size = 10000
df_sample = df.sample(n=sample_size, random_state=42)
imputer = KNNImputer(n_neighbors=5)
df_sample_imputed = imputer.impute(df_sample)

Missingness Percentage

Low (<10%): - Any method works - Simple methods often sufficient

Moderate (10-30%): - Use model-based methods (KNN, MICE, regression) - Evaluate carefully

High (30-50%): - Use sophisticated methods (MICE, MissForest) - Consider if imputation is appropriate - Validate extensively

Very High (>50%): - ⚠️ Warning: Imputation may not be reliable - Consider dropping variables - Use domain knowledge - Model missingness explicitly

Experimental Comparison

When unsure, compare multiple methods:

from imputation_methods import (
    MeanImputer, KNNImputer, MICEImputer,
    MissForestImputer, rmse
)
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor

# Introduce artificial missingness for validation
df_complete = df.copy()
rng = np.random.default_rng(42)
mask = rng.random(df.shape) < 0.2  # 20% missing completely at random
df_missing = df_complete.copy()
df_missing[mask] = np.nan

methods = {
    'Mean': MeanImputer(),
    'KNN-3': KNNImputer(n_neighbors=3),
    'KNN-5': KNNImputer(n_neighbors=5),
    'MICE': MICEImputer(random_state=42),
    'MissForest': MissForestImputer(random_state=42)
}

results = []
for name, imputer in methods.items():
    # Impute
    df_imputed = imputer.impute(df_missing)

    # Evaluate imputation quality
    imputation_rmse = rmse(
        df_complete.values[mask],
        df_imputed.values[mask]
    )

    # Evaluate downstream task (if applicable)
    model = RandomForestRegressor(random_state=42)
    cv_score = cross_val_score(
        model, df_imputed, y, cv=5, scoring='r2'
    ).mean()

    results.append({
        'method': name,
        'imputation_rmse': imputation_rmse,
        'downstream_r2': cv_score
    })

results_df = pd.DataFrame(results)
print(results_df.sort_values('downstream_r2', ascending=False))

Quick Reference by Scenario

Scenario Top Choice Alternative Avoid
Prototype/Quick Mean/Median KNN MissForest, Autoencoder
Time Series LOCF KNN Mean/Median
ML Pipeline KNN MICE, MissForest Hot Deck
Small Data MICE PMM, MissForest Mean/Median
Large Data Mean/Median KNN (sampled) MissForest, GP
High Dimensional Bayesian PCA SoftImpute KNN
Production Mean/KNN - MissForest, GP
Non-linear MissForest Autoencoder Regression
Preserve Distribution PMM Hot Deck Mean

Hyperparameter Tuning

For methods with hyperparameters:

KNN - Choosing k

from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Ridge

k_values = [3, 5, 7, 10, 15]
best_score = -np.inf
best_k = 5

for k in k_values:
    imputer = KNNImputer(n_neighbors=k)
    X_imputed = imputer.impute(X_train)
    model = Ridge()
    score = cross_val_score(model, X_imputed, y_train, cv=5).mean()

    if score > best_score:
        best_score = score
        best_k = k

print(f"Best k: {best_k} with score: {best_score:.4f}")

Bayesian PCA - Choosing Components

# Try different numbers of components
for n_comp in [2, 5, 10, 15]:
    imputer = PPCAImputer(n_components=n_comp)
    df_imputed = imputer.impute(df_train)
    # Evaluate reconstruction quality
    score = evaluate_reconstruction(df_train, df_imputed)
    print(f"n_components={n_comp}: {score:.4f}")

Next Steps