Classical machine learning
Missing values and pipelines
A gap in the data can mean something, so flag it and fill it. The fill value must be learned on training data only, which is why imputers belong inside a pipeline.
Real data has gaps, and the gaps often carry information. A missing last-payment date can mean the customer never paid. Filling every gap with the average hides that signal. This page covers how to flag missing values and fill them, and why the fill value must be learned inside the training pipeline.
Concept
Before you start: a pipeline chains preprocessing steps and a model into one object. You fit it on training data once, then apply the same fitted steps to every new input. That keeps training and serving identical.
Missingness can be a signal
Three common strategies, in order of usefulness:
- Indicator plus impute. Add a flag column such as
is_missing_last_payment, then fill the numeric column with the median. Categorical columns can use a"__MISSING__"level. - Let the model handle it. LightGBM and XGBoost learn a default direction for missing values. Still make the serving path match.
- Fill with a domain value. Use
0for counts where zero means "none". Do not use0for a continuous sensor, where zero is a real reading.
Concept
Missingness as signal means the pattern of what is absent predicts the label. An explicit indicator usually helps more than a clever imputer, and it costs nothing to add.
Walking through a median fill
Suppose the training data has one missing value. Fit the median on training rows only, then apply it everywhere:
import numpy as np
train = np.array([12.4, np.nan, 9.0, 15.0])
test = np.array([np.nan, 30.0])
median = np.nanmedian(train) # fitted on training data only
print("fill value:", median)
train_filled = np.where(np.isnan(train), median, train)
test_filled = np.where(np.isnan(test), median, test)
missing_flag = np.isnan(test).astype(int) # keep the signal too
print(train_filled)
print(test_filled, missing_flag)Output:
fill value: 12.4
[12.4 12.4 9. 15. ]
[12.4 30. ] [1 0]The median of the three known training values is 12.4, and that number is used for both the training gap and the test gap. The test set never influenced it. The flag records which test row was missing.
Keep the imputer inside the pipeline
If you compute the mean on the full dataset, the test rows shape the fill value, and your validation score is optimistic. The fix is to put the imputer and the scaler inside a pipeline, so they are fitted only when the model is fitted.
Example
Sketch, not run here: a scikit-learn pipeline that imputes and scales the numeric columns, one-hot encodes the categorical columns, and then fits a logistic model. Fitting clf on training data fits every step. Serialize clf as one object, so production cannot forget the imputer.
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
numeric = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
])
categorical = Pipeline([
("impute", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
])
preprocess = ColumnTransformer([
("num", numeric, ["age", "logins_14d", "spend_30d"]),
("cat", categorical, ["plan", "country"]),
])
clf = Pipeline([
("prep", preprocess),
("model", LogisticRegression(max_iter=1000, class_weight="balanced")),
])
clf.fit(X_train, y_train)Common mistakes
- Filling continuous sensors with zero. Zero may be a real reading. Use the median and a flag.
- Imputing before the split. The fill then uses information from the test rows.
- Dropping the flag. A filled value without the indicator hides the fact that the value was missing.
Check yourself
- Why add a missing-indicator next to the filled value? Because the missingness can predict the label, and filling alone hides it.
- Why fit the median on training rows only? Otherwise the test rows influence the fill value, which leaks information into validation.
Key takeaways
- A missing value can be a signal, so keep a flag next to the fill.
- Choose the fill by meaning: median for most numbers,
0only when zero means none. - Fit imputers and scalers inside a pipeline, on training data only.
- Serialize the whole pipeline, so serving applies the same fitted steps.
Further learning
The fill is only correct if the time windows behind each feature are correct too: point-in-time features.
Related
Features and data preparation
Turning raw data into model inputs without leaking the future. Covers scaling, encoding, missing values, time windows, target encoding and class imbalance, and keeping training and serving identical.
Data leakage
Leakage is when a model learns from information it would not have at prediction time. Offline scores look excellent, then production falls apart. Here is how to find it.
ML in production
Feature stores, training-serving skew, monitoring for drift and the lifecycle of a deployed model.