Skip to content

Clustering

Clustering is the flagship unsupervised task: group observations so that points in the same group are similar and points in different groups are dissimilar β€” with no labels to guide or evaluate the grouping. Typical uses: customer segmentation, anomaly detection, image compression, organizing documents (the road to topic modeling).

Because there is no ground truth, every clustering result is a hypothesis about structure, and the algorithm's assumptions determine what kind of structure it can find.

k-means

The classical algorithm (Lloyd, 1957/1982). Choose \(k\); find centroids \(\mu_1, \dots, \mu_k\) minimizing the within-cluster sum of squares (inertia):

\[ \min_{\mu_1,\dots,\mu_k} \; \sum_{i=1}^{n} \min_{j} \; \lVert x_i - \mu_j \rVert^2 \]

Lloyd's algorithm alternates two steps until assignments stop changing:

  1. Assign: each point joins its nearest centroid;
  2. Update: each centroid moves to the mean of its assigned points.

Worked by hand

Six points, two obvious groups, and a deliberately bad start β€” both centroids on the left:

point \(x\) \(y\) point \(x\) \(y\)
A 1 1 D 8 8
B 1 2 E 9 8
C 2 1 F 8 9

Start with \(\mu_1 = (1,1)\), \(\mu_2 = (2,1)\) and turn the crank:

iteration assignment inertia new centroids
1 A,B β†’ 1 Β· C,D,E,F β†’ 2 284.00 (1, 1.5) and (6.75, 6.5)
2 A,B,C β†’ 1 Β· D,E,F β†’ 2 20.69 (1.33, 1.33) and (8.33, 8.33)
3 A,B,C β†’ 1 Β· D,E,F β†’ 2 2.67 unchanged β†’ converged

Three things to take from that table. The inertia falls monotonically β€” 284, 20.7, 2.67 β€” and this is guaranteed: both steps can only decrease it, which is why the algorithm always terminates. The convergence test is on the assignments, not the inertia. And a start as bad as this one still recovered, in three iterations β€” but nothing guaranteed that, which is what n_init is for.

Step through the same two operations here. The arrows show where step 2 is about to move each cross:

Where the inertia actually comes from

Inertia is the sum of squared distances from each point to its own centroid β€” the total length of those spokes, squared. Try to beat the algorithm by hand: place the centroids yourself and see how close you get to the minimum it finds.

from sklearn.cluster import KMeans

km = KMeans(n_clusters=3, n_init=10, random_state=0)   # n_init: restarts
labels = km.fit_predict(X_scaled)
km.inertia_          # within-cluster sum of squares
km.cluster_centers_

Properties and pitfalls:

  • You must choose \(k\) in advance;
  • Converges to a local optimum β€” hence multiple restarts (n_init);
  • Assumes clusters are convex, roughly spherical, similar in size (it partitions space into Voronoi cells around centroids);
  • Distance-based β†’ scale your features (Preprocessing);
  • Every point is assigned to a cluster β€” k-means has no concept of noise or outliers.

Choosing k

  • Elbow method: plot inertia vs \(k\); inertia always decreases, so look for the "elbow" where gains flatten. Heuristic and often ambiguous.
  • Silhouette score: for each point, with \(a\) = mean distance to its own cluster and \(b\) = mean distance to the nearest other cluster,
\[ s = \frac{b - a}{\max(a, b)} \in [-1, 1]. \]

Average \(s\) near 1 β†’ compact, well-separated clusters; near 0 β†’ overlapping; negative β†’ likely misassigned. Choose the \(k\) that maximizes the mean silhouette.

For point A = (1,1) in the worked example: its own group is {B, C}, at distances 1 and 1, so \(a = 1\). The other group is {D, E, F}, at mean distance \(b = 10.39\). That gives

\[ s_A = \frac{10.39 - 1}{10.39} = 0.90 \]

which is what "comfortably inside its own group" looks like as a number. Pick any point in the simulator and watch the two averages it is built from:

Now sweep \(k\) and put the two criteria side by side. On clean blobs they agree; on other shapes they do not, and the elbow is the one that gives way:

from sklearn.metrics import silhouette_score
silhouette_score(X_scaled, labels)

