EDA and Feature Engineeringpandas 3.0 · scikit-learn 1.9 · seaborn 0.13 · Python 3.12+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
72 small wins to finish your pathNext lesson →

Nominal and ordinal features

A nominal feature is a category with no order and an ordinal feature is a category whose values have one, and which of the two a column is decides every encoder that can be used on it.

Last updated: 12 Oct, 2026 · pandas 3.0 · scikit-learn 1.9

The outlier part finished the numeric columns. A text column cannot reach a model at all until it becomes numbers, and the first question is not which function to call. It is whether the categories in the column stand in an order.

One text column holding the grades A, C and F is shown next to the four encodings of it. One hot encoding makes three columns grade_A, grade_C and grade_F holding a single 1 per row and claims no order and no size. Ordinal encoding makes one column of ranks 2, 1 and 0 and claims the order you choose. Count encoding makes one column of 4, 3 and 1 and claims that a common category is a large one. Target mean encoding makes one column of 0.90, 0.60 and 0.10 and reads the target. The footer says to pick by what the column is: nominal categories take one hot, count or target mean, while an ordered category can take ordinal for one column instead of n.

Every encoder writes a claim into the data that the model will believe. One hot claims nothing beyond identity. Ordinal claims an order and a spacing. The point of this lesson is that one of those claims is free and the other has to be earned.

Telling the two kinds apart

The question to ask about a column

A column is ordinal when you can finish the sentence "this value is more than that one" without inventing anything. Small, medium and large. First, business and economy class. A grade of A, B or C. If finishing the sentence takes an assumption, the column is nominal.

  • Nominal: city, airline, colour, port of embarkation, product category.
  • Ordinal: size, grade, education level, satisfaction rating, income band.
  • It depends: day of the week is ordered inside a week and wraps around at the end of it, which Cyclical encoding for hours and weekdays deals with.

What an invented order does

Mapping a nominal column to 0, 1 and 2 always runs. The model then reads those numbers as a scale, and the scale came from the alphabet or from the order the levels happened to appear in.

python
embarked.map({"C": 0, "Q": 1, "S": 2})   # why that order and not another?

Three arbitrary orders on one nominal column

Embarked holds the three ports a passenger boarded at: Cherbourg, Queenstown and Southampton. No order exists among them. Correlating the coded column with survival under three different arbitrary orders shows what the code is worth.

ExampleThe same nominal column, three arbitrary orders
import pandas as pd

URL = ("https://raw.githubusercontent.com/krishnaik06/Feature-Engineering-Live-sessions/"
       "master/titanic.csv")
df = pd.read_csv(URL, usecols=["Embarked", "Survived"]).dropna()

orders = [{"C": 0, "Q": 1, "S": 2},
          {"S": 0, "C": 1, "Q": 2},
          {"Q": 0, "S": 1, "C": 2}]

for order in orders:
    coded = df["Embarked"].map(order)
    print(order, " correlation with Survived:",
          round(coded.corr(df["Survived"]), 4))

What the three correlations mean

  • The number changes with the order. Minus 0.1697, plus 0.1087, plus 0.1268. Same column, same target, three answers.
  • One of them changes sign. A model reading the first coding learns that a higher code means a worse chance of survival. Reading the third it learns the opposite.
  • None of the three is the right one. There is no order among three ports, so there is no coding that tells the truth. Every one of them is an invention.
  • Nothing warned about any of this. All three ran, all three produced a plausible number, and the choice that produced it was never written down.

Ordering an age band from what you know about the business

The clip meets the same choice on a shopping dataset whose Age column arrives as the bands 0-17, 18-25, 26-35 and so on. Dummy columns are shown first and then dropped in favour of ranks, because the bands are in an order and the amount people spend moves along it.

Choosing ordinal over one hot for an ordered category · from the EDA and Feature Engineering video · 1:14:25 to 1:19:12

The Titanic Age column arrives as a number rather than as bands, so pd.cut makes the bands first. The decision after that is the one the clip makes; only the dataset differs.

Ranking a column that does have an order

pd.cut turns the numeric age into bands. labels names them and bins sets the edges, so the result is an ordered category where the rank is not an invention: a passenger in 46-60 is older than one in 18-25 by definition.

ExampleTitanic Age cut into bands, then ranked
import pandas as pd

URL = ("https://raw.githubusercontent.com/krishnaik06/Feature-Engineering-Live-sessions/"
       "master/titanic.csv")
df = pd.read_csv(URL, usecols=["Age", "Survived"])

labels = ["0-17", "18-25", "26-35", "36-45", "46-60", "60+"]
df["band"] = pd.cut(df["Age"], bins=[0, 17, 25, 35, 45, 60, 100], labels=labels)
rank = {name: i for i, name in enumerate(labels)}      # 0-17 is 0, 60+ is 5
df["band_rank"] = df["band"].map(rank)

print(df[["Age", "band", "band_rank"]].head(6).to_string(index=False))
print()
print("one hot would cost", df["band"].nunique(),
      "columns; the rank costs 1")
print(df.groupby("band", observed=True)["Survived"].mean().round(4).to_string())

Reading the ranked bands

  • The rank follows the band. 0-17 is 0 and 60+ is 5, and the numbers in between are the bands in the order a person would read them.
  • Six categories became one column. One hot would have added six, or five with the first dropped. That saving is the reason to use a rank when a real order exists.
  • The survival rates are not in rank order. 0-17 survived at 0.5398, 18-25 at 0.3351 and 26-35 at 0.4235. The column is ordered in age and the target does not follow it, so a model that can only draw a straight line through the rank will do badly.
  • Row 5 has no band. That passenger has no age, so pd.cut returned NaN and the rank is NaN too. An encoder does not fill holes; the missing-value part does that first.

The third point is the honest limit of ordinal encoding. It claims an order, which is true here, and it also claims equal spacing between consecutive ranks, which is a second claim nobody checked. One hot makes neither claim and costs five more columns.

Nominal vs ordinal

NominalOrdinal
ExampleEmbarked: C, Q, Sband: 0-17 ... 60+
Is one value more than another?noyes
Safe encodersone hot, count, frequency, target meanall of those, plus a rank
Cost of the usual choicen or n-1 new columns1 new column
What goes wrong with the other choicea rank invents an order the model will useone hot throws the order away, which a tree then has to rediscover

Where the choice is made in practice

  • A survey column. Strongly disagree through strongly agree is ordinal, and a rank keeps the frame narrow enough to model.
  • A product catalogue. Category and brand are nominal, with hundreds of levels, which is the problem One hot encoding with many categories takes on.
  • A size column that looks numeric. Shoe sizes, screen sizes and clothing sizes arrive as text and are ordered, so they are rank candidates rather than dummy candidates.
Watch out. A rank on a nominal column never raises an error and never prints a warning. It produces a model that scores, ships and carries an order somebody invented. Write down why an order exists before you encode one.
Try it yourself
  • Add a fourth order to the list, {"S": 0, "Q": 1, "C": 2}, and read the fourth correlation.
  • Change the band edges to [0, 12, 20, 40, 60, 100] and print the survival rates again.
  • Print df["band"].dtype and df["band"].cat.categories to see what pd.cut returned.

Little by little, you're building something great.