Chapter 137 – Hierarchical Clustering Part 2: Dendrograms, PCA & Evaluation
1. Pipeline Overview
End-to-end flow on the Wine dataset (178 × 13): scale features → Ward linkage dendrogram → AgglomerativeClustering (k=3) → PCA to 2D → check cluster counts. Labels are unsupervised IDs only.
2. Why StandardScaler Is Required
Features span very different ranges (e.g. Proline > 1000 vs Malic Acid ~1). Unscaled Euclidean distances would be dominated by large-magnitude columns. StandardScaler sets mean 0 and std 1 so every feature contributes fairly.
3. Dendrogram (SciPy)
from scipy.cluster.hierarchy import linkage, dendrogram
Z = linkage(X_scaled, method="ward")
plt.figure(figsize=(10, 6))
dendrogram(Z)
plt.ylabel("Merge distance")
plt.show()
Y-axis runs ~0 (leaves) to ~35 (root). Top branches support a natural cut at k = 3.
4. Fit Agglomerative Model
hc = AgglomerativeClustering(n_clusters=3, linkage="ward")
labels = hc.fit_predict(X_scaled)
Outputs integer IDs 0 / 1 / 2 for each of the 178 samples.
5. PCA Visualization
pca = PCA(n_components=2)
X_2D = pca.fit_transform(X_scaled)
plt.scatter(X_2D[:, 0], X_2D[:, 1], c=labels, cmap="Set1", edgecolors="k")
plt.xlabel("PC1"); plt.ylabel("PC2")
plt.show()
Cores of the three groups separate clearly; a few boundary points mix, which is normal for real data.
6. Cluster Balance
- Cluster 2: 64
- Cluster 0: 58
- Cluster 1: 56
Max gap is only 8 samples → balanced partition, not degenerate tiny clusters.
7. Full Script Skeleton
wine = load_wine()
X = wine.data
X_scaled = StandardScaler().fit_transform(X)
Z = linkage(X_scaled, method="ward")
# dendrogram(Z) ...
hc = AgglomerativeClustering(n_clusters=3, linkage="ward")
labels = hc.fit_predict(X_scaled)
X_2D = PCA(n_components=2).fit_transform(X_scaled)
# scatter + value_counts ...