Constant and Sentinel Imputation
Constant imputation replaces a gap with a fixed value chosen as part of the feature definition. A sentinel is a special constant intended to mark absence, such as −1 for a count whose valid values are nonnegative.
The fill is a representation decision. It does not establish what the absent measurement really was. The important questions are what the constant means, how the next transformation treats it, and whether the model can distinguish it from an observed value.
A known zero and an unknown count are different
Consider the number of purchases in a month. If a complete purchase ledger confirms no purchases, the count is genuinely zero. If the ledger was unavailable, the count is unknown; assigning zero would be an encoding choice.
Choose a sentinel from the domain, not the sample minimum
If observed counts can never be negative, −1 can mark a gap without colliding with a valid count. Seeing only values 3–5 in the current training sample is not sufficient evidence that 0, 1, or 2 are invalid. Those values may appear tomorrow.
A categorical sentinel needs the same protection: reserve a token such as __MISSING__ that cannot also be a legitimate category. If several reasons for absence matter—unknown, not applicable, or not collected—preserve them separately when the source actually distinguishes them.
Parse the input convention before encoding it. An empty string, a database null, a floating-point NaN, and the literal string "NA" are different values unless the data contract says to treat them alike. Do not accidentally erase a legitimate category while normalizing gaps.
The number still enters the model’s arithmetic
Take [3, missing, 5, missing, 4]. The observed values 3 and 4 are one unit apart. Fill a gap with −999 and its numerical distance from 3 becomes 1,002, even though the true value could be near 3. Try three choices:
A distance-based model uses those numerical differences when finding neighbors. Standardizing afterward also uses the artificially filled values to calculate its center and scale; it does not restore their missing-value meaning. Similarly, a linear model multiplies the sentinel by its learned coefficient.
A constant plus an explicit flag gives a linear model a separate offset for missing cases. The chosen constant can still affect fitting and regularization. Evaluate the representation with the intended model and preprocessing order.
Why trees can sometimes use sentinels conveniently
For nonnegative observed counts and sentinel −1, the split x < 0 can isolate missing inputs. That makes an absence-specific branch available to the tree.
The tree is not guaranteed to choose that branch. Split criteria, depth limits, minimum leaf sizes, training coverage, and competing features all affect the learned tree. If missingness occurs only at serving time, there were no training cases teaching that branch what to predict.
Also inspect earlier transformations. Applying a logarithm to −1 is invalid even if the eventual estimator is a tree. Some estimators support NaNs directly; compare that supported behavior with a sentinel pipeline instead of assuming every tree requires a numeric replacement.
Represent a missing category explicitly
A categorical fill becomes an ordinary level once encoded. In the following example, the one-hot columns are Basic, Pro, and __MISSING__. We reserve the missing token in the declared category list.
A missing test input activates the missing column. An observed but unseen category, Plus, follows the encoder’s unknown-category policy instead. With handle_unknown="ignore" and no dropped category, that policy gives an all-zero vector:
Python: distinguish missing from an unseen category
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
train = np.array([["Basic"], [None], ["Pro"]], dtype=object)
test = np.array([[None], ["Plus"]], dtype=object)
encoder = Pipeline([
("fill", SimpleImputer(
missing_values=None, strategy="constant", fill_value="__MISSING__",
keep_empty_features=True,
)),
("onehot", OneHotEncoder(
categories=[["Basic", "Pro", "__MISSING__"]],
handle_unknown="ignore", sparse_output=False,
)),
]).fit(train)
encoded = encoder.transform(test)
assert encoded.tolist() == [[0., 0., 1.], [0., 0., 0.]]
print(encoded)
[[0. 0. 1.]
[0. 0. 0.]]
The schema makes the distinction representable. It does not guarantee useful predictions for a category or missingness pattern with no training support. Choose the unknown-category policy deliberately, and inspect it alongside the encoding strategy.
Keep the policy consistent through training and serving
A fixed constant does not need to be estimated from sample values. Choosing the constant, deciding which columns to retain, and fitting later scalers or encoders still belong to the training procedure. Keep these choices inside the model-selection design rather than adjusting them after inspecting final-test outcomes.
For an all-missing training feature, a constant can preserve a column when explicitly configured to do so. It does not supply observed values for learning that feature’s effect. Monitor whether the feature later becomes populated, and keep its meaning consistent with the trained pipeline.
Retain raw missingness information long enough to create indicators, audit coverage, and inspect errors on incomplete inputs. The simple-imputation baseline provides a useful comparison, and the mechanism discussion explains why no sentinel resolves uncertainty about the hidden data by itself.
C++: replace gaps while retaining the raw input
The code copies the raw column and changes only NaNs. The nonnegative-count contract, rather than the range of this five-element sample, justifies reserving −1.
Compile the constant fill
#include <cmath>
#include <iostream>
#include <limits>
#include <vector>
std::vector<double> fillConstant(const std::vector<double>& raw, double fill) {
auto filled = raw;
for (double& value : filled)
if (std::isnan(value)) value = fill;
return filled;
}
int main() {
const double NA = std::numeric_limits<double>::quiet_NaN();
const std::vector<double> raw = {3, NA, 5, NA, 4};
const double sentinel = -1; // Contract: observed counts are nonnegative.
for (double value : fillConstant(raw, sentinel))
std::cout << value << " ";
std::cout << "\n";
// raw still contains its gaps, so indicators can be computed from it.
}
3 -1 5 -1 4
References and further reading
- Scikit-learn: SimpleImputer — explicit constants, missing-value markers, and retaining empty features.
- Scikit-learn: OneHotEncoder — declared categories and the behavior of unknown-category policies.
- Scikit-learn: imputation guide — missingness indicators and estimators with native missing-value support.