Worked examples — Ordinal and target encoding
This page is a drill. The parent note told you the two mappings and their formulas. Here we throw every kind of input at those formulas — every count, every degenerate case, every "what happens at the edge" — so that when a real dataset hands you something weird, you have already seen it.
Before we start: two words we will lean on constantly.
The scenario matrix
Encoding looks trivial until you meet an edge. Here is every case class this topic can throw at you. Each of the worked examples below is tagged with the cell it lands in.
| # | Case class | What makes it tricky | Worked ex. |
|---|---|---|---|
| A | Plain ordinal, order given | nothing — the baseline | Ex 1 |
| B | Ordinal with a gap in the scale | is the spacing uniform? | Ex 2 |
| C | Ordinal misused on a nominal feature | order that doesn't exist | Ex 3 |
| D | Target encode, several counts | mean per group | Ex 4 |
| E | Degenerate category: | one row = pure noise | Ex 5 |
| F | Smoothing with parameter | shrink toward global mean | Ex 5, 6 |
| G | Zero-observation / unseen category at test | division by zero avoided | Ex 7 |
| H | Limit and | what smoothing collapses to | Ex 8 |
| I | Real-world word problem end-to-end | glue it all together | Ex 9 |
| J | Exam twist: leakage trap | the fold-safe fix | Ex 10 |
The rest of the page fills in every row.
Ordinal cases (A, B, C)
Forecast: guess the five numbers before reading on.
- Write the order as ranks.
Poor→1, Fair→2, Good→3, Excellent→4. Why this step? Ordinal encoding's only job is ; consecutive integers are the smallest set that guarantees it. - Look up each row. Good=3, Poor=1, Excellent=4, Fair=2, Good=3. Why this step? Encoding is a dictionary lookup, nothing more.
Verify: the maximum encoded value (4) sits exactly where "Excellent" is, the minimum (1) where "Poor" is. Order preserved. ✓
Forecast: will vs correctly say "Black is a little above Brown", or will it lie about how much?
- Rank the categories, then look up each row. The map is White→1, Brown→2, Black→3. Now read the data
[White, Black, White, Brown]left to right and substitute: White=1, Black=3, White=1, Brown=2, giving Why this step? Same rule as Ex 1 — you rank the categories once, then do a plain dictionary lookup for each row. The order of the data rows is preserved; only the labels change to their ranks. - Name the danger. A tree only ever asks "is ?", so the gaps between 1, 2, 3 are irrelevant — it just needs monotonicity. But a linear model reads as equal jumps in skill, which is a modelling assumption you are silently making. Why this step? This is the whole reason ordinal encoding is safe for trees and risky for linear models.
Verify: the two White rows must encode to the same number — positions 1 and 3 are both ✓. Ranks are strictly increasing ✓. The "equal spacing" claim is an assumption, not a fact — flag it. See Feature Scaling if the downstream model is distance-based.
Read the figure below: the three category ranks sit on a number line at . The two red double-headed arrows mark the gaps between consecutive belts. Notice they are drawn identical length — that visual equality is exactly the hidden "equal-skill-jump" assumption a linear model swallows. There is no evidence Brown→Black is the same skill leap as White→Brown; the encoding just declares it so.

Forecast: what false belief does the number line now force onto the model?
- Spot the invented order. The map claims
Green (3) > Blue (2) > Red (1), i.e. green is "more" than red. Color has no such ranking — it is nominal, not ordinal. Why this step? Ordinal encoding is only valid when a true order exists (Ex 1, 2). Here it manufactures a lie. - Fix. For a low-cardinality nominal feature use One-hot Encoding; for a high-cardinality one use target encoding (below). Why this step? Both avoid imposing a fake ranking.
Verify: sanity check — swap Red and Green labels. Ordinal encoding gives a different dataset ( vs ) purely from relabelling; a correct nominal encoder must be label-invariant. Ordinal fails this test → confirms misuse. ✓
Target-encoding cases (D–H)
We reuse one running table so the numbers are traceable.
City Price(k$)
NYC 500
NYC 600
NYC 550
Seattle 400
Seattle 450
Portland 300
Two constants we will need throughout, plus the weight that controls smoothing:
Forecast: which city gets the biggest number?
- Count each category. . Why this step? Every mean needs its denominator; the counts also decide who is "trustworthy" later.
- Group means. Why this step? This is — the target's signal per category, compressed to one number.
- Replace. Encoded column = .
Verify: the encoded values must lie between the min (300) and max (600) of the raw prices, and NYC (highest prices) must get the highest code. ✓, NYC largest ✓.
Read the figure below: three bars, one per city, whose heights are the encoded values. The red bar (NYC) towers highest at 550 — a visual reminder that target encoding turns "which city?" into "how expensive, on average?", so the tallest bar is literally the priciest place.

