Encoding Categorical Data
Many Machine Learning algorithms expect numerical input. If a dataset contains categories such as Red, Blue,Delhi, or High, we often need to represent those categories numerically before training the model. This process is called categorical encoding.
Categorical encoding is an important preprocessing technique in Machine Learning. The correct method depends on what the category means. For example, Red / Blue / Green have no natural order, whileLow / Medium / High do have a meaningful order.
This guide explains categorical encoding from beginner to advanced level using simple examples, tables, Scikit-learn implementations, and practical best practices.
Encoding in Simple Words
Imagine a school dataset with a column called T-shirt Size. A model may not be able to use the words S, M, andL directly. Encoding changes those categories into a numerical form while trying to preserve their meaning.
Read the category
Example: "Medium"
Ask if order matters
Small < Medium < Large
Choose an encoder
Ordinal encoding
Create numbers
Small=0, Medium=1, Large=2
| Column | Values | Does order matter? | Simple starting choice |
|---|---|---|---|
| City | Delhi, Mumbai, Chennai | No | One-hot encoding |
| T-shirt Size | Small, Medium, Large | Yes | Ordinal encoding |
The key question
Before encoding a category, ask: Does this category have a real, meaningful order? That one question prevents many beginner mistakes.
What is Categorical Data?
Categorical data represents labels, groups, or categories rather than measurable numeric quantities. Unlike numerical features like salary or age, categorical variables describe qualities or classes.
| Feature | Example Values |
|---|---|
| Gender | Male, Female |
| Color | Red, Blue, Green |
| City | Bangalore, Delhi, Mumbai |
| Education | Graduate, Postgraduate |
| Product Type | Mobile, Laptop, Tablet |
Why Machine Learning Models Need Encoding
Many commonly used ML estimators expect numerical feature arrays. Text categories such as Red, Blue, or Green therefore need a suitable numerical representation before those estimators can use them. The representation matters because different algorithms use the numbers in different ways.
📐 Distance calculations
KNN and many clustering methods measure numerical distances, so arbitrary category codes can create misleading distances.
📉 Gradient descent
Linear models and neural networks optimise numerical parameters, so categorical inputs need a numerical representation.
📊 Statistical optimisation
Linear and logistic regression operate on numerical feature matrices and can be sensitive to fake order introduced by poor encoding.
🔢 Matrix operations
Neural networks eventually work with numerical tensors; categories may be one-hot encoded or represented through learned embeddings.
💡 The core problem
When a model expects numeric features, a text category such as Red needs to be represented in a usable numerical form. Encoding builds that bridge while trying not to invent relationships that do not exist.
Types of Categorical Variables
Categories that have NO hierarchy or order. One is not "greater" than the other.
Categories that have a clear, mathematical hierarchy or meaningful order.
Major Encoding Techniques
| Encoding Method | Suitable For | Dimensionality |
|---|---|---|
| Label Encoding | Classification target labels (y) | Low |
| One-Hot Encoding | Nominal data | High |
| Ordinal Encoding | Ordered categories (features) | Low |
| Binary Encoding | High-cardinality data | Medium |
| Frequency Encoding | High-cardinality categories when frequency is informative | Low |
| Target Encoding | Advanced ML / High Cardinality | Low |
1. Label Encoding
In Scikit-learn, LabelEncoder is designed for the target variable y. For example, a classification target containing Fail and Passcan be converted to 0 and 1. The model still treats these as class labels, not as measurements where one class is "twice" another.
| Original target | Encoded target |
|---|---|
| Pass | 1 |
| Fail | 0 |
| Pass | 1 |
from sklearn.preprocessing import LabelEncoder
y = ['Pass', 'Fail', 'Pass', 'Pass']
encoder = LabelEncoder()
encoded_y = encoder.fit_transform(y)
print("Original:", y)
print("Encoded: ", encoded_y)
print("Classes: ", encoder.classes_)Original: ['Pass', 'Fail', 'Pass', 'Pass'] Encoded: [1 0 1 1] Classes: ['Fail' 'Pass']
Scikit-learn's LabelEncoder is for y, not for ordinary input columns X. For an input feature such as City, use an encoder chosen for the feature meaning—for example One-Hot Encoding for unordered cities or Ordinal Encoding for truly ordered categories.
Why not simply write Red = 1, Blue = 2, Green = 3?
Those numbers accidentally create an order and distance that the colors do not have. A distance-based or linear model may treat Green (3) as farther from Red (1) than Blue (2), even though that relationship was invented by us.
| Color | Arbitrary code | Problem |
|---|---|---|
| Red | 1 | No real reason Red should be "lowest" |
| Blue | 2 | The code creates a fake middle position |
| Green | 3 | The code creates a fake highest position |
2. One-Hot Encoding (Dummy Variables)
For Nominal Data (no order), we use One-Hot Encoding. Instead of assigning ascending numbers in a single column, we create entirely new columns for every single unique category. We use1 to indicate "Hot" (True) and 0 for "Cold" (False).
This is powerful because it removes any fake ordering between categories. The model no longer assumes Green > Blue > Red. Each category becomes completely independent.
| Original Column | → | Color_Red | Color_Blue | Color_Green |
|---|---|---|---|---|
| Red | → | 1 | 0 | 0 |
| Blue | → | 0 | 1 | 0 |
| Green | → | 0 | 0 | 1 |
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
df = pd.DataFrame({'Color': ['Red', 'Blue', 'Green']})
# Pandas Method
encoded_pd = pd.get_dummies(df['Color'], dtype=int)
print("Pandas get_dummies:\n", encoded_pd, "\n")
# Scikit-Learn Method
encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
result = encoder.fit_transform(df[['Color']])
print("Scikit-Learn OneHotEncoder:\n", result)Pandas get_dummies:
Blue Green Red
0 0 0 1
1 1 0 0
2 0 1 0
Scikit-Learn OneHotEncoder:
[[0. 0. 1.]
[1. 0. 0.]
[0. 1. 0.]]Do we always need to drop one dummy column?
With an intercept, a full set of one-hot columns is perfectly linearly dependent: if Red = 0 and Blue = 0, Green must be 1. For unregularized linear models, dropping one category is a common way to make the design matrix full rank and make coefficients easier to interpret.
Important: dropping a category is not a universal rule. Tree models do not need it, and regularized linear models can often work with all one-hot columns. Choose based on the model and your goal.
pd.get_dummies(df, drop_first=True)
# or
OneHotEncoder(drop='first')3. Ordinal Encoding
Ordinal Encoding is for input features that truly have an order. A familiar example is T-shirt size: Small comes before Medium, and Medium comes before Large.
Important: the codes preserve the order, but they do not automatically prove equal distance. Code 2 does not mean Large is "twice as large" as Medium.
from sklearn.preprocessing import OrdinalEncoder
data = [['Small'], ['Medium'], ['Large']]
# Tell the encoder the real order
encoder = OrdinalEncoder(categories=[['Small', 'Medium', 'Large']])
encoded = encoder.fit_transform(data)
print(encoded)[[0.] [1.] [2.]]
4. Binary Encoding
Binary Encoding first converts categories into integers and then into binary format. One-Hot Encoding becomes inefficient for "high-cardinality" features (too many unique categories). Binary Encoding heavily reduces dimensionality.
| Category | Integer | Binary |
|---|---|---|
| Red | 1 | 001 |
| Blue | 2 | 010 |
| Green | 3 | 011 |
category_encodersPython package. The exact bit columns are determined by the encoder's learned mapping.import category_encoders as ce
import pandas as pd
df = pd.DataFrame({'Color': ['Red', 'Blue', 'Green']})
encoder = ce.BinaryEncoder(cols=['Color'])
result = encoder.fit_transform(df)
print(result)Color_0 Color_1 0 0 1 1 1 0 2 1 1
5. Frequency Encoding
Categories are replaced with their occurrence frequency. It is efficient for large datasets but causes information loss if categories share the same frequency.
| Category | Count | Encoding |
|---|---|---|
| Red | 3 | Red becomes 3 |
| Blue | 2 | Blue becomes 2 |
| Green | 1 | Green becomes 1 |
Frequency encoding is compact, but two different categories with the same count receive the same number. It also makes sense only when category frequency carries useful information for the problem.
import pandas as pd
data = pd.Series(['Red', 'Blue', 'Red', 'Green', 'Red', 'Blue'])
freq = data.value_counts()
print("Frequencies:\n", freq)
# Map the frequencies back to original data
encoded = data.map(freq)
print("\nEncoded Data:\n", encoded.to_numpy())Frequencies: Red 3 Blue 2 Green 1 dtype: int64 Encoded Data: [3 2 3 1 3 2]
6. Target Encoding
Target Encoding replaces a category with a number learned from the target. For a binary target such as Purchased = 1 andNot Purchased = 0, the basic intuition is the average target value for each category.
| City | Training targets | Simple raw mean |
|---|---|---|
| Delhi | 1, 1 | (1 + 1) ÷ 2 = 1.0 |
| Mumbai | 0, 0 | (0 + 0) ÷ 2 = 0.0 |
| Chennai | 1 | 1 ÷ 1 = 1.0 |
import pandas as pd
from sklearn.preprocessing import TargetEncoder
df = pd.DataFrame({
'City': ['Delhi', 'Mumbai', 'Delhi', 'Chennai', 'Mumbai'],
'Purchased': [1, 0, 1, 1, 0]
})
encoder = TargetEncoder(cv=2)
# fit_transform uses cross-fitting on the training data
encoded_city = encoder.fit_transform(df[['City']], df['Purchased'])
print(encoded_city.shape)(5, 1) # The encoded values are learned using cross-fitting and smoothing.
Target Encoding is an advanced choice. It can be useful for high-cardinality features, but it should normally be evaluated inside a proper validation or cross-validation workflow.
Pipelines & ColumnTransformer
Real datasets contain a mix of Numerical and Categorical features. We can preprocess them together cleanly using ColumnTransformer.
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
categorical_cols = ['Gender', 'City']
# Define the preprocessing steps
preprocessor = ColumnTransformer(
transformers=[
('cat', OneHotEncoder(handle_unknown='ignore'), categorical_cols)
]
)
# Bundle preprocessing and modeling into a Pipeline
pipeline = Pipeline([
('preprocessor', preprocessor),
('model', LogisticRegression())
])
# Now you can just use pipeline.fit(X_train, y_train)The High Cardinality Problem
High Cardinality means that a feature has many unique categories—for example thousands of product codes or locations. One-hot encoding can then create a very wide sparse feature matrix, which may increase memory use, computation time, and overfitting risk.
Possible alternatives: group rare categories, use frequency/count encoding, binary or hashing-style encoders, carefully cross-fitted target encoding, native categorical support in an appropriate model, or learned embeddings in deep learning. The best choice depends on the data and model.
Real-World Applications
- Product category: One-hot when the number of categories is manageable
- Size: Ordinal when Small < Medium < Large is meaningful
- Large brand list: Consider grouping rare brands or a high-cardinality method
- Risk band: Ordinal if Low < Medium < High is the intended meaning
- Occupation: One-hot when cardinality is manageable
- Location: Choose an approach based on cardinality and whether location meaning is useful
- Severity: Ordinal if the clinical levels have a true order
- Blood group: One-hot because A, B, AB and O have no numeric order
- Hospital: High-cardinality methods may be considered if many hospitals are present
Common Beginner Mistakes
- ❌ Using
LabelEncoderon ordinary nominal input features. - ❌ Automatically one-hot encoding extremely high-cardinality columns without checking the resulting feature width and model requirements.
- ❌ Using different encoders for training and testing data (Always use
fit_transformon train, and ONLYtransformon test). - ❌ Ignoring unseen categories during prediction instead of deciding how the encoder should handle them.
- ❌ Allowing data leakage when using Target Encoding.
- ❌ Treating identifier columns such as customer IDs as ordinary categories without asking whether they should be used at all.
Practice Checkpoint
Interview Knowledge Test
Scenario:
You are building an ML model to predict University admissions. The dataset contains Education Level with values['High School', 'Bachelor', 'Master', 'PhD'] andStudent Home State with values from different states.
Which feature is naturally ordinal and which is nominal? Also, what would you do if the dataset contained an Application Statuscolumn whose values included Accepted?
Reveal Answer
Education Level: Ordinal Encoding may be reasonable.There is a meaningful educational progression, so you can explicitly define the intended order. Whether this feature should be treated as equally spaced still depends on the model and problem.
Home State: Nominal. Karnataka is not mathematically "greater" than Kerala. One-hot encoding is a straightforward choice when the number of categories is manageable; other approaches can be evaluated when cardinality is high.
Application Status containing Accepted: check for leakage first.If the target is whether a student will be admitted, a status that already says Accepted may reveal the answer. The correct action may be to remove or time-restrict that feature rather than encode it.
Common Questions About Categorical Encoding
Label Encoding vs One-Hot Encoding — what is the difference?
In Scikit-learn, LabelEncoder is for target labels y. OneHotEncoder is for categorical input features where categories do not have a natural order.
When should I use Ordinal Encoding?
Use it when the input categories have a genuine order, such as Low < Medium < High. Do not invent an order simply because an encoder can assign integers.
What if a new category appears after training?
Decide this during preprocessing. For example, OneHotEncoder can be configured with handle_unknown='ignore', while other encoders provide their own unknown-category strategies.
Where this fits in the learning path
First clean missing values in Handling Missing Data, then encode categories here. Continue with Feature Scaling andFeature Engineering. For reusable preprocessing pipelines, revisitScikit-learn Essentials.