PCA (Principal Component Analysis) › Visualizing PCA

Michael BrenndoerferJuly 21, 20253 min read
Machine Learning from ScratchAll chapters
Part 6 of 9Explainability
Chapter 48PCA (Principal Component Analysis) › Visualizing PCA
Reading section 4 of 9 · Visualizing PCA · 3 minSection 4 of 9 · 3 min

Book contents

Section 4 of 9 · 3 min read

PCA (Principal Component Analysis) › Visualizing PCAOne section · Sections in this chapter4 / 9 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

The mathematics is clearer when you see it in action. Let's visualize PCA on a simple two-dimensional dataset where we can see what's happening.

In[3]:
Code
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

for parent in [Path.cwd(), *Path.cwd().parents]:
    helper_dir = (
        parent / "apps/web/content/notebooks/1_machine-learning-from-scratch"
    )
    if (helper_dir / "mls_plot_style.py").exists():
        if str(helper_dir) not in sys.path:
            sys.path.insert(0, str(helper_dir))
        break
else:
    raise FileNotFoundError("Could not locate mls_plot_style.py")

from mls_plot_style import PALETTE, polish_axes, use_mls_style

# Set random seed for reproducibility
np.random.seed(42)

# Create correlated 2D data
n_samples = 200
mean = [0, 0]
cov = [[2, 1.5], [1.5, 2]]
X = np.random.multivariate_normal(mean, cov, n_samples)

# Standardize the data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Apply PCA
pca = PCA()
X_pca = pca.fit_transform(X_scaled)

use_mls_style(fixed_canvas=True)

# Original data
fig, ax1 = plt.subplots(figsize=(3, 2.8))
ax1.scatter(
    X_scaled[:, 0],
    X_scaled[:, 1],
    alpha=0.66,
    c=PALETTE["primary"],
    s=18,
    edgecolors=PALETTE["paper"],
    linewidths=0.3,
)
ax1.set_xlabel("Feature 1")
ax1.set_ylabel("Feature 2")
ax1.set_title("Original Data (Standardized)")
ax1.set_aspect("equal")

# Add principal component directions
pc1 = pca.components_[0] * np.sqrt(pca.explained_variance_[0])
pc2 = pca.components_[1] * np.sqrt(pca.explained_variance_[1])
ax1.arrow(
    0,
    0,
    pc1[0],
    pc1[1],
    head_width=0.1,
    head_length=0.1,
    fc=PALETTE["coral"],
    ec=PALETTE["coral"],
    linewidth=2,
)
ax1.arrow(
    0,
    0,
    pc2[0],
    pc2[1],
    head_width=0.1,
    head_length=0.1,
    fc=PALETTE["teal"],
    ec=PALETTE["teal"],
    linewidth=2,
)
ax1.text(
    pc1[0],
    pc1[1],
    "PC1",
    fontsize=8,
    color=PALETTE["coral"],
    weight="bold",
)
ax1.text(
    pc2[0],
    pc2[1],
    "PC2",
    fontsize=8,
    color=PALETTE["teal"],
    weight="bold",
)
polish_axes(ax1)
fig.subplots_adjust(left=0.18, right=0.97, bottom=0.18, top=0.86)
plt.show()

# PCA transformed data
fig, ax2 = plt.subplots(figsize=(3, 2.8))
ax2.scatter(
    X_pca[:, 0],
    X_pca[:, 1],
    alpha=0.66,
    c=PALETTE["purple"],
    s=18,
    edgecolors=PALETTE["paper"],
    linewidths=0.3,
)
ax2.set_xlabel("First Principal Component")
ax2.set_ylabel("Second Principal Component")
ax2.set_title("Data After PCA Transformation")
ax2.set_aspect("equal")
polish_axes(ax2)
fig.subplots_adjust(left=0.18, right=0.97, bottom=0.18, top=0.86)
plt.show()

# Print explained variance ratios
print("Explained variance ratios:")
for i, ratio in enumerate(pca.explained_variance_ratio_):
    print(f"PC{i + 1}: {ratio:.3f} ({ratio * 100:.1f}%)")
Out[3]:
Visualization
Scatter plot of standardized correlated data with labeled PC1 and PC2 direction arrows.
Original standardized data with principal component directions shown as coral (PC1) and teal (PC2) arrows.
Scatter plot of the data after PCA transformation in principal-component coordinates.
The same correlated data projected onto the principal component coordinate system.
Explained variance ratios:
PC1: 0.873 (87.3%)
PC2: 0.127 (12.7%)
In[4]:
Code
use_mls_style(fixed_canvas=True)

# Create scree plot
fig, ax = plt.subplots(figsize=(3, 2.8))
ax.plot(
    range(1, len(pca.explained_variance_ratio_) + 1),
    pca.explained_variance_ratio_,
    color=PALETTE["primary"],
    marker="o",
    linewidth=2,
    markersize=5,
)
ax.set_xlabel("Principal Component")
ax.set_ylabel("Explained Variance Ratio")
ax.set_title("Scree Plot")
ax.set_xticks(range(1, len(pca.explained_variance_ratio_) + 1))
polish_axes(ax)
fig.subplots_adjust(left=0.20, right=0.97, bottom=0.20, top=0.86)
plt.show()

# Cumulative variance explained
cumulative_variance = np.cumsum(pca.explained_variance_ratio_)
fig, ax = plt.subplots(figsize=(3, 2.8))
ax.plot(
    range(1, len(cumulative_variance) + 1),
    cumulative_variance,
    color=PALETTE["rose"],
    marker="o",
    linewidth=2,
    markersize=5,
)
ax.set_xlabel("Number of Components")
ax.set_ylabel("Cumulative Variance Explained")
ax.set_title("Cumulative Variance Explained")
ax.set_xticks(range(1, len(cumulative_variance) + 1))
ax.axhline(
    y=0.95,
    color=PALETTE["teal"],
    linestyle="--",
    alpha=0.85,
    label="95% threshold",
)
ax.legend(loc="lower right")
polish_axes(ax)
fig.subplots_adjust(left=0.20, right=0.97, bottom=0.20, top=0.86)
plt.show()
Out[4]:
Visualization
Line plot of individual explained variance ratios for principal components one and two.
Scree plot showing the explained variance ratio for each principal component.
Line plot of cumulative explained variance with a horizontal 95 percent threshold.
Cumulative variance explained as components are added, with a dashed 95% threshold.