Forecast: raw target says 300. Will smoothing pull it up toward the global mean a little or a lot?
- Compute the global mean carefully. Why this step? This is the anchor every category shrinks toward; getting it right matters because all later numbers depend on it. (Add the six prices: they total , not — do the sum, don't eyeball it.)
- Recall the smoothing formula. Why this step? With one observation, 300 is almost certainly noise. Smoothing = "add imaginary rows sitting at the global mean" — a Bayesian prior that says when in doubt, be average.
- Plug in : Why this step? The weight on the category is — barely 9%. One row earns almost no trust, so the answer sits near the global mean.
Verify: must lie strictly between the raw category value 300 and the global mean 466.67, and closer to 466.67 (since is small). ✓, closer to 466.67 ✓. This shrinkage directly fights Overfitting.
Forecast: NYC has more data — will it shrink less than Portland did?
- Same formula, new numbers. Why this step? Weight on the category is — over twice Portland's. More rows = more trust = less shrinkage.
Verify: distance from category mean, NYC vs Portland. NYC moved ; Portland moved . The category with more data moved less ✓ — exactly the intended behaviour.
Read the figure below: each city is a vertical red arrow starting at its raw mean (black dot) and ending at its smoothed code (red square). The dashed black line is the global mean . Every arrow points toward that dashed line — that is what "shrinkage" looks like — and Portland's arrow is far longer than NYC's, showing thin data gets dragged much harder.

Forecast: what do you do when the denominator's group is empty?
- NYC, Seattle already have training means: 550 and 425 (Ex 4). Why this step? Known categories reuse the learned map — that is the point of fitting on train, applying on test.
- Boston has no rows. Raw formula would be — undefined. Fall back to the global mean . Why this step? With zero evidence, the smoothing formula itself already gives the right answer: set and Smoothing automatically handles the unseen case — no special branch needed.
Verify: unseen → exactly global mean ✓; the never occurs because ✓.
Forecast: at one extreme you get pure raw mean; at the other, pure global mean. Which is which?
- (no smoothing): weight . Why this step? Zero imaginary rows = trust the data completely = plain target encoding (Ex 4). This is the maximum-variance / overfitting end.
- (infinite smoothing): weight . Why this step? Infinitely many prior rows drown the data = every category collapses to the same number = the feature becomes useless (maximum bias). This is the underfitting end.
Verify: plug ✓; plug a huge , arbitrarily close to the global mean ✓. So is a bias–variance dial — pick it with Cross-Validation.
Read the figure below: the red curve is the smoothed Portland code as slides from to large. It starts pinned to the lower dashed line (raw mean 300) and bends smoothly up toward the upper dashed line (global mean 466.67). Reading it left-to-right is reading the bias–variance trade-off: left edge overfits, right edge underfits.

Word problem + exam twist (I, J)
Forecast: Wed clearly tips best — but Tue and Fri each have thin data. Guess which of those shrinks harder.
- Counts and group means. Mon ; Tue ; Wed ; Fri . Why this step? Establish evidence per category before trusting any of them.
- Global mean. . Why this step? This is the prior everything shrinks toward, and the fallback for Thu.
- Smooth each with : Why this step? Notice Tue (raw 9, thin data) is pulled up to 13.46, while Wed (strong, well-populated 20) keeps most of its edge at 16.90.
- Thu is unseen → global mean (Case G logic).
Verify: every smoothed value lies between its raw mean and 14.571 ✓; the ranking of smoothed codes still puts Wed on top ( highest) ✓; Tue moved further from its raw mean than Wed did (thin vs thick data) ✓.
Forecast: does a row's own target sneak into its own feature?
- Spot the leak. For Portland (the only row), . The encoded feature for that row contains its own label. The model can "cheat" by memorising the code. Why this step? This is why the parent note screams don't fit on the full data.
- Fold-safe fix (out-of-fold encoding). Split rows into folds; encode each fold using means computed from the other folds only, so a row is never used to build its own code. Concretely with 2 folds: put NYC-row-1 in fold A, encode it using only NYC rows in fold B. Why this step? It restores the train/apply separation within the training set — the same principle as Cross-Validation.
- Numeric contrast. Leaky Portland code (exact label). A fold-safe code, having no other Portland rows to average, falls back to the global mean . The gap is precisely the leaked information you just removed.
Verify: leaky code equals the raw label exactly () for any category ✓; the fold-safe code for a singleton reduces to the global mean ✓; their difference is nonzero, confirming leakage was real ✓.
Recall Quick self-test
Portland smoothed code with , global mean :::
What weight does a category with get when ? :::
Unseen test category encodes to ::: the global mean ( here)
:::
Why is ordinal encoding wrong for Color? ::: it invents an order the feature does not have (nominal, not ordinal)