ML Hierarchical Clustering

Learn how agglomerative clustering builds a hierarchy of clusters and how to read that hierarchy from a dendrogram.

What Is Hierarchical Clustering?

Hierarchical clustering groups data points into nested clusters rather than a single flat partition. Instead of committing to a fixed number of clusters up front the way K-Means does, it builds an entire tree of merges — from every point in its own tiny cluster all the way up to one giant cluster containing everything — and lets you decide afterward how many groups makes sense by cutting that tree at a chosen height.

The most common approach, agglomerative clustering, works bottom-up: it starts with every sample as its own single-point cluster, then repeatedly finds the two closest clusters and merges them into one. This merge-the-closest-pair step repeats until only one cluster remains, and the full sequence of merges is what gets drawn as a dendrogram.

  • Single linkage merges clusters based on the distance between their closest pair of points — tends to produce long, chained clusters.
  • Complete linkage merges based on the distance between their farthest pair of points — tends to produce compact, evenly sized clusters.
  • Average linkage merges based on the average distance between all pairs across the two clusters — a middle ground between single and complete.
  • Ward linkage merges the pair that increases total within-cluster variance the least — usually the best default for roughly spherical clusters.

Agglomerative Clustering in scikit-learn

Example

from sklearn.datasets import make_blobs
from sklearn.cluster import AgglomerativeClustering

X, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.8, random_state=1)

model = AgglomerativeClustering(n_clusters=3, linkage="ward")
labels = model.fit_predict(X)

print("Cluster labels for first 10 points:", labels[:10])
print("Points per cluster:", [sum(labels == i) for i in range(3)])
Note: linkage='ward' only works with Euclidean distance and is a solid default for compact, roughly equal-sized clusters. Switch to 'average' or 'complete' linkage with a different metric if your clusters have irregular shapes or your features aren't continuous.

Reading a Dendrogram

A dendrogram plots every merge made during agglomerative clustering: the x-axis lists the individual samples (or clusters), and the y-axis shows the distance at which two clusters were joined. Drawing a horizontal line across the dendrogram at a chosen height and counting how many vertical lines it crosses tells you how many clusters you'd get by cutting the hierarchy at that point — a low cut gives many small clusters, a high cut gives fewer, larger ones.

Example

from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt

merge_matrix = linkage(X, method="ward")

dendrogram(merge_matrix, truncate_mode="lastp", p=12)
plt.xlabel("Cluster size")
plt.ylabel("Merge distance")
plt.title("Dendrogram (last 12 merges)")
plt.show()

Example

from scipy.cluster.hierarchy import fcluster

# Cut the tree at a fixed distance instead of a fixed number of clusters
flat_labels = fcluster(merge_matrix, t=6, criterion="distance")
print("Number of clusters at distance 6:", len(set(flat_labels)))

# The scikit-learn equivalent: let distance_threshold pick the cluster count
auto_model = AgglomerativeClustering(n_clusters=None, distance_threshold=6, linkage="ward")
auto_labels = auto_model.fit_predict(X)
print("Number of clusters found:", auto_model.n_clusters_)
Note: Hierarchical clustering scales worse than K-Means — computing all pairwise distances costs O(n^2) memory and time, which makes it impractical much past a few thousand points. Its payoff is the dendrogram itself: a full picture of how clusters nest inside each other, useful even when you never cut it into a final flat clustering.

Exercise: ML Hierarchical Clustering

What happens at each step of agglomerative hierarchical clustering?