Clustering
k-means always returns clusters — which is not evidence that clusters exist
KMeansinertiasilhouette_scoreDBSCANGaussianMixturecluster profilingWatch it happen
Play it through, or step back and forth yourself.
Everything so far had a y. Unsupervised learning has none — you are looking for structure rather than predicting a known answer, which also means there's no obvious way to tell whether you found any.
The idea
Everything so far had a y. Unsupervised learning has none — you're looking for structure rather than predicting a known answer. Which also means there's no test set, no accuracy, and no obvious way to be wrong. That freedom is the difficulty: unsupervised results are much easier to over-believe.
How k-means works
- Place k centres (
k-means++picks spread-out starting points). - Assign each row to its nearest centre.
- Move each centre to the mean of its assigned rows.
- Repeat until nothing moves.
from sklearn.cluster import KMeans
X_scaled = preprocessing.fit_transform(X)
km = KMeans(n_clusters=3, n_init=10, random_state=0).fit(X_scaled)
km.labels_ # which cluster each row landed in
km.cluster_centers_ # where the centres ended up
km.inertia_ # total squared distance to centresIt converges to a local optimum that depends on where the centres started, which is why n_init=10 runs it ten times and keeps the best.
What it assumes
Quite a lot. "Nearest centre" carves space into straight-edged cells, so k-means can only produce convex, roughly round groups of similar size and density. Give it elongated clusters and it cuts them in half; give it nested rings and it cannot represent them at all.
DBSCANfinds arbitrary shapes and can label points as noise, which k-means never does.GaussianMixtureallows elongated clusters and gives soft memberships — probabilities rather than hard assignments.AgglomerativeClusteringbuilds a hierarchy you can cut at any level.
And since it's a distance method, scaling is mandatory — lesson 10 in full force. Unscaled, hour (range 15) would dominate distance_km (range 6) for no reason but units, and you'd be clustering by time of day without meaning to.
Choosing k
k inertia silhouette
2 6210.1 0.1330
3 5322.2 0.1743 ← peak
4 4941.9 0.1509
5 4677.7 0.1298
6 4461.9 0.1226Inertia falls forever — at k = n every point is its own centre and inertia is zero — so you look for an "elbow", and elbows are in the eye of the beholder.
Silhouette is better: for each row it compares the distance to its own cluster against the nearest other cluster, giving −1 to 1. It has a genuine maximum, so it can actually choose.
And ours is weak
The peak is 0.174 at k=3. As a rough guide: above 0.5 means well-separated clusters, above 0.25 is worth a look, and 0.17 means the structure is faint at best.
So the honest reading is: k=3 is the best available answer, and there may not be much of a question. Saying that is more useful than presenting three clusters as a finding.
It always returns clusters
This is the thing to hold onto. Ask k-means for three clusters and you get three — on any data whatsoever, including pure noise. The algorithm has no way to tell you "there is no structure here".
Getting clusters is not evidence that clusters exist. That's what the silhouette score is for, and why the next step matters.
Validate against something you didn't cluster on
cluster rows late rate avg minutes
0 368 31.8% 27.6
1 380 8.7% 22.5
2 152 32.9% 28.0Cluster 1 is 8.7% late against 31.8% and 32.9%. It has separated the easy deliveries from the hard ones without ever seeing the target.
That's the right way to validate clustering: profile the groups against a column you didn't cluster on. If they differ on something meaningful, the structure is probably real even when the silhouette is unimpressive.
What clustering is actually for
- Describing your data — "we have three kinds of delivery" is a sentence people can act on.
- Segmentation — customer groups, routes, regions, for humans to make decisions about.
- Exploration before you have labels.
- Anomaly detection — rows far from every centre.
Not for prediction. You have a target and a supervised model that reaches 0.879 AUC; the clustering reaches nothing, because that was never the job.
See it run
The lesson's code, ready to run and to fiddle with.
Putting the kettle on…
Starting up…
Worked example
not gradedAlready written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.
tryclustering on pure noise — np.random.default_rng(0).normal(size=Xs.shape) — and checking the silhouette.
Your turn
3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Sweep k over [2, 3, 4, 5] and return the silhouette scores, rounded to 4 places. The peak tells you which k to pick.
Validate the clusters. Fit k=3, then return the late rate per cluster as a list rounded to 3 places, in cluster order.
Prove that k-means always returns clusters. Fit k=3 on pure random noise of the same shape and return [n_clusters_found, silhouette], score rounded to 3 places.