Hierarchical clustering

Agglomerative clustering builds a dendrogram: start with every point as its own cluster, repeatedly merge the two closest clusters until one remains, then cut the tree at the desired level. No need to fix \(k\) beforehand β€” you choose it by cutting.

The definition of "closest clusters" is the linkage:

Linkage Distance between clusters Behavior
single closest pair of points finds elongated chains, sensitive to noise
complete farthest pair compact clusters
average mean pairwise distance compromise
Ward merge minimizing inertia increase k-means-like, most common default
from sklearn.cluster import AgglomerativeClustering
labels = AgglomerativeClustering(n_clusters=3, linkage='ward').fit_predict(X_scaled)

Cost is \(O(n^2)\) memory/time β€” fine for thousands of points, prohibitive for millions.

DBSCAN and HDBSCAN: density-based clustering

DBSCAN (Ester et al., 1996) defines clusters as dense regions separated by sparse regions, using two parameters: eps (neighborhood radius) and min_samples (points required to call a neighborhood dense).

  • Core point: has β‰₯ min_samples neighbors within eps;
  • Border point: within eps of a core point, but not core itself;
  • Noise: neither β€” DBSCAN labels outliers (label βˆ’1) instead of forcing them into clusters.

Strengths: finds arbitrarily shaped clusters, no \(k\) to choose, built-in noise detection. Weaknesses: a single global eps fails when clusters have different densities; eps is not intuitive to tune.

HDBSCAN (Campello, Moulavi & Sander, 2013) removes the global eps: it builds a hierarchy over all density levels and extracts the most stable clusters, handling variable-density data with essentially one intuitive parameter (min_cluster_size). This robustness is why BERTopic uses HDBSCAN to cluster document embeddings β€” documents that fit no topic simply become noise instead of polluting topics.

from sklearn.cluster import HDBSCAN   # scikit-learn β‰₯ 1.3
labels = HDBSCAN(min_cluster_size=10).fit_predict(X_scaled)

Assumptions matter: k-means vs DBSCAN

Before the comparison, see the failure directly. k-means imposes convex, similarly-sized, spherical groups on whatever you give it. Cycle through the five shapes below β€” the moons get cut across, the concentric rings are impossible for any set of centroids, the elongated groups get split along the wrong diagonal, and on pure noise it still returns tidy partitions:

The lesson is not that k-means is bad. It is that the metric does not always warn you: on several of those shapes the silhouette stays respectable while the partition is meaningless. A metric can only score the partition it is given against the assumptions it shares with the algorithm.

k-means vs DBSCAN on blobs and two moons

On convex blobs both succeed. On the two moons, k-means fails by construction β€” it can only draw Voronoi boundaries between centroids β€” while DBSCAN follows the density and recovers the crescents, marking stray points as noise.

Choosing an algorithm

Situation Reach for
Convex, similar-size clusters; large n; need speed k-means (or MiniBatchKMeans)
Want a dendrogram / taxonomy; small n hierarchical (Ward)
Arbitrary shapes, noise/outliers expected DBSCAN
Arbitrary shapes with varying density (e.g. embeddings) HDBSCAN

Validate like a skeptic

With no labels, always inspect clusters: silhouette scores, 2D projections (PCA/UMAP), and β€” most importantly β€” whether the clusters mean something in the domain. A clustering nobody can name is rarely useful.

Class materials

Class notebook (in Portuguese)

Hands-on notebook used in class β€” Aula 07 β€” Clustering: open in Colab

Video

Algoritmo k-means (k-mΓ©dias)

Algoritmo k-means (k-mΓ©dias) β€” in Portuguese


References

  • Lloyd, S. P. "Least Squares Quantization in PCM." IEEE Trans. Inf. Theory 28 (1982). DOI
  • Rousseeuw, P. J. "Silhouettes: A Graphical Aid to the Interpretation and Validation of Cluster Analysis." J. Comp. Appl. Math. 20 (1987). DOI
  • Campello, R.; Moulavi, D.; Sander, J. "Density-Based Clustering Based on Hierarchical Density Estimates." PAKDD (2013). DOI

The full course bibliography is on the references page.


Quiz