A model only understands numbers. So what do you do with a column like city, whose values are "NYC", "LA", "SF" — and why does the most tempting fix quietly hand the model the answer key?
| City | Label y | Encoded |
|---|---|---|
| NYC | 1 | 1 |
| LA | 0 | 0 |
| NYC | 1 | 1 |
| SF | 1 | 2 |
| LA | 0 | 0 |
| NYC | 0 | 1 |
Same six rows, same city column, three different ways to turn it into numbers. Ordinal encoding just assigns each city an integer. One-hot spreads it into a binary column per city. Naive target encoding replaces each city with the average outcome for that city — watch the lone SF row especially closely as you switch to it.
Three ways to turn a category into numbers, each with a different failure mode:
- — a category value, e.g.
"SF". - index of — ordinal encoding's arbitrary position for that category in some fixed list.
- — the label of row .
- target — the mean label among rows sharing category ; naive target encoding includes the current row in that average, leave-one-out excludes it.
- Ordinal encoding invents an ordering
Mapping
LA→0, NYC→1, SF→2makesSFnumerically "greater than"NYC, which means nothing — a model that uses distance or magnitude will treat that fake ordering as real information. - One-hot encoding avoids false order, at the cost of width
Three categories become three binary columns, each independent. No fake ordering, but a column per category — fine for a handful of cities, unworkable for a column with thousands of distinct values.
- Target encoding is compact, and dangerous by default
Compressing a category down to "the average outcome for rows like this" packs in real signal in one column — but if that average is computed using the row's own label, the row is handed a feature derived from the very thing you're trying to predict.
| City | Label y | Encoded |
|---|---|---|
| NYC | 1 | 0.667 |
| LA | 0 | 0.000 |
| NYC | 1 | 0.667 |
| SF | 1 | 1.000 |
| LA | 0 | 0.000 |
| NYC | 0 | 0.667 |
Look at the SF row: it's the only row with that city, so naive target encoding's "average outcome for SF" is an average of exactly one number — its own label. The encoded value and the true label become the same number. Leave-one-out encoding excludes the current row before averaging, so a singleton category has no other rows left to average — it falls back to the global mean instead of leaking.
Six rows: NYC,1 LA,0 NYC,1 SF,1 LA,0 NYC,0 — global mean label :
- NYC appears three times: 1, 1, 0
Naive target encoding for every NYC row is the same number, — safe, since no single row dominates the average.
- SF appears exactly once: label 1
Naive target encoding averages that one label with itself: — identical to the row's own label. The "feature" is the answer, restated.
- Leave-one-out catches it
- For the SF row, leave-one-out excludes the row itself — and there are no other SF rows to average, so it falls back to the global mean, .
- For an NYC row, e.g. the one labeled , leave-one-out averages the other two NYC labels (), giving — a real average of other rows, not a copy of its own answer.
Switch between naive and leave-one-out target encoding until the SF row's encoded value exactly matches its own label (1) — the sign of the leak.
| City | Label y | Encoded |
|---|---|---|
| NYC | 1 | 0.500 |
| LA | 0 | 0.000 |
| NYC | 1 | 0.500 |
| SF | 1 | 0.500 |
| LA | 0 | 0.000 |
| NYC | 0 | 1.000 |
One-hot and ordinal encoding never see the label at all, so they can't leak it — their tradeoff is width versus false ordering. Target encoding is the one that touches directly, which makes it powerful and risky in the same breath: the fix isn't to avoid it, it's to always exclude each row's own label from its own encoding, exactly the way cross-validation excludes each fold from its own evaluation. Encoded or not, not every column deserves to be in the model at all — the next chapter turns to deciding which features earn their place.