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 →

Label encoding

LabelEncoder turns one column of labels into the integers 0 to n minus 1, in alphabetical order of the label, and it was built for the target column rather than for features.

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

Ordinal encoding chose the ranks by hand. This lesson is about the encoder that chooses them for you, what that means for a feature, and the mistake that follows from using one encoder object on several columns.

The clip encodes four text columns of a flight dataset in a row, and makes the case for a fitted transformer over get_dummies when train and test data arrive separately.

Label encoding several columns · from the EDA and Feature Engineering video · 2:37:11 to 2:40:08

The page runs the same pattern on the Titanic frame, which has two text columns rather than four, and then shows what the one-object version does to them.

Syntax: fit on the labels, then transform them.

python
encoder = LabelEncoder()
codes = encoder.fit_transform(series)   # 0 to n-1
encoder.classes_                        # the labels, in code order
encoder.inverse_transform(codes)        # back to the labels

Encoding two columns with one encoder

The pattern that looks fine

One encoder object, called twice. It runs, both columns come out as integers, and the frame looks exactly as it should.

python
encoder = LabelEncoder()
df["Sex"] = encoder.fit_transform(df["Sex"])
df["Embarked"] = encoder.fit_transform(df["Embarked"])

What the object is holding afterwards

fit_transform replaces whatever the encoder had learned. After the second call the object knows only the second column, so the first column can no longer be decoded, and no fitted encoder for it exists to use on new data.

python
encoder.classes_                        # the ports, not female and male
encoder.inverse_transform(df["Sex"])    # runs, and answers with ports

Running the reuse, and reading what comes back

ExampleOne encoder across two columns, then one per column
import pandas as pd
from sklearn.preprocessing import LabelEncoder

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

one = df.copy()
encoder = LabelEncoder()
one["Sex"] = encoder.fit_transform(one["Sex"])
one["Embarked"] = encoder.fit_transform(one["Embarked"])
print("one object  classes_:", list(encoder.classes_))
print("decoding the Sex column:",
      list(encoder.inverse_transform(one["Sex"].head(3))))

print()
many = df.copy()
encoders = {}
for column in ["Sex", "Embarked"]:
    encoders[column] = LabelEncoder()
    many[column] = encoders[column].fit_transform(many[column])
for column, enc in encoders.items():
    print(f"{column:9s} classes_:", list(enc.classes_))
print("decoding the Sex column:",
      list(encoders["Sex"].inverse_transform(many["Sex"].head(3))))

What the two decodings show

  • The reused object holds C, Q and S. The ports from the second column. The labels female and male were thrown away by the second fit_transform.
  • Decoding the Sex column returned Q, C, C. Three port names for three passengers whose sex was male, female, female. The call did not raise, because the codes 1, 0 and 0 are valid codes in the port alphabet too.
  • One encoder per column keeps both alphabets. Sex holds female and male, Embarked holds C, Q and S, and decoding the Sex column answers with male, female, female.
  • The encoded frames are identical. Both versions produce the same numbers in the same places. Only the fitted objects differ, which is why this is caught at the point where new data arrives rather than at the point where the mistake is made.

The fitted object is the part that matters. Without it there is no way to encode tomorrow's rows the way today's were encoded, and a model fed differently coded columns answers nonsense with full confidence.

The order the codes come in

LabelEncoder sorts the labels and numbers them from 0. For the ports that gives C 0, Q 1 and S 2, which is the alphabet and not a property of the ports. A model that treats the column as numeric reads an order that the encoder invented, which is the problem Nominal and ordinal features measured.

ExampleThe codes, and what they claim about the three ports
import pandas as pd
from sklearn.preprocessing import LabelEncoder

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

encoder = LabelEncoder()
codes = encoder.fit_transform(embarked)
print("classes_:", list(encoder.classes_))
print("codes   :", sorted(int(code) for code in set(codes)))
print("counts  :", pd.Series(codes).value_counts().sort_index().to_dict())
print("the column now says Q is halfway between C and S")

LabelEncoder, OrdinalEncoder and get_dummies

LabelEncoderOrdinalEncoderpd.get_dummies
Takesone 1-D columna whole framea whole frame
Order of the codesalphabetical, alwaysalphabetical, or the one you passnot applicable
Meant forthe target columnordered featuresnominal features
Remembers what it learnedyes, one column's worthyes, every column'sno
A level it has not seenraises at transformraises, or takes a value you choosemakes a new column
Fits in a Pipelineno, it takes y onlyyesno

For features, OrdinalEncoder does everything LabelEncoder does and handles a frame in one call. The reason to reach for LabelEncoder is a target column of class names that a classifier needs as integers.

Where label encoding belongs

  • The target of a classifier. Class names like spam and ham become 0 and 1, and inverse_transform turns predictions back into names for a report.
  • A feature read only by trees. A decision tree splits on thresholds, so an arbitrary code costs it less than it costs a linear model. It still invents an order, and the tree still has to spend splits undoing it.
  • A quick look at a frame. Coding every text column is a fast way to get corr() or a baseline model to run, as long as nothing built on it ships.
Watch out. One encoder object per column, kept somewhere you can reach at prediction time. A single object reused across columns overwrites itself with no error and leaves you unable to encode new rows the way the model was trained.
Try it yourself
  • Call encoder.transform(pd.Series(["Z"])) after the loop and read the error that comes back.
  • Replace the loop with one OrdinalEncoder on both columns at once and compare categories_ with the two classes_ lists.
  • Encode Survived with a LabelEncoder and check that classes_ is the two values it already held.

Every expert started right here.