🧬 Section 4 · Unsupervised Learning 🟡 Intermediate MODULE 18

PCA — Dimensionality Reduction

⏱️ 28 min read
📖 Principal Component Analysis & Explained Variance
🧩 4 Quiz Questions
🏗️ 1 Challenge · 3 tasks
Your progress in Section 450%
🎯 What you'll learn: Real datasets often have dozens or hundreds of features, and Lesson 17's clustering algorithms (like every distance-based algorithm this course has covered) start to struggle once there are too many of them. This lesson covers PCA (Principal Component Analysis)sklearn.decomposition.PCA — the standard tool for compressing a wide feature set down to a handful of new features that capture most of the original variance. You'll learn what those new features actually mean, how to read explained_variance_ratio_, how to decide how many to keep, and the two very different jobs PCA does: making high-dimensional data visualizable in 2D, and speeding up/cleaning up another model's input as a preprocessing step.

The Curse of Dimensionality, Briefly

Every distance-based algorithm covered so far — KNN, SVM, K-Means, DBSCAN — relies on the idea that "close together" means "similar." As the number of features grows, that idea quietly breaks down: in high-dimensional space, the distance between the nearest and farthest points from any given point starts to converge, so almost everything ends up looking roughly equally far from everything else. This effect is commonly called the curse of dimensionality.

On top of that, more features usually means more noise, more redundant/correlated columns, slower training, and a higher risk of overfitting relative to the amount of training data available. PCA addresses this directly: instead of discarding features by hand, it mathematically combines all of them into a smaller set of new features that preserve as much of the original information (specifically, variance) as possible.

🐌
Slower Training
More features means more computation for every distance calculation or gradient step.
📡
Sparser Data
The same number of rows covers high-dimensional space far more thinly than low-dimensional space.
🔁
Redundant Features
Many real-world features are correlated with each other and carry overlapping information.

What PCA Actually Does

PCA looks for the direction in feature space along which the data varies the MOST, and calls that direction the first principal component (PC1). Then it looks for the next-best direction — one that's perpendicular (uncorrelated) to PC1 — and calls that PC2, and so on. Each principal component is a new axis, built as a weighted combination of the ORIGINAL features, ranked by how much of the data's total variance it captures.

📝
Where eigenvectors and eigenvalues fit in, at an intuitive level
Under the hood, PCA computes the COVARIANCE MATRIX of the (centered) features, then finds its eigenvectors and eigenvalues. Each eigenvector points in one of those "directions of maximum variance" — it becomes a principal component's direction. Its paired eigenvalue is a single number measuring how much variance lies along that direction — the bigger the eigenvalue, the more "important" that component is. Sorting eigenvectors by their eigenvalues, largest first, is exactly how PCA decides that PC1 matters more than PC2. None of the code in this lesson computes eigenvectors by hand — sklearn.decomposition.PCA does it internally — but that's the mechanism behind explained_variance_ratio_ in Section 3.
Illustrative 2D data with its two principal component directions
Points are stretched diagonally. PC1 (teal) points along the longest spread of the data; PC2 (purple) is perpendicular to PC1 and captures whatever variance is left.
PC1 PC2

sklearn.decomposition.PCA — Fitting and Explained Variance

Exactly like every distance/variance-based algorithm in this course, features need to be scaled BEFORE running PCA — otherwise a large-range feature would dominate "variance" simply because of its units, not because it's genuinely more informative.

pca_fit_basic.py
PYTHON
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X_scaled = StandardScaler().fit_transform(X)

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)

