Target Encoding Explained: Useful Signal or Hidden Leakage?
Target encoding is one of the most seductive traps in feature engineering. You encode a high-cardinality feature like city or merchant_id, watch your…

Key topics
Target encoding is one of the most seductive traps in feature engineering. You encode a high-cardinality feature like city or merchant_id, watch your validation score jump, and then discover the model falls apart on real data. The technique genuinely captures useful signal—but the same mechanism that makes it powerful is the one that leaks.
Why target encoding exists
Some categorical features have a problem: too many categories. Think of a merchant_id column with thousands of distinct values, or a postal_code field covering hundreds of regions. One-hot encoding turns each category into its own binary column, exploding your feature space into a wide, mostly-zero matrix that invites overfitting. Label encoding keeps a single column but imposes a false ordering—the model may treat category 7 as "greater than" category 3, which is meaningless for unordered categories.
Target encoding (also called mean encoding) sidesteps both problems. Instead of expanding the feature or inventing an order, it replaces each category with the mean of the target variable for samples in that category. One dense numeric column carries real predictive information about the category, and the dimensionality stays flat.
That sounds like the best of both worlds. The catch is that it changes what the encoding is.
Knowledge check
Check your understanding
Answer this question before you continue.
The mechanism: encoding is a label statistic
Here is the shift that matters. One-hot encoding and label encoding transform the feature alone. They never look at the label. Target encoding does something categorically different: it computes a per-category statistic of the target.
For binary classification, each category becomes the positive rate of that category. For regression, it becomes the mean outcome. If 60% of London customers churned, London maps to 0.6. If the average transaction value for a merchant is $142, that merchant maps to 142.
This is a supervised transformation, not an unsupervised preprocessing step. You are not "encoding the category." You are estimating the category's conditional target probability. The label feeds the feature.
That single fact changes where the encoding belongs in your workflow.
Knowledge check
Check your understanding
Answer this question before you continue.
Where the leakage hides
Here is the naive approach that looks innocent and quietly corrupts everything:
# Don't do this
full_data["city_encoded"] = full_data.groupby("city")["churned"].transform("mean")
X_train, X_test, y_train, y_test = train_test_split(
full_data.drop("churned", axis=1), full_data["churned"]
)
The encoding is computed on the full dataset before the split. Every training row's encoded value was calculated using test rows' labels. A test row's outcome silently influences the feature value the model sees for a training row in the same category.
The model then learns a shortcut: "this category had high outcomes, so predict high." But part of what it is memorizing is the very labels it is being evaluated against. Your validation score inflates because the model was handed a compressed copy of the answer.
This is not overfitting in the usual sense. It is estimation leakage—behaving like peeking at test labels, not like fitting a scaler. Research on leakage mechanisms has shown that target encoding on full data produces score inflation far beyond what harmless transformations cause. Ordinal encoding, which assigns integer codes without consulting the label, shows essentially no inflation. The difference is entirely in whether the label participated in building the feature.
Contrast this with scaling a numeric feature. A scaler uses only that feature's own distribution. Target encoding uses the label. The two cannot be treated the same way, and the moment you run target encoding before your split, you have crossed the evaluation boundary.
Knowledge check
Check your understanding
Answer this question before you continue.
Why smoothing matters
Target encoding has a second failure mode that is easy to confuse with leakage: overfitting to rare categories.
A category with one sample and a positive outcome encodes to 1.0. A category with two samples and one positive outcome encodes to 0.5. These are noisy estimates, not reliable signals. The model will treat a single lucky transaction as strong evidence.
Smoothing fixes this by blending the category mean with the global target mean, weighted by the category's sample count:
smoothed = (count * category_mean + m * global_mean) / (count + m)
Rare categories shrink toward the global mean. Frequent categories keep their own estimate. The m parameter controls how much weight the global mean gets. This is regularization: you trade a little bias for much less variance on small-sample categories.
Here is the distinction worth naming clearly: smoothing reduces overfitting from noisy estimates. It does not fix leakage. A smoothed encoding computed on the full dataset still leaks, because test labels still participated in the calculation. The two problems are separate, and treating smoothing as a cure for leakage is how models quietly break in production.
Knowledge check
Check your understanding
Answer this question before you continue.
The validation-safe workflow
The rule is simple to state and easy to violate: the encoder must be fit on the training fold only, then applied to both training and validation folds using those learned statistics.
Within cross-validation, this means fitting a fresh encoder inside each fold. Never compute category means once on the full dataset and reuse them across folds.
Scikit-learn's TargetEncoder handles this through internal cross-fitting. fit_transform uses out-of-fold encodings for training rows, so no row's encoded value was computed from its own label. transform applies the full-training-set encoding to new data. This is why the distinction between fit-time and transform-time behavior matters so much here.
The safest pattern is to bundle the encoder with your model inside a Pipeline:
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import TargetEncoder
from sklearn.linear_model import Ridge
preprocessor = ColumnTransformer(
transformers=[("target", TargetEncoder(), ["city"])],
remainder="passthrough",
)
pipeline = Pipeline(
steps=[("preprocessor", preprocessor), ("model", Ridge())]
)
When this pipeline is cross-validated, the split happens before encoding. Each fold fits a fresh encoder on its training portion only. The validation rows never influence the encoding the model sees during training.
One practical note: unseen categories at prediction time need a fallback. The standard choice is the global target mean, which is exactly what the smoothing formula produces as the category count approaches zero.
When to use target encoding (and when not to)
Target encoding is a compression bet. You trade interpretable one-hot columns for a dense, powerful, leak-prone single column. The bet pays off when three conditions hold:
- The feature has high cardinality, so one-hot encoding would explode the feature space.
- The category genuinely correlates with the target.
- You have a validation-safe pipeline that fits the encoder inside each fold.
Skip it when the feature has few categories. One-hot encoding is simpler, avoids injecting target noise into your features, and keeps the encoding interpretable. Skip it when you suspect strong interaction effects between categories and other features—one-hot encoding preserves those interactions in a way a single compressed column cannot.
There is also a subtler cost. Target encoding injects target noise into the feature. When the category-target relationship is weak, or the dataset is small, that noise can hurt more than the signal helps. The encoding is only as reliable as the estimate behind it.
My decision rule: high cardinality plus strong category-target signal plus a validation-safe pipeline equals a good target-encoding candidate. Miss any one of those, and a simpler encoding is probably the better tool.
The mental model that keeps you safe
Target encoding is a supervised transformation that estimates a label statistic. Because it consults the label, it must be fit inside the validation boundary every single time. Smoothing fixes noisy estimates, not leakage. The only real cure for leakage is where you fit the encoder.
If you want to see this firsthand, build a small pipeline with TargetEncoder and compare validation scores with and without internal cross-fitting. Or compute the encoding on the full dataset before splitting and watch the score inflate—then watch it collapse when you evaluate on genuinely held-out data. The experiment will make the mechanism unforgettable.
The feature that carries a compressed copy of the answer will always look great in training. The question is whether it still works when the answer is no longer in the room.
Knowledge check
Final check
Finish the article by checking the ideas you just learned.
References
Research updated Sep 8, 2026


