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.
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.
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 labelsEncoding 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.
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.
encoder.classes_ # the ports, not female and male
encoder.inverse_transform(df["Sex"]) # runs, and answers with portsRunning the reuse, and reading what comes back
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))))one object classes_: ['C', 'Q', 'S'] decoding the Sex column: ['Q', 'C', 'C'] Sex classes_: ['female', 'male'] Embarked classes_: ['C', 'Q', 'S'] decoding the Sex column: ['male', 'female', 'female']
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.
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")classes_: ['C', 'Q', 'S']
codes : [0, 1, 2]
counts : {0: 168, 1: 77, 2: 644}
the column now says Q is halfway between C and SLabelEncoder, OrdinalEncoder and get_dummies
| LabelEncoder | OrdinalEncoder | pd.get_dummies | |
|---|---|---|---|
| Takes | one 1-D column | a whole frame | a whole frame |
| Order of the codes | alphabetical, always | alphabetical, or the one you pass | not applicable |
| Meant for | the target column | ordered features | nominal features |
| Remembers what it learned | yes, one column's worth | yes, every column's | no |
| A level it has not seen | raises at transform | raises, or takes a value you choose | makes a new column |
| Fits in a Pipeline | no, it takes y only | yes | no |
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_transformturns 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.
Related
- Previous: Ordinal encoding
- Next: Count and frequency encoding
- See also: Handling an unknown category
- Reference: sklearn.preprocessing.LabelEncoder
- Call
encoder.transform(pd.Series(["Z"]))after the loop and read the error that comes back. - Replace the loop with one
OrdinalEncoderon both columns at once and comparecategories_with the twoclasses_lists. - Encode
Survivedwith aLabelEncoderand check thatclasses_is the two values it already held.
Every expert started right here.