print(f"Original shape: {X_scaled.shape}")
print(f"Reduced shape:  {X_pca.shape}")
print(f"Explained variance ratio: {pca.explained_variance_ratio_}")
print(f"Total variance captured: {pca.explained_variance_ratio_.sum():.3f}")
# Original shape: (500, 8)
# Reduced shape:  (500, 2)
# Explained variance ratio: [0.42 0.23]
# Total variance captured: 0.650
Reading explained_variance_ratio_
Each entry in explained_variance_ratio_ is the proportion of the ORIGINAL total variance that one principal component captures, ordered from most to least — PC1 first. In the example above, PC1 alone explains 42% of the variance across the original 8 features, and PC1+PC2 together explain 65%. That's a real, quantifiable trade-off: reducing 8 dimensions down to 2 kept 65% of the original information and threw away the remaining 35%.
Illustrative explained variance ratio per component (8 original features)
PC1
42%
cum. 42%
PC2
23%
cum. 65%
PC3
13%
cum. 78%
PC4
8%
cum. 86%
PC5–PC8
14% total
cum. 100%

Choosing How Many Components to Keep

There's no single universally-correct number of components — it depends entirely on the goal. The most common approach: plot CUMULATIVE explained variance against the number of components, and pick the smallest number that reaches a target threshold (often 90–95%).

cumulative_variance.py
PYTHON
import numpy as np
import matplotlib.pyplot as plt

pca_full = PCA().fit(X_scaled)
cumulative = np.cumsum(pca_full.explained_variance_ratio_)

plt.plot(range(1, len(cumulative) + 1), cumulative, marker="o")
plt.axhline(y=0.95, color="r", linestyle="--", label="95% threshold")
plt.xlabel("Number of components")
plt.ylabel("Cumulative explained variance")
plt.legend()
plt.show()

n_components_95 = np.argmax(cumulative >= 0.95) + 1
print(f"Components needed for 95% variance: {n_components_95}")
# Components needed for 95% variance: 6

Scikit-learn also accepts a FLOAT for n_components, letting PCA figure out the exact count itself — a shortcut for the same idea above.

pca_variance_threshold.py
PYTHON
# n_components between 0 and 1 keeps the SMALLEST number of components
# that reaches at least that much explained variance
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X_scaled)

print(f"Reduced from {X_scaled.shape[1]} to {pca.n_components_} dimensions")
# Reduced from 8 to 6 dimensions
⚠️
"Explains more variance" is not automatically "better for the model"
PCA's variance calculation has no idea whether a downstream task is regression, classification, or clustering — it only knows about spread in the data. Occasionally, a direction with LOWER variance still turns out to carry useful predictive signal, and PCA would rank it below a higher-variance direction that's actually noise. A cumulative-variance threshold is a strong default starting point, not a mathematical guarantee — the challenge at the end of this lesson asks you to check the trade-off directly.

PCA for Visualization — Seeing High-Dimensional Data in 2D

Humans can't look at an 8-dimensional scatter plot — but by keeping only the first 2 (or 3) principal components, whatever structure exists in the full feature space often remains visible in a plot a person can actually read.

pca_visualization.py
PYTHON
import matplotlib.pyplot as plt

pca_2d = PCA(n_components=2)
X_pca = pca_2d.fit_transform(X_scaled)

plt.figure(figsize=(8, 6))
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap="viridis", alpha=0.6)
plt.xlabel("Principal Component 1")
plt.ylabel("Principal Component 2")
plt.title("PCA — 2D Projection")
plt.colorbar(scatter, label="Class")
plt.show()
Illustrative PCA 2D projection, colored by an existing label
Even though the model never used the color/class during fit_transform, the classes still separate visibly along PC1 — a sign PC1 tracks something genuinely related to the class.
Class 0
Class 1
Class 2

PCA as a Preprocessing Step Before Another Model

The second, very different use of PCA: rather than plotting the result, feed the reduced features directly into a classifier or regressor. This can speed up training, reduce overfitting on noisy or redundant features, and — exactly like StandardScaler in Lesson 16 — belongs INSIDE a Pipeline so it gets refit correctly on each cross-validation fold's training data only.

pca_preprocessing_pipeline.py
PYTHON
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split

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

pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("pca", PCA(n_components=0.95)),
    ("model", SVC(kernel="rbf"))
])
pipeline.fit(X_train, y_train)

