Chapter 115 · Practice Challenges, Solved¶
Five dimensionality-reduction exercises, worked in scikit-learn on the Chapter 115 food-nutrition table.
import numpy as np, pandas as pd, matplotlib.pyplot as plt
import seaborn as sns # seaborn = high-level statistical plots (heatmaps, pairplots, count/bar plots)
from matplotlib.colors import ListedColormap
EM="#4338ca"; DEEP="#3730a3"; LIGHT="#c7d2fe"; INK="#1a2138"; GRID="#e6e9f2"; RED="#ef4444"; AMBER="#d97706"; GREEN="#059669"; BLUE="#2563eb"; PUR="#9333ea"; GREY="#94a3b8"; SLATE="#475569"; ORG="#4338ca"; CYAN="#0891b2"
plt.rcParams.update({"figure.facecolor":"white","axes.facecolor":"white","figure.dpi":110,"font.size":11,
"axes.edgecolor":GRID,"axes.grid":True,"grid.color":GRID,"axes.axisbelow":True,"axes.spines.top":False,
"axes.spines.right":False,"axes.titlesize":12,"axes.titleweight":"bold","legend.frameon":False})
sns.set_style("whitegrid")
BASE="https://raw.githubusercontent.com/johnfisher-ai/Statistics-Data-Science-AI-Visual-Book/main/data/"
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
try: df = pd.read_excel('../../data/dimensionality-reduction--foods.xlsx', sheet_name='Data')
except FileNotFoundError: df = pd.read_excel(BASE + 'dimensionality-reduction--foods.xlsx', sheet_name='Data')
num = ['calories','fat_g','satfat_g','protein_g','carbs_g','sugar_g','fiber_g','sodium_mg']
Xs = StandardScaler().fit_transform(df[num])
print(df.shape)
(180, 10)
pca = PCA().fit(Xs)
cum = np.cumsum(pca.explained_variance_ratio_)
k = int(np.argmax(cum >= 0.90) + 1)
print('cumulative variance:', [round(x,3) for x in cum])
print('components needed for >=90%:', k)
cumulative variance: [np.float64(0.42), np.float64(0.76), np.float64(0.947), np.float64(0.966), np.float64(0.977), np.float64(0.986), np.float64(0.994), np.float64(1.0)] components needed for >=90%: 3
Solution. Three components clear 90% (about 95%), the eight correlated columns really carry only three dimensions of information.
ev = pca.explained_variance_ratio_
print('PC1 = %.0f%%, PC2 = %.0f%%, together = %.0f%%' % (ev[0]*100, ev[1]*100, (ev[0]+ev[1])*100))
PC1 = 42%, PC2 = 34%, together = 76%
Solution. PC1 (42%) and PC2 (34%) together keep 76% of the variance, enough for one honest 2D picture.
load = pd.Series(PCA(n_components=2).fit(Xs).components_[0], index=num)
print(load.round(2).sort_values().to_string())
print('largest-magnitude loading on PC1:', load.abs().idxmax())
fiber_g -0.38 protein_g -0.27 sodium_mg -0.11 fat_g 0.35 satfat_g 0.35 sugar_g 0.35 calories 0.43 carbs_g 0.47 largest-magnitude loading on PC1: carbs_g
Solution. Carbohydrate has the largest loading on PC1, confirming PC1 is a 'carb-and-calorie load' axis (high carbs/calories/sugar, low fiber/protein).
p3 = PCA(n_components=3).fit(Xs)
recon = p3.inverse_transform(p3.transform(Xs))
mse = float(np.mean((Xs - recon)**2))
print('reconstruction MSE with 3 components =', round(mse,3))
print('variance retained =', round(p3.explained_variance_ratio_.sum(),3))
reconstruction MSE with 3 components = 0.053 variance retained = 0.947
Solution. Three of eight components rebuild the standardized table with tiny error (~0.05) while retaining 95% of the variance, lossy compression that also denoises.
emb = TSNE(n_components=2, perplexity=30, random_state=0, init='pca').fit_transform(Xs)
print('t-SNE embedding shape:', emb.shape)
print('PCA is linear, reversible, and interpretable (axes have meaning);')
print('t-SNE is nonlinear and for visualization only (axes/among-cluster distances are not meaningful).')
t-SNE embedding shape: (180, 2) PCA is linear, reversible, and interpretable (axes have meaning); t-SNE is nonlinear and for visualization only (axes/among-cluster distances are not meaningful).
Solution. t-SNE preserves local neighborhoods and often separates groups more crisply, but its axes carry no units and you cannot reversibly project new points, so it is a visualization tool, not a compression or preprocessing step like PCA.