🧠 Section 1 · Foundations 🟡 Intermediate MODULE 03

Data Preprocessing — Encoding & Scaling

⏱️ 24 min read
📖 Feature Engineering
🧩 4 Quiz Questions
🏗️ 1 Challenge · 3 tasks
Your progress in Section 160%
🎯 What you'll learn: Most scikit-learn algorithms only accept numeric input, and many are sensitive to the scale of that input — so raw data almost never goes straight into a model. This lesson covers turning categorical columns into numbers with OneHotEncoder and LabelEncoder, rescaling numeric columns with StandardScaler and MinMaxScaler, and the single rule that prevents the most common data leakage bug: fit preprocessing on the training set only.

Why Raw Data Usually Isn't ML-Ready

A typical pandas DataFrame straight out of a CSV mixes numbers, text categories, and inconsistent ranges — all in one table. Two problems come up constantly:

🔤
Text columns aren't numeric
A column like city or payment_method holds strings. Scikit-learn's math-based algorithms need numbers, so categories must be converted before fitting.
📏
Numeric columns live on different scales
A dataset might have age in the 20–80 range and income in the tens of thousands. Left as-is, some algorithms treat the bigger-magnitude column as automatically more important.
# Raw data — not yet ready for most sklearn models
ageincomecitysubscribed
02542000Lahoreyes
14181000Karachino
23355000Lahoreyes

By the end of this lesson, that table needs to become: city converted into numeric columns, subscribed converted into a numeric target, and age/income rescaled onto a comparable range — all without letting any information from the test set influence how those transforms are learned.

Categorical Encoding

There are two encoders you'll reach for constantly, and picking the wrong one for the job silently damages a model.

OneHotEncoder — for unordered (nominal) categories

One-hot encoding creates a separate binary (0/1) column for each category. Use it for features where the categories have no natural order — like city or payment_method — so the model doesn't assume "Karachi" is somehow greater than "Lahore" the way it would if you encoded them as 0 and 1 on a single column.

onehot_encoding.py
PYTHON
from sklearn.preprocessing import OneHotEncoder
import numpy as np

cities = np.array([['Lahore'], ['Karachi'], ['Lahore'], ['Islamabad']])

# sparse_output=False returns a plain NumPy array (sklearn >= 1.2;
# use sparse=False on older versions)
encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(cities)

print(encoded)
print(encoder.categories_)
# Resulting columns after one-hot encoding "city"
city_Islamabadcity_Karachicity_Lahore
0001
1010
2001
3100

LabelEncoder — mainly for the target column

LabelEncoder converts categories into a single column of integers (0, 1, 2, …). It's the right tool for encoding a classification target (y), where a single integer column is exactly what's expected. It's generally the WRONG tool for an input feature with unordered categories, because a single numeric column implies an order and a distance between categories that usually isn't real.

label_encoding.py
PYTHON
from sklearn.preprocessing import LabelEncoder

# A typical use: encoding the target column y, not a feature
labels = ['no', 'yes', 'yes', 'no']

le = LabelEncoder()
y_encoded = le.fit_transform(labels)

print(y_encoded)         # [0 1 1 0]
print(le.classes_)      # ['no' 'yes'] — alphabetical order by default
⚠️
When categories DO have a real order, use OrdinalEncoder instead
For a genuinely ordered feature like size with values small/medium/large, scikit-learn's OrdinalEncoder lets you specify the correct category order explicitly. Neither OneHotEncoder (which throws away the order) nor a default LabelEncoder (which orders alphabetically, not logically) handles this correctly out of the box.

Feature Scaling

Once every column is numeric, the columns still might live on very different scales. Scaling rewrites each numeric column onto a comparable range without changing the relationships within that column.

StandardScaler — zero mean, unit variance

Standardization
z = (x − mean) / standard_deviation
Every value is converted to how many standard deviations it sits from the column's mean. The result has mean 0 and standard deviation 1.

MinMaxScaler — squeeze into a fixed range

Min-Max Normalization
x' = (x − min) / (max − min)
Every value is rescaled to fall between 0 and 1 (by default), based on the column's observed minimum and maximum.
scaling.py
PYTHON
from sklearn.preprocessing import StandardScaler, MinMaxScaler

standard = StandardScaler()
X_train_std = standard.fit_transform(X_train)

minmax = MinMaxScaler()
X_train_mm = minmax.fit_transform(X_train)