print(f"Components kept: {pipeline.named_steps['pca'].n_components_}")
print(f"Test accuracy: {pipeline.score(X_test, y_test):.3f}")
Visualization PCA vs. preprocessing PCA — same class, two different jobs
AspectPCA for visualizationPCA as preprocessing
n_componentsFixed at 2 (or 3) — for plottingChosen by a variance threshold (e.g. 0.95)
GoalMake structure visible to a humanImprove/speed up a downstream model
Success measureDoes it look interpretable / separable?Does test-set performance improve or hold steady?
Typical placementA standalone step before plt.scatterA step inside a Pipeline, before the model
PCA components lose direct interpretability
A principal component is a weighted MIX of the original features (its weights live in pca.components_) — "PC1" doesn't mean anything as clean as "income" or "age" on its own. That's a real cost: a LinearRegression coefficient on an original feature is directly explainable to a stakeholder; a coefficient on PC3 usually isn't, without extra work inspecting components_. Weigh that against the accuracy/speed benefit before reducing dimensions on a model where interpretability matters.

Lesson Summary

The curse of dimensionality makes distance-based algorithms less reliable as feature count grows.
PCA finds new axes (principal components) along directions of maximum variance, ranked by eigenvalue.
PCA().explained_variance_ratio_ reports how much of the original variance each component captures; np.cumsum gives the running total.
n_components can be an integer count OR a float threshold (e.g. 0.95) that sklearn resolves automatically.
PCA has two distinct jobs: fixed 2D/3D projections for VISUALIZATION, and variance-threshold reduction as a PREPROCESSING step inside a pipeline.
🧩 Knowledge Check — Lesson 18
4 questions on principal components, explained variance, and PCA's two use cases.
1. What does the first principal component (PC1) represent?
2. What does pca.explained_variance_ratio_[0] = 0.42 mean?
3. Why should features be scaled with StandardScaler before running PCA?
4. A model uses PCA(n_components=0.95) inside a Pipeline before a classifier, rather than a fixed PCA(n_components=2). What's the goal in this case?
💪
Try It Yourself — Lesson 18
Reduce, visualize, and preprocess with PCA · Intermediate Level

Use any dataset from this course with at least 4–5 numeric features — Lesson 12's house-price data or Lesson 17's clustering data both work.

Task 1: Plot the cumulative variance curve 📈

Fit PCA() with no n_components limit on your scaled data, compute np.cumsum(pca.explained_variance_ratio_), and plot it like Section 4. How many components does it take to reach 90% cumulative variance? 95%?
Task 2: Make a 2D PCA scatter plot 🎨

Reduce your data to 2 components and reproduce Section 5's scatter plot, coloring points by any categorical column you have (or by a K-Means cluster label from Lesson 17 if you don't have one). Does any visible structure or separation appear?
Task 3: Compare a model with and without PCA preprocessing ⚖️

Train the same classifier or regressor twice on a train/test split — once on the raw scaled features, once on PCA-reduced features (n_components=0.95) inside a Pipeline. Compare test-set accuracy or R² between the two. Did dimensionality reduction help, hurt, or make no real difference? Write 2–3 sentences on what you found.
💡 Show hints if you're stuck
  • Task 1: np.argmax(cumulative >= 0.90) + 1 and the same with 0.95 give you both counts directly, exactly like Section 4's code.
  • Task 2: If you don't have a natural category column, running Lesson 17's KMeans(n_clusters=3) on the same scaled data first and coloring by kmeans.labels_ works well.
  • Task 3: A small drop in accuracy alongside a big drop in dimensions (and faster training) is often still a good trade — that's the real decision this task is meant to surface.
Finished this lesson?
Mark it complete to track your progress.
🎉

Lesson 18 Complete!

You can now explain what a principal component is, fit sklearn.decomposition.PCA, read explained_variance_ratio_, choose a component count by cumulative variance, and use PCA correctly for both visualization and preprocessing. Next up: turning that same "how much does a point stand out" intuition toward finding anomalies directly.

Module 18 of 24 Section 4 — Unsupervised Learning