Welcome to the world of Principal Component Analysis! 📊 In this comprehensive guide, we'll explore one of the most powerful dimensionality reduction techniques in machine learning. Think of it as finding the "essence" of your data!
- What is PCA?
- How PCA Works
- The Mathematical Foundation
- Implementation Details
- Step-by-Step Example
- Real-World Applications
- Understanding the Code
- Model Evaluation
Principal Component Analysis (PCA) is a dimensionality reduction technique that transforms high-dimensional data into a lower-dimensional space while preserving as much information (variance) as possible.
Real-world analogy: Imagine taking a photo of a 3D sculpture. The photo is 2D, but if you position the camera correctly, you can capture most of the important details. PCA does exactly this - it finds the best "angles" (principal components) to view your data!
| Aspect | Details |
|---|---|
| Algorithm Type | Unsupervised, Linear Transformation |
| Purpose | Dimensionality Reduction, Feature Extraction |
| Tasks | Data Compression, Visualization, Noise Reduction |
| Output | Lower-dimensional representation of data |
| Key Benefit | Reduces complexity while preserving information |
"Find the directions of maximum variance in your data"
PCA works by:
- Finding directions (principal components) where data varies most
- Ranking them by importance (variance explained)
- Projecting data onto the top components
- Discarding less important dimensions
Step 1: Center the data (subtract mean from each feature)
↓
Step 2: Compute covariance matrix (how features vary together)
↓
Step 3: Calculate eigenvectors & eigenvalues of covariance matrix
↓
Step 4: Sort eigenvectors by eigenvalues (descending)
↓
Step 5: Select top k eigenvectors (principal components)
↓
Step 6: Project data onto principal components
Original Data (2D):
y
4 | ●
3 | ● ●
2 | ● ●
1 | ●
0 +--------- x
0 1 2 3 4
PCA finds direction of maximum spread:
y
4 | ●
3 | ● / ● ← PC1 (diagonal direction)
2 | ● / ● Maximum variance here!
1 | ●
0 +--------- x
0 1 2 3 4
After projection to 1D:
●-●-●-●-● ← All points on PC1 line
(Most information preserved!)
Benefits of PCA:
High Dimensions (100 features):
❌ Hard to visualize
❌ Slow to train models
❌ Risk of overfitting
❌ Curse of dimensionality
❌ Lots of noise
↓ PCA ↓
Low Dimensions (10 features):
✅ Easy to visualize
✅ Fast training
✅ Better generalization
✅ Reduced complexity
✅ Noise filtered out
Original data: 100 features
PCA with 10 components: 95% variance retained
Information preserved: 95%
Dimensions reduced: 90%
Speed improvement: 10x faster!
First, we center the data by subtracting the mean:
X_centered = X - mean(X)
Why? PCA finds directions of variance from the mean. If data isn't centered, the first component might just point toward the mean!
Example:
Original data:
X = [[1, 2],
[2, 4],
[3, 6]]
Mean: [2, 4]
Centered:
X_centered = [[-1, -2],
[ 0, 0],
[ 1, 2]]Visualization:
Before centering: After centering:
y y
6 | ● 2 | ●
4 | ● 0 | ● (mean at origin)
2 | ● -2 | ●
0 +---- x 0 +---- x
0 2 4 -2 0 2
The covariance matrix measures how features vary together:
Cov(X) = (X^T × X) / (n - 1)
For 2 features:
Cov = [ Var(x₁) Cov(x₁,x₂) ]
[ Cov(x₂,x₁) Var(x₂) ]
Interpretation:
- Diagonal elements: Variance of each feature
- Off-diagonal elements: Covariance between features
- Positive covariance: Features increase together
- Negative covariance: One increases, other decreases
Example:
X = [[-1, -2],
[ 0, 0],
[ 1, 2]]
Cov = [[ 1.0, 2.0], # Var(x₁)=1, Cov(x₁,x₂)=2
[ 2.0, 4.0]] # Cov(x₂,x₁)=2, Var(x₂)=4Meaning: x₂ has more variance (4 vs 1), and they're positively correlated (cov=2)
We decompose the covariance matrix:
Cov × v = λ × v
where:
v = eigenvector (direction of principal component)
λ = eigenvalue (variance along that direction)
Intuitive Meaning:
- Eigenvector: Direction in space
- Eigenvalue: How much data spreads in that direction
Example:
Cov = [[1, 2],
[2, 4]]
Eigenvalues: λ₁ = 5.0, λ₂ = 0.0
Eigenvectors: v₁ = [0.45, 0.89], v₂ = [-0.89, 0.45]Visualization:
y
4 | ●
3 | ●↗ ● ← PC1: direction of v₁ (most variance)
2 | ●↗ ●
1 | ● PC2: direction of v₂ (no variance)
0 +--------- x
0 1 2 3 4
PC1 (eigenvalue=5.0): Main direction of data spread
PC2 (eigenvalue=0.0): Perpendicular, no spread
Principal components are the eigenvectors sorted by eigenvalues:
PC1 = eigenvector with largest eigenvalue (most important)
PC2 = eigenvector with 2nd largest eigenvalue
...
PCₙ = eigenvector with smallest eigenvalue (least important)
Properties:
- Orthogonal: All PCs are perpendicular to each other
- Ordered: PC1 explains most variance, PC2 second-most, etc.
- Uncorrelated: Features in PC space are independent
To transform data to principal component space:
X_transformed = X_centered × PC^T
where:
X_centered: centered data (n_samples × n_features)
PC: principal components matrix (n_components × n_features)
X_transformed: projected data (n_samples × n_components)
Example:
X_centered = [[-1, -2], PC = [[0.45, 0.89]] (just PC1)
[ 0, 0],
[ 1, 2]]
X_transformed = X_centered × PC^T
= [[-1×0.45 + -2×0.89], = [[-2.23],
[ 0×0.45 + 0×0.89], [ 0.00],
[ 1×0.45 + 2×0.89]] [ 2.23]]
Reduced from 2D to 1D!How much information each component captures:
Explained Variance Ratio = λᵢ / Σλⱼ
where:
λᵢ = eigenvalue of component i
Σλⱼ = sum of all eigenvalues
Example:
Eigenvalues: [5.0, 3.0, 1.0, 0.5]
Total: 9.5
Variance ratios:
PC1: 5.0/9.5 = 0.526 (52.6%)
PC2: 3.0/9.5 = 0.316 (31.6%)
PC3: 1.0/9.5 = 0.105 (10.5%)
PC4: 0.5/9.5 = 0.053 (5.3%)
Cumulative:
PC1: 52.6%
PC1+PC2: 84.2%
PC1+PC2+PC3: 94.7% ← Keep 3 components for ~95% variance!
All: 100%To transform back to original space:
X_reconstructed = X_transformed × PC + mean
where:
X_transformed: data in PC space
PC: principal components
mean: original data mean
Note: If we kept all components, reconstruction is perfect. If we dropped some, there's information loss.
Our implementation includes the following key components:
class PrincipalComponentAnalysis:
def __init__(self, n_components=None):
self.n_components = n_components
self.components_ = None # Principal components
self.mean_ = None # Data mean
self.explained_variance_ = None # Variance per component
self.explained_variance_ratio_ = None # Proportion of variance-
__init__(n_components)- Initialize model- n_components: Number of components to keep
- Can be int (exact number) or float (variance threshold)
- None = keep all components
-
fit(X)- Compute principal components- Centers the data
- Computes covariance matrix
- Finds eigenvectors and eigenvalues
- Sorts and selects top components
-
transform(X)- Project data to PC space- Centers data using training mean
- Multiplies by principal components
- Returns lower-dimensional representation
-
fit_transform(X)- Convenience method- Combines fit() and transform()
- Returns transformed data directly
-
inverse_transform(X_transformed)- Reconstruct data- Projects back to original space
- Adds back the mean
- Returns approximation of original data
-
score(X)- Evaluate model fit- Measures reconstruction error
- Lower error = better fit
- Based on mean squared error
Let's walk through a complete example with simple 2D data to understand every step:
import numpy as np
# Original data: 5 samples, 2 features
X = np.array([
[1, 2],
[2, 4],
[3, 6],
[4, 8],
[5, 10]
])
print("Original data shape:", X.shape) # (5, 2)Visualization:
y
10 | ●
8 | ●
6 | ●
4 | ●
2 | ●
0 +----------- x
0 1 2 3 4 5
Data lies roughly on a line!
Perfect for PCA to 1D
mean = np.mean(X, axis=0) # [3, 6]
X_centered = X - mean
print("Centered data:")
print(X_centered)
# [[-2, -4],
# [-1, -2],
# [ 0, 0],
# [ 1, 2],
# [ 2, 4]]After centering:
y
4 | ●
2 | ●
0 | ● (now centered at origin)
-2 | ●
-4 | ●
+----------- x
-2 -1 0 1 2
cov = np.cov(X_centered.T)
print("Covariance matrix:")
print(cov)
# [[ 2.5, 5.0],
# [ 5.0, 10.0]]Interpretation:
- Var(x) = 2.5: x varies from -2 to 2
- Var(y) = 10.0: y varies from -4 to 4 (more variance!)
- Cov(x,y) = 5.0: strong positive correlation
eigenvalues, eigenvectors = np.linalg.eig(cov)
print("Eigenvalues:", eigenvalues)
# [12.5, 0.0]
print("Eigenvectors:")
print(eigenvectors)
# [[ 0.447, 0.894], ← eigenvector 1
# [ 0.894, -0.447]] ← eigenvector 2Analysis:
- λ₁ = 12.5: First component captures ALL variance!
- λ₂ = 0.0: Second component has NO variance
- This makes sense - data is perfectly linear
idx = eigenvalues.argsort()[::-1] # [0, 1] (already sorted)
eigenvalues = eigenvalues[idx] # [12.5, 0.0]
eigenvectors = eigenvectors[:, idx]
explained_variance_ratio = eigenvalues / sum(eigenvalues)
print("Variance explained:", explained_variance_ratio)
# [1.0, 0.0] ← PC1 explains 100% of variance!# Keep just 1 component (reduces 2D → 1D)
n_components = 1
PC = eigenvectors[:, :n_components].T
print("Principal Component:")
print(PC) # [[0.447, 0.894]]Visualization:
y
4 | ●
2 | ●↗ ← PC1 direction: [0.447, 0.894]
0 | ●↗
-2 | ●↗
-4 | ●
+----------- x
-2 -1 0 1 2
PC1 points along the data spread!
X_transformed = X_centered @ PC.T
print("Transformed data (1D):")
print(X_transformed)
# [[-4.47],
# [-2.24],
# [ 0.00],
# [ 2.24],
# [ 4.47]]Result:
Original (2D): Transformed (1D):
y
4 | ● 4.47 ●
2 | ● 2.24 ●
0 | ● → 0.00 ●
-2 | ● -2.24 ●
-4 | ● -4.47 ●
+---- x
Reduced from 2D to 1D!
Information preserved: 100%
from _11_pca import PrincipalComponentAnalysis
# Create and fit PCA
pca = PrincipalComponentAnalysis(n_components=1)
X_reduced = pca.fit_transform(X)
print("Reduced data shape:", X_reduced.shape) # (5, 1)
print("Variance explained:", pca.explained_variance_ratio_[0]) # 1.0
# Reconstruct
X_reconstructed = pca.inverse_transform(X_reduced)
print("Reconstruction error:", np.mean((X - X_reconstructed)**2)) # ~0Reduce image file size while preserving quality:
- Input: High-resolution image (millions of pixels)
- Output: Compressed representation
- Example: "Compress 1MB image to 100KB with 95% quality"
Visualize high-dimensional data in 2D/3D:
- Input: Dataset with 100+ features
- Output: 2D/3D projection for plotting
- Example: "Visualize customer segments in 2D"
Remove noise while keeping signal:
- Input: Noisy measurements
- Output: Clean data (keeping top PCs)
- Example: "Clean sensor data by removing noise components"
Create better features for ML models:
- Input: Many correlated features
- Output: Fewer, uncorrelated features
- Example: "Transform 50 features → 10 principal components"
Represent faces compactly:
- Input: Face images (thousands of pixels)
- Output: Compact face representation (50 components)
- Example: "Recognize faces using 50 eigenfaces"
Analyze gene expression data:
- Input: Expression levels of 20,000 genes
- Output: Key patterns (principal components)
- Example: "Find main patterns in cancer gene expression"
Find latent factors in user preferences:
- Input: User-item ratings matrix
- Output: Latent features (user/item factors)
- Example: "Find movie preference patterns"
Identify main market factors:
- Input: Returns of 500 stocks
- Output: Main market factors (5-10 components)
- Example: "Find main drivers of stock market"
Let's break down the key parts of our implementation:
self.mean_ = np.mean(X, axis=0)
X_centered = X - self.mean_What this does:
# Example
X = [[1, 10],
[2, 20],
[3, 30]]
mean = [2, 20] # average of each column
X_centered = [[-1, -10], # 1-2=-1, 10-20=-10
[ 0, 0], # 2-2=0, 20-20=0
[ 1, 10]] # 3-2=1, 30-20=10Why necessary?
- PCA measures variance from the mean
- Without centering, results are wrong!
- The first PC might just point to the data mean
covariance_matrix = np.cov(X_centered.T)What np.cov does:
# For each pair of features i,j:
cov[i,j] = sum((X[:,i] - mean[i]) * (X[:,j] - mean[j])) / (n-1)
# Result is symmetric matrix:
# feature1 feature2
# f1 [ var1 cov12 ]
# f2 [ cov21 var2 ]Interpretation:
cov = [[2.0, 1.5],
[1.5, 3.0]]
# Means:
# - Feature 1 has variance 2.0
# - Feature 2 has variance 3.0
# - They're positively correlated (cov=1.5)eigenvalues, eigenvectors = np.linalg.eig(covariance_matrix)What this finds:
For covariance matrix C, finds vectors v and values λ such that:
C × v = λ × v
Results:
- eigenvalues: [λ₁, λ₂, ..., λₙ] (variance along each PC)
- eigenvectors: [v₁, v₂, ..., vₙ] (direction of each PC)
Example:
C = [[2, 1],
[1, 2]]
eigenvalues = [3, 1]
eigenvectors = [[0.707, 0.707], # PC1: diagonal direction
[0.707, -0.707]] # PC2: other diagonalidx = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]Step by step:
# Before sorting
eigenvalues = [1.0, 5.0, 3.0]
# argsort gives indices that would sort
idx = eigenvalues.argsort() # [0, 2, 1]
# [::-1] reverses to get descending order
idx = idx[::-1] # [1, 2, 0]
# Apply sorting
eigenvalues = eigenvalues[idx] # [5.0, 3.0, 1.0] ✓
eigenvectors = eigenvectors[:, idx] # columns reorderedif isinstance(self.n_components, float) and 0 < self.n_components < 1:
# Keep enough for desired variance
cumsum = np.cumsum(self.explained_variance_ratio_)
self.n_components_ = np.argmax(cumsum >= self.n_components) + 1How this works:
explained_variance_ratio = [0.5, 0.3, 0.15, 0.05]
cumsum = [0.5, 0.8, 0.95, 1.0]
# Want 95% variance
n_components = 0.95
# Find first index where cumsum >= 0.95
np.argmax(cumsum >= 0.95) # returns 2
n_components_ = 2 + 1 = 3 # need 3 componentsX_transformed = np.dot(X_centered, self.components_.T)Matrix multiplication:
X_centered: (n_samples × n_features)
components: (n_components × n_features)
components.T: (n_features × n_components)
Result: (n_samples × n_components)
# Example:
X_centered = [[-1, -2, -3], components = [[0.5, 0.5, 0.7],
[ 0, 0, 0], [0.7, 0.0, 0.7]]
[ 1, 2, 3]]
components.T = [[0.5, 0.7],
[0.5, 0.0],
[0.7, 0.7]]
X_transformed = X_centered × components.T
= [[-1×0.5+-2×0.5+-3×0.7, -1×0.7+-2×0.0+-3×0.7],
[ 0×0.5+ 0×0.5+ 0×0.7, 0×0.7+ 0×0.0+ 0×0.7],
[ 1×0.5+ 2×0.5+ 3×0.7, 1×0.7+ 2×0.0+ 3×0.7]]
= [[-3.6, -2.8],
[ 0.0, 0.0],
[ 3.6, 2.8]]
Reduced from 3D to 2D!X_reconstructed = np.dot(X_transformed, self.components_) + self.mean_How it works:
# Forward: X_centered × PC.T = X_transformed
# Backward: X_transformed × PC = X_centered_reconstructed
X_transformed: (n_samples × n_components)
components: (n_components × n_features)
Result: (n_samples × n_features)
Then add back mean to get back to original scaleExample:
X_transformed = [[2.0],
[0.0],
[-2.0]]
components = [[0.7, 0.7]] # 1 component
X_centered_recon = X_transformed × components
= [[2.0×0.7, 2.0×0.7],
[0.0×0.7, 0.0×0.7],
[-2.0×0.7, -2.0×0.7]]
= [[ 1.4, 1.4],
[ 0.0, 0.0],
[-1.4, -1.4]]
X_reconstructed = X_centered_recon + mean
= [[ 1.4, 1.4], + [3, 5]
[ 0.0, 0.0],
[-1.4, -1.4]]
= [[4.4, 6.4],
[3.0, 5.0],
[1.6, 3.6]]Shows how much information each component captures:
pca = PrincipalComponentAnalysis(n_components=None)
pca.fit(X)
print("Explained variance:")
for i, var in enumerate(pca.explained_variance_):
print(f" PC{i+1}: {var:.2f}")
print("\nExplained variance ratio:")
for i, ratio in enumerate(pca.explained_variance_ratio_):
print(f" PC{i+1}: {ratio:.4f} ({ratio*100:.2f}%)")Output example:
Explained variance:
PC1: 12.50
PC2: 3.20
PC3: 0.80
PC4: 0.20
Explained variance ratio:
PC1: 0.7561 (75.61%) ← Most important!
PC2: 0.1935 (19.35%)
PC3: 0.0484 (4.84%)
PC4: 0.0121 (1.21%) ← Least important
Shows total variance retained with k components:
cumulative = np.cumsum(pca.explained_variance_ratio_)
print("Cumulative variance:")
for i, cum in enumerate(cumulative):
print(f" First {i+1} components: {cum:.4f} ({cum*100:.2f}%)")Output:
Cumulative variance:
First 1 components: 0.7561 (75.61%)
First 2 components: 0.9496 (94.96%) ← 2 components for ~95%!
First 3 components: 0.9980 (99.80%)
First 4 components: 1.0000 (100.00%)
Decision: Keep 2 components to retain 95% variance!
Measures information loss from dimensionality reduction:
# Original data
X_original = X
# Reduce dimensions
X_reduced = pca.transform(X)
# Reconstruct
X_reconstructed = pca.inverse_transform(X_reduced)
# Calculate error
mse = np.mean((X_original - X_reconstructed) ** 2)
relative_error = mse / np.var(X_original)
print(f"Reconstruction MSE: {mse:.6f}")
print(f"Relative error: {relative_error:.6f}")Interpretation:
MSE = 0.001: Excellent reconstruction (very little loss)
MSE = 0.1: Good reconstruction (acceptable loss)
MSE = 1.0: Poor reconstruction (significant loss)
A scree plot shows variance explained by each component:
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
# Plot variance per component
plt.subplot(1, 2, 1)
plt.bar(range(1, len(pca.explained_variance_ratio_) + 1),
pca.explained_variance_ratio_)
plt.xlabel('Principal Component')
plt.ylabel('Variance Explained')
plt.title('Scree Plot')
# Plot cumulative variance
plt.subplot(1, 2, 2)
cumsum = np.cumsum(pca.explained_variance_ratio_)
plt.plot(range(1, len(cumsum) + 1), cumsum, marker='o')
plt.axhline(y=0.95, color='r', linestyle='--', label='95% threshold')
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Variance')
plt.title('Cumulative Variance')
plt.legend()
plt.tight_layout()
plt.show()What to look for:
Good scree plot: Bad scree plot:
|█ |█
|█ |█
|▓ |█
|▒ |█
|░ ░ ░ ░ |▓
+------- |▒ ░ ░ ░
↑ +-------
"Elbow" here: No clear elbow
Keep ~3 components Hard to decide
Shows how much each original feature contributes to each PC:
# Get loadings
loadings = pca.components_
print("Component loadings:")
print("Feature contributions to each PC:")
for i, pc in enumerate(loadings):
print(f"\nPC{i+1}:")
for j, loading in enumerate(pc):
print(f" Feature {j+1}: {loading:.4f}")Interpretation:
PC1: [0.7, 0.7, 0.0, 0.0]
→ PC1 is combination of Features 1 and 2
PC2: [0.0, 0.0, 0.7, -0.7]
→ PC2 is difference between Features 3 and 4
Use when you know exact dimensionality needed:
# Reduce to 2D for visualization
pca = PrincipalComponentAnalysis(n_components=2)
X_2d = pca.fit_transform(X)When to use:
- Visualization (2D or 3D)
- Fixed architecture requirements
- Specific dimensionality reduction goal
Keep components until reaching variance threshold:
# Keep 95% of variance
pca = PrincipalComponentAnalysis(n_components=0.95)
X_reduced = pca.fit_transform(X)
print(f"Kept {pca.n_components_} components")
print(f"Variance retained: {sum(pca.explained_variance_ratio_[:pca.n_components_]):.2%}")Common thresholds:
- 0.95 (95%): Standard choice, good balance
- 0.99 (99%): High fidelity, minimal loss
- 0.90 (90%): Aggressive reduction, faster
- 0.80 (80%): Very aggressive, major speedup
Look for "elbow" in scree plot:
Variance explained:
|
0.5 |● ← PC1: 50%
0.3 | ● ← PC2: 30%
0.1 | ● ← PC3: 10% (elbow here!)
0.05| ● ● ● ← PC4+: small contributions
+------------
1 2 3 4 5
Choose 3 components (before elbow flattens)
Evaluate with downstream task:
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
best_score = 0
best_n = 0
for n in [2, 5, 10, 15, 20, 25]:
pca = PrincipalComponentAnalysis(n_components=n)
X_reduced = pca.fit_transform(X_train)
clf = LogisticRegression()
scores = cross_val_score(clf, X_reduced, y_train, cv=5)
avg_score = np.mean(scores)
print(f"n={n:2d}: {avg_score:.4f}")
if avg_score > best_score:
best_score = avg_score
best_n = n
print(f"\nBest n_components: {best_n}")Use Fixed Number when:
✓ Visualization requirement (2D/3D)
✓ Hardware constraints
✓ Fixed model architecture
Use Variance Threshold when:
✓ Want to preserve information
✓ Balance speed vs accuracy
✓ Standard use case
Use Elbow Method when:
✓ Exploratory analysis
✓ Want intuitive selection
✓ No specific target
Use Cross-Validation when:
✓ Have labeled data
✓ Downstream task defined
✓ Need optimal performance
PCA is sensitive to feature scales because it measures variance:
Without scaling:
Feature 1: Age (20-80) → Variance ≈ 400
Feature 2: Income (20k-200k) → Variance ≈ 3,000,000,000
PCA will be dominated by income!
Age is virtually ignoredWith scaling:
Feature 1: Age (scaled) → Variance ≈ 1
Feature 2: Income (scaled) → Variance ≈ 1
Both features contribute fairly!Most common for PCA:
x_scaled = (x - mean) / std
Code:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Now: mean=0, std=1 for each feature
pca = PrincipalComponentAnalysis(n_components=0.95)
X_pca = pca.fit_transform(X_scaled)After standardization:
All features have:
- Mean = 0
- Standard deviation = 1
- Equal contribution to PCA
Alternative when you need specific range:
x_scaled = (x - min) / (max - min)
Code:
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)
# Now: all features in range [0, 1]
pca = PrincipalComponentAnalysis(n_components=0.95)
X_pca = pca.fit_transform(X_scaled)Don't scale when:
- All features already on same scale
- Features are same units (e.g., all pixels in image)
- Variance differences are meaningful
Example: Image pixels
# All pixels are 0-255, same scale
X_images = load_images() # shape: (n_images, height*width)
# Can apply PCA directly
pca = PrincipalComponentAnalysis(n_components=50)
X_pca = pca.fit_transform(X_images)-
Dimensionality Reduction
- Reduces features while keeping information
- Makes data manageable
- Speeds up training
-
Removes Correlation
- Principal components are uncorrelated
- Better for algorithms sensitive to correlation
- Cleaner feature space
-
Noise Reduction
- Small components often represent noise
- Keeping top components filters noise
- Improves signal-to-noise ratio
-
Visualization
- Reduce to 2D/3D for plotting
- Understand data structure
- Identify patterns and clusters
-
Computational Efficiency
- Fewer features = faster algorithms
- Lower memory requirements
- Scalable to large datasets
-
No Labels Needed
- Unsupervised method
- Works without target variable
- Useful for exploratory analysis
-
Interpretability Loss
- PCs are linear combinations of features
- Hard to interpret what PCs mean
- Original features more interpretable
-
Linear Assumption
- Only captures linear relationships
- Misses non-linear patterns
- May need kernel PCA for non-linear data
-
Scale Sensitive
- MUST scale features appropriately
- Results change with scaling method
- Can be misleading if not scaled
-
Variance ≠ Importance
- High variance doesn't always mean important
- Low variance features might be crucial
- May lose important information
-
Outlier Sensitive
- Outliers affect mean and variance
- Can skew principal components
- May need outlier removal first
-
Not Sparse
- PCs use all features (non-zero coefficients)
- Cannot remove irrelevant features
- Consider sparse PCA for feature selection
Good Use Cases:
- ✅ Many correlated features
- ✅ Need faster training
- ✅ High-dimensional visualization
- ✅ Remove multicollinearity
- ✅ Compress data
- ✅ Noise reduction
Bad Use Cases:
- ❌ Features already independent
- ❌ Need interpretable features
- ❌ Non-linear relationships
- ❌ Very sparse data
- ❌ Few features to begin with
- ❌ Outliers present (clean first)
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
# Load breast cancer dataset (30 features)
data = load_breast_cancer()
X, y = data.data, data.target
print(f"Original dataset: {X.shape}")
print(f"Features: {data.feature_names}")
# Split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# CRITICAL: Scale features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Fit PCA with all components (for analysis)
pca_full = PrincipalComponentAnalysis(n_components=None)
pca_full.fit(X_train_scaled)
# Analyze variance
print("\n=== Variance Analysis ===")
cumsum = np.cumsum(pca_full.explained_variance_ratio_)
for i, (var, cum) in enumerate(zip(pca_full.explained_variance_ratio_[:10], cumsum[:10])):
print(f"PC{i+1:2d}: {var:6.4f} ({var*100:5.2f}%) | Cumulative: {cum:6.4f} ({cum*100:5.2f}%)")
# Find number of components for 95% variance
n_components_95 = np.argmax(cumsum >= 0.95) + 1
print(f"\nComponents for 95% variance: {n_components_95}")
# Apply PCA with optimal components
pca = PrincipalComponentAnalysis(n_components=0.95)
X_train_pca = pca.fit_transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)
print(f"\n=== Dimensionality Reduction ===")
print(f"Original dimensions: {X_train_scaled.shape[1]}")
print(f"Reduced dimensions: {X_train_pca.shape[1]}")
print(f"Reduction: {(1 - X_train_pca.shape[1]/X_train_scaled.shape[1])*100:.1f}%")
print(f"Variance retained: {sum(pca.explained_variance_ratio_[:pca.n_components_]):.4f}")
# Train classifier on original data
print("\n=== Model Performance ===")
clf_original = LogisticRegression(max_iter=10000, random_state=42)
clf_original.fit(X_train_scaled, y_train)
y_pred_original = clf_original.predict(X_test_scaled)
acc_original = accuracy_score(y_test, y_pred_original)
print(f"Accuracy (original 30D): {acc_original:.4f}")
# Train classifier on PCA data
clf_pca = LogisticRegression(max_iter=10000, random_state=42)
clf_pca.fit(X_train_pca, y_train)
y_pred_pca = clf_pca.predict(X_test_pca)
acc_pca = accuracy_score(y_test, y_pred_pca)
print(f"Accuracy (PCA {pca.n_components_}D): {acc_pca:.4f}")
# Calculate reconstruction error
X_reconstructed = pca.inverse_transform(X_train_pca)
recon_error = np.mean((X_train_scaled - X_reconstructed) ** 2)
print(f"\nReconstruction MSE: {recon_error:.6f}")
# Visualize in 2D
pca_2d = PrincipalComponentAnalysis(n_components=2)
X_train_2d = pca_2d.fit_transform(X_train_scaled)
plt.figure(figsize=(10, 6))
colors = ['red', 'blue']
labels = ['Malignant', 'Benign']
for i, (color, label) in enumerate(zip(colors, labels)):
mask = y_train == i
plt.scatter(X_train_2d[mask, 0], X_train_2d[mask, 1],
c=color, alpha=0.6, label=label, edgecolors='k', s=50)
plt.xlabel(f'PC1 ({pca_2d.explained_variance_ratio_[0]:.1%} variance)')
plt.ylabel(f'PC2 ({pca_2d.explained_variance_ratio_[1]:.1%} variance)')
plt.title('Breast Cancer Dataset - PCA Projection (2D)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
print(f"\n2D projection retains {sum(pca_2d.explained_variance_ratio_):.2%} of variance")| Aspect | PCA | t-SNE |
|---|---|---|
| Type | Linear | Non-linear |
| Speed | Fast | Slow |
| Deterministic | Yes | No |
| Global structure | Preserved | Not preserved |
| Local structure | Not emphasized | Preserved |
| Use case | General purpose | Visualization |
| Aspect | PCA | LDA |
|---|---|---|
| Supervision | Unsupervised | Supervised |
| Goal | Maximum variance | Maximum separation |
| Labels needed | No | Yes |
| Use case | Reduce dimensions | Classification |
| Aspect | PCA | Autoencoders |
|---|---|---|
| Complexity | Linear | Non-linear |
| Training | Instant | Time-consuming |
| Interpretability | Better | Worse |
| Flexibility | Limited | High |
Not necessarily the most "important" features, but directions where data spreads most.
PCA is extremely sensitive to feature scales. Standardization is almost always necessary.
All PCs are perpendicular to each other and uncorrelated.
Use explained variance ratio to decide how many components to keep.
Can only capture linear relationships. For non-linear patterns, use kernel PCA or other methods.
Keeping fewer components means losing some information. This is a tradeoff for dimensionality reduction.
Principal Component Analysis is a fundamental technique in data science! By understanding:
- How PCA finds directions of maximum variance
- How eigenvalues and eigenvectors work
- How to choose the right number of components
- How to properly scale features
You've gained a powerful tool for:
- ✅ Reducing dimensionality
- ✅ Visualizing high-dimensional data
- ✅ Removing noise
- ✅ Speeding up ML algorithms
- ✅ Understanding data structure
When to Use PCA:
- ✅ Many correlated features
- ✅ Need visualization (2D/3D)
- ✅ Speed up training
- ✅ Remove multicollinearity
- ✅ Compress data
When to Consider Alternatives:
- ❌ Need interpretable features → Use feature selection
- ❌ Non-linear relationships → Use kernel PCA or t-SNE
- ❌ Labeled data for classification → Use LDA
- ❌ Very sparse data → Use sparse PCA
- ❌ Complex patterns → Use autoencoders
Next Steps:
- Apply PCA to your datasets
- Experiment with different numbers of components
- Combine PCA with classification/regression
- Learn about kernel PCA for non-linear data
- Explore other dimensionality reduction techniques (t-SNE, UMAP)
- Study applications in specific domains (images, genomics, finance)
Happy dimensionality reducing! 📊🎯