print("Standardized mean ≈ 0:", X_train_std.mean(axis=0).round(2))
print("MinMax range is [0, 1]:", X_train_mm.min(), X_train_mm.max())
Which one, when?
StandardScaler is the safer default for most algorithms and tolerates outliers a bit better. MinMaxScaler is preferred when you specifically need values bounded in a fixed range (some neural network activation functions expect this), but it's more sensitive to outliers since a single extreme value stretches the whole range.

Why scaling matters for some algorithms and not others:

📐
Distance-based algorithms NEED scaling
K-Nearest Neighbors and Support Vector Machines compute distances between points. Without scaling, a feature measured in the thousands (like income) dominates the distance calculation over a feature measured in tens (like age), regardless of which is actually more predictive.
🌳
Tree-based models generally DON'T need it
Decision trees and random forests split data based on threshold comparisons on one feature at a time ("is age > 30?"). Rescaling a column doesn't change the order of values, so it doesn't change which splits the tree finds useful.

The Fit-on-Train-Only Rule

Every encoder and scaler in scikit-learn has both a .fit() step (learn parameters — the mean/std for a scaler, the category list for an encoder) and a .transform() step (apply those learned parameters). The rule that prevents data leakage: fit only on the training set, then use that same fitted object to transform both the training set and the test set.

fit_train_only.py
PYTHON
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

scaler = StandardScaler()

# CORRECT: fit_transform on train, transform (no fit!) on test
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# WRONG — do not do this:
# X_test_scaled = scaler.fit_transform(X_test)  # refits on test stats!
⚠️
Why re-fitting on the test set is a real bug, not a style preference
If you call .fit_transform() on the test set, the scaler learns a DIFFERENT mean and standard deviation than the one it used for training — and worse, that mean/std is computed partly from data the model is supposed to have never seen. The model's reported test performance becomes unrealistically optimistic, and it will behave differently the moment it meets real new data that wasn't part of computing that second fit.

This is exactly what scikit-learn's Pipeline class from Lesson 2 protects you from — inside a Pipeline, .fit() is only ever called on training folds, and .transform() is applied everywhere else automatically.

Lesson Summary

Use OneHotEncoder for unordered categorical features, LabelEncoder mainly for the target column.
StandardScaler centers data to mean 0 / std 1; MinMaxScaler squeezes it into a fixed range like [0, 1].
Scaling matters for distance-based algorithms (KNN, SVM), and is largely unnecessary for tree-based models.
Always fit on the training set only, then .transform() both train and test with that same fitted object.
🧩 Knowledge Check — Lesson 3
4 questions on encoding, scaling, and avoiding data leakage.
1. You have an unordered categorical feature "payment_method" with values card/cash/wallet. Which encoder is the right choice?
2. Which algorithm family is MOST sensitive to unscaled features?
3. What's wrong with calling scaler.fit_transform(X_test) after already calling scaler.fit_transform(X_train)?
4. What does StandardScaler do to a numeric column?
💪
Try It Yourself — Lesson 3
Encode and scale a small dataset · Intermediate Level

Hands-on practice with the exact functions from this lesson.

Task 1: One-hot encode a feature 🏷️

Using the pattern from Section 2, one-hot encode this list of categories: ['red', 'blue', 'green', 'blue', 'red']. Print the resulting array and encoder.categories_.
Task 2: Scale two ways and compare 📏

Given X_train = np.array([[10], [20], [30], [40], [50]]), apply both StandardScaler and MinMaxScaler, and print both results side by side. Confirm the MinMax output stays between 0 and 1.
Task 3: Spot the leakage bug 🐛

Below is a snippet with a data leakage bug. Rewrite it correctly.

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2)
💡 Show hints if you're stuck
  • Task 1: encoder.categories_ lists the alphabetically-sorted unique categories the encoder learned, e.g. array(['blue', 'green', 'red']).
  • Task 2: MinMax on this data gives [0, 0.25, 0.5, 0.75, 1.0]. Standard gives values centered around 0, symmetric since the data is evenly spaced.
  • Task 3: The bug is scaling BEFORE splitting. Correct order: split first, then scaler.fit_transform(X_train) and scaler.transform(X_test) separately.
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 3 Complete!

You can now encode categorical features, scale numeric ones, and know exactly why fit-on-train-only matters. Next: getting the train/test split itself right, plus cross-validation.

Module 03 of 24 Section 1 — What is Machine Learning?