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 →

Ordinal encoding

Ordinal encoding replaces each category with a rank, so a column of n ordered categories becomes one numeric column instead of n indicator columns.

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

One hot encoding with many categories cut the column count by grouping rare levels. An ordered column can skip the whole problem: one column holds the order, and the order is the information.

The clip defines the technique and gives three examples of an order worth encoding: grades, a batsman's years of experience and the days of the week.

What ordinal number encoding is · from the Live Feature Engineering sessions · 6:44 to 10:35

Syntax: a dictionary and Series.map, or scikit-learn's transformer with the order spelled out.

python
series.map({"F": 0, "D": 1, "C": 2, "B": 3, "A": 4})

OrdinalEncoder(categories=[["F", "D", "C", "B", "A"]])   # worst first

Ranking the days of a week

A date column, and the weekday name

pd.date_range builds a fixed set of dates, so the example produces the same answer whenever it is run. dt.day_name() reads the weekday off each date.

python
days = pd.date_range("2026-10-01", periods=10, freq="D")
df["weekday"] = df["Day"].dt.day_name()

The order, written down

The dictionary is the whole technique. Writing it by hand is the step where the order gets decided by a person who knows what the column means, which is the one part no library can do.

python
order = {"Monday": 1, "Tuesday": 2, "Wednesday": 3, "Thursday": 4,
         "Friday": 5, "Saturday": 6, "Sunday": 7}

A date column turned into a rank

ExampleTen dates, their weekday names and the rank
import pandas as pd

days = pd.date_range("2026-10-01", periods=10, freq="D")
df = pd.DataFrame({"Day": days})
df["weekday"] = df["Day"].dt.day_name()

order = {"Monday": 1, "Tuesday": 2, "Wednesday": 3, "Thursday": 4,
         "Friday": 5, "Saturday": 6, "Sunday": 7}
df["weekday_rank"] = df["weekday"].map(order)

print(df.head(8).to_string(index=False))
print("dtype:", df["weekday_rank"].dtype)

Reading the ranked dates

  • Seven categories became one integer column. One hot would have added seven columns, or six with the first dropped.
  • The rank is an int64. Every value in the column matched a key in the dictionary. A value that does not match comes back as NaN, which turns the column into a float and is the usual way this goes wrong.
  • The dates are fixed. Building the frame from a date range rather than from today means the output is the same on every run, which a date column built from datetime.today() cannot promise.
  • Monday is 1 and Sunday is 7. That ordering is a choice. Starting at 0 instead changes nothing a model can see, because adding a constant to every value of a feature moves the intercept and leaves the rest of the fit alone.

Letting scikit-learn do it, and the default that gets it wrong

OrdinalEncoder does the same job as a transformer, which means it can sit in a pipeline and remember its categories. With no categories argument it sorts the levels alphabetically, and for grades the alphabet is almost exactly backwards.

ExampleThe alphabetical default, then the order written out
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder

grades = pd.DataFrame({"grade": ["B", "A", "C", "F", "A", "D"]})

wrong = OrdinalEncoder().fit(grades)
print("alphabetical order :", list(wrong.categories_[0]))
print("codes              :", wrong.transform(grades).ravel())

print()
right = OrdinalEncoder(categories=[["F", "D", "C", "B", "A"]]).fit(grades)
print("the order you chose:", list(right.categories_[0]))
print("codes              :", right.transform(grades).ravel())

Why the default is a bug here

  • Alphabetically, A is 0 and F is 4. The best grade gets the smallest number and the worst grade the largest, so the column is an upside-down ranking.
  • Nothing raised. Both calls returned a column of floats with no warning. A model trained on the first one learns that a larger code means a better student.
  • The explicit list fixes it. categories=[["F", "D", "C", "B", "A"]] puts F at 0 and A at 4, which is the direction a grade runs.
  • The codes come back as floats. OrdinalEncoder always returns float64, because it reserves room for the value it writes when it meets a level it has not seen.

Mapping with a dictionary vs OrdinalEncoder

Series.map with a dictOrdinalEncoder
Orderwhatever you writealphabetical unless you pass categories
A level not in the orderbecomes NaN, silentlyraises, or takes a value you choose
Output typeint64 if every level matched, float64 otherwisealways float64
Several columns at onceone call per columnone call for the whole frame
Works in a Pipelinenoyes
Best forone column with an order you are writing by handseveral ordered columns inside a pipeline

Where a rank is the right encoding

  • A size or grade column. Small, medium and large have one order and everybody agrees on it.
  • A rating scale. One to five stars is already a rank; keeping it as one column is cheaper than five dummies and loses nothing.
  • A column with many ordered levels. Income bands or age bands with a dozen levels would cost a dozen columns as dummies and cost one as a rank.
Watch out. A rank also claims that the gap between consecutive codes is the same everywhere. The step from Monday to Tuesday and the step from Saturday to Sunday are both 1, and in a weekday column they are not the same kind of step. Where that matters, use the indicator columns instead, or the sine and cosine pair that Cyclical encoding for hours and weekdays builds.
Try it yourself
  • Remove one weekday from the dictionary and print the dtype of the ranked column.
  • Change the dictionary so Sunday is 1 and Monday is 7, and compare the two ranked columns side by side.
  • Pass handle_unknown="use_encoded_value", unknown_value=-1 to OrdinalEncoder and transform a frame holding the grade E.

This is what real progress feels like.