Mean, Median, and Mode Imputation
Simple imputation stores one replacement value per column, learned from the observed training entries. When a later row has a gap, it receives that stored value. Observed entries are left unchanged.
This is a useful predictive baseline because the transformation is cheap and easy to inspect. It does not recover the original missing values, and a filled table does not carry the same statistical information as a fully observed one.
Work through one column before choosing a summary
Take the training column [1, 2, missing, 4, 100, missing]. There are four observed values. The mean adds those four and divides by four; the missing entries do not count as zero. For the median, sort the observed values and average the two middle ones when the count is even:
The value 100 pulls the mean upward. The median remains between 2 and 4. That does not tell us that 100 is an error; it explains how the summaries respond to a large observation.
Observed values: 1, 2, 4, 100.
For the observed sample, the mean minimizes total squared error among constant predictions; the median minimizes total absolute error. This helps explain their behavior, but the best fill for a downstream model still needs to be evaluated as part of that model’s complete pipeline.
For categories, decide what a replacement means
The mode is the most frequent observed category. For [Basic, Pro, Basic, missing], mode imputation fills Basic. This increases the count of an already common category; it is not evidence that the missing account truly belongs to it.
If several categories tie, record a deterministic tie rule. Scikit-learn’s most_frequent strategy chooses the smallest tied value. A dedicated category such as Missing is another legitimate design, covered in constant imputation. It preserves a distinction between observed categories and absent information.
Do not average arbitrary integer category codes: the average of code 1 and code 3 need not represent category 2. Also distinguish a missing entry from a previously unseen but observed category; categorical encoding handles the latter problem separately.
A repeated fill changes the distribution
For a smaller example, the observed values 1, 3, and 5 have mean 3. Their squared deviations from 3 are 4, 0, and 4, totaling 8. Fill two gaps with 3, and the two added deviations are both zero:
Single-value filling creates a concentration at the replacement value and can distort relationships with other columns. A downstream predictor may still use that representation effectively, but treating the filled values as measured observations for standard errors or scientific inference can understate uncertainty. The multiple-imputation chapter addresses that different objective.
Fit once on training data, then transform
For our original training column, store the median 3. If a test batch is [missing, 1000], transform it to [3, 1000]. The new value 1000 does not update the stored median, and it is not itself replaced.
In cross-validation, each training fold gets its own fitted imputer. Put the imputer inside the pipeline passed to the splitter or search. The shared-fold tuning example explains the boundary; the same rule applies to learned medians and modes.
Python: inspect the fitted median and transformed values
import numpy as np
from sklearn.impute import SimpleImputer
train = np.array([[1.], [2.], [np.nan], [4.], [100.], [np.nan]])
test = np.array([[np.nan], [1000.]])
imputer = SimpleImputer(strategy="median")
imputer.fit(train)
assert imputer.statistics_.tolist() == [3.0]
assert imputer.transform(test).ravel().tolist() == [3.0, 1000.0]
print("Filled training column:", imputer.transform(train).ravel())
print("Filled test column:", imputer.transform(test).ravel())
Decide what to do with an all-missing training column
No mean, median, or mode can be estimated when the training column has no observed entries. Choose a policy: remove the feature with a recorded schema change, preserve it using an explicit constant, or reject the fit and investigate the collection problem.
Scikit-learn exposes this choice through keep_empty_features. In its current API, retaining an empty feature with a nonconstant strategy uses 0 as the replacement; that zero is a fallback, not a learned median. Decide whether that representation is appropriate and preserve the same feature ordering at serving time.
The C++ implementation below instead raises an error for an all-missing column. This avoids indexing an empty vector or silently assigning a statistic that the data cannot support.
C++: separate the learned statistic from filling
Estimate the median from present training values, then pass that stored value to every later transformation. The sample also demonstrates the all-missing error path.
Compile the median imputer
#include <algorithm>
#include <cmath>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <vector>
double columnMedian(const std::vector<double>& column) {
std::vector<double> observed;
for (double x : column) {
if (std::isnan(x)) continue;
if (!std::isfinite(x)) throw std::invalid_argument("nonfinite value");
observed.push_back(x);
}
if (observed.empty())
throw std::domain_error("no observed values: choose a fallback policy");
std::sort(observed.begin(), observed.end());
const std::size_t n = observed.size();
if (n % 2) return observed[n / 2];
return observed[n / 2 - 1] / 2.0 + observed[n / 2] / 2.0;
}
void fillMissing(std::vector<double>& column, double fittedValue) {
for (double& x : column) if (std::isnan(x)) x = fittedValue;
}
int main() {
const double NA = std::numeric_limits<double>::quiet_NaN();
std::vector<double> train = {1, 2, NA, 4, 100, NA};
std::vector<double> test = {NA, 1000};
const double fittedMedian = columnMedian(train);
fillMissing(train, fittedMedian);
fillMissing(test, fittedMedian);
std::cout << "stored median: " << fittedMedian << "\n";
std::cout << "test: " << test[0] << " " << test[1] << "\n";
try { columnMedian({NA, NA}); }
catch (const std::domain_error&) {
std::cout << "all-missing column: explicit fallback required\n";
}
return 0;
}
stored median: 3
test: 3 1000
all-missing column: explicit fallback required
Judge the baseline by the job it must do
Use simple filling when you need a transparent first predictive pipeline, then compare alternatives on the same held-out examples. Inspect errors separately for complete and incomplete inputs, and for important groups. A global score can hide a poor result on the rows most affected by the imputer.
When other features carry useful information about the absent value, KNN or iterative imputation can model that structure. More complicated filling is not guaranteed to improve the downstream predictor; assess the full recipe rather than how plausible the filled table looks.
A missingness indicator can retain the distinction between an observed median-valued entry and an imputed one. Its usefulness depends on whether that missingness pattern remains informative at deployment. The mechanism chapter explains why it does not establish that the missing values have been recovered.
References and further reading
- Scikit-learn: SimpleImputer — summary strategies, tie handling, and empty-feature behavior.
- Scikit-learn: imputation guide — using simple and multivariate imputers within predictive workflows.
- Van Buuren: ad-hoc imputation methods — why single-value fills distort distributions and do not propagate inferential uncertainty.