PCA — Dimensionality Reduction
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.
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.
sklearn.decomposition.PCA does it internally — but that's the mechanism behind explained_variance_ratio_ in Section 3.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.
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
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%.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%).
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.
# 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
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.
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()
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.
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}")
| Aspect | PCA for visualization | PCA as preprocessing |
|---|---|---|
| n_components | Fixed at 2 (or 3) — for plotting | Chosen by a variance threshold (e.g. 0.95) |
| Goal | Make structure visible to a human | Improve/speed up a downstream model |
| Success measure | Does it look interpretable / separable? | Does test-set performance improve or hold steady? |
| Typical placement | A standalone step before plt.scatter | A step inside a Pipeline, before the model |
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
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.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.
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%?
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?
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) + 1and the same with0.95give 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 bykmeans.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.