Skip to content
learn
  1. Learn
  2. Machine Learning
  3. Unsupervised Learning

Machine Learning ML-07.01

k-means clustering

Group unlabelled points into k clusters by alternating assign and update steps, choose k with the elbow and silhouette, and start well with k-means++.

Beginner 14 min Last reviewed

The big idea

You run a shop and have spending data for 10,000 customers, but no labels. Are there natural groups, such as bargain hunters, weekend splurgers and loyal regulars? ClusteringGrouping data points so that points in the same group are more similar to each other than to points in other groups, without using labels. finds such groups without being told what they are.

k-meansA clustering algorithm that partitions data into k groups, each represented by its mean (centroid), by alternating assignment of points to the nearest centroid and recomputation of centroids. is the simplest and most used clustering algorithm. Picture k pizza delivery depots in a new city. Each house orders from its nearest depot. Then each depot moves to the centre of the houses it serves. Some houses now have a different nearest depot, so they switch. Repeat until no house switches. The depots end up in the middle of natural neighbourhoods.

How it works

The k-means loop on 12 points Twelve points form two groups, one around (2, 2) and one around (6, 4). Step 1: two centroid rings start at (3.0, 4.6) and (4.4, 1.0). Step 2: points are coloured by nearest centroid, mixed across groups. Step 3: centroids move to (4.19, 3.47) and (3.72, 2.22), shown by glowing arrows. Step 4: after reassigning, centroids move to (6.05, 3.93) and (1.93, 1.97), the centres of the two groups. 01234567012345feature 1feature 2 Figure 1. Assign, update, repeat. Each step can only lower the total squared distance from points to their centroids, so the loop always stops, here after two rounds.
  1. Start: place k = 2 centroids Two centroids (rings) start in poor positions, between the two natural groups. The 12 points have no labels.
  2. Assign: each point joins its nearest centroid Points take the colour of the closest ring. Because the centroids started badly, both colours are mixed across both groups.
  3. Update: each centroid moves to the mean of its points The violet centroid moves to (4.19, 3.47) and the cyan one to (3.72, 2.22). Both moved towards the data they own.
  4. Repeat until nothing changes Reassigning splits the points cleanly by group; the next update moves the centroids to (6.05, 3.93) and (1.93, 1.97). Another assign step changes nothing, so k-means has converged.

Step 1: Choose k and place k starting centroids

A centroidThe centre of a cluster: the mean of the points assigned to it. is a cluster's centre. You choose k, the number of clusters, in advance, and the algorithm needs k starting positions.

Step 2: Assign every point to its nearest centroid

Compute the (usually Euclidean) distance from each point to each centroid and give the point the label of the closest one. This carves space into regions: every location belongs to the nearest centroid.

Step 3: Move each centroid to the mean of its points

Average the coordinates of the points in each cluster. That mean is the position that minimises the cluster's sum of squared distances.

Step 4: Repeat until assignments stop changing

The quantity k-means minimises is inertiaThe sum of squared distances from each point to its assigned centroid; the objective k-means minimises. Also called within-cluster sum of squares.:

where mu_c(i) is the centroid of point i's cluster. The assign step lowers it (each point picks its closest centroid) and the update step lowers it (the mean is the best centre), so it never goes up and the loop must stop. But it stops at a local minimumA solution that cannot be improved by small changes but is worse than the best possible (global) solution., which depends on the starting centroids.

Step 5: Start well with k-means++

Random starting positions can put two centroids in one true cluster and none in another. k-means++An initialization for k-means that picks the first centroid at random from the data and each next one with probability proportional to its squared distance from the nearest centroid already chosen. spreads the starts out: pick one data point at random, then pick each next start with probability proportional to its squared distance from the nearest start so far. Far-away points are likely picks. In the code below, single runs with random starts got stuck in a worse solution 43 percent of the time; with k-means++ none did. Libraries also run several starts (n_init) and keep the lowest inertia.

Step 6: Choose k

Inertia always falls as k grows (with k = n, every point is its own cluster and inertia is 0), so you cannot just minimise it.

  • Elbow method: plot inertia against k and look for the bend where adding clusters stops paying. In the code, inertia drops from 5,018 at k = 3 to 913 at k = 4, then only to 826 at k = 5: the elbow is at 4.
  • Silhouette score: for each point, compare a = mean distance to its own cluster with b = mean distance to the nearest other cluster: s = (b - a) / max(a, b). It ranges from -1 to 1 and higher is better. The average silhouette peaks at 0.830 for k = 4 in the code.
  • Usefulness: often k is a business decision, such as five customer segments the marketing team can act on.

Try it

This interactive needs JavaScript. The diagrams and text above cover the same idea.

Try this:

  1. With k = 4 and random initialization, press Step repeatedly and watch the centroids glide. Then press New start a few times. Do you find a start where two centroids share one blob? What is the final inertia then?
  2. Switch to k-means++ and repeat New start plus Run to end. How often does it end badly now?
  3. Set k to 3, then 6. With 6, how does k-means split the four blobs, and how much does inertia drop?

Going deeper

Code: the loop from scratch, choosing k, initialization and a failure

kmeans.pyruns with python3 + numpy + scikit-learn
import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs, make_moons
from sklearn.metrics import adjusted_rand_score, silhouette_score

X, truth = make_blobs(n_samples=600, centers=4, cluster_std=0.9, random_state=7)

# k-means from scratch: assign, update, repeat until nothing changes
def kmeans(X, k, seed):
    rng = np.random.default_rng(seed)
    C = X[rng.choice(len(X), k, replace=False)]
    for it in range(1, 100):
        labels = np.argmin(((X[:, None] - C[None]) ** 2).sum(-1), axis=1)
        newC = np.array([X[labels == j].mean(0) if np.any(labels == j) else C[j] for j in range(k)])
        if np.allclose(newC, C):
            break
        C = newC
    return labels, ((X - C[labels]) ** 2).sum(), it

labels, inertia, iters = kmeans(X, 4, seed=0)
print(f"from scratch: converged in {iters} iterations, inertia {inertia:.1f}, "
      f"agreement with true groups (ARI) {adjusted_rand_score(truth, labels):.3f}")

# Choosing k: inertia always falls; silhouette peaks at a good k
print("\nk   inertia   silhouette")
for k in range(2, 8):
    km = KMeans(k, n_init=10, random_state=0).fit(X)
    print(f"{k}   {km.inertia_:7.1f}   {silhouette_score(X, km.labels_):.3f}")

# Initialization: one run each from 100 different seeds
for init in ["random", "k-means++"]:
    runs = [KMeans(4, init=init, n_init=1, random_state=s).fit(X).inertia_ for s in range(100)]
    bad = np.mean(np.array(runs) > min(runs) * 1.05)
    print(f"\n{init:<9}: best inertia {min(runs):.1f}, worst {max(runs):.1f}, "
          f"runs stuck in a worse solution {bad:.0%}", end="")
print()

# A limitation: non-round clusters
Xm, ym = make_moons(400, noise=0.06, random_state=0)
print(f"\ntwo moons: k-means ARI {adjusted_rand_score(ym, KMeans(2, n_init=10, random_state=0).fit_predict(Xm)):.3f}")

Output

from scratch: converged in 3 iterations, inertia 913.2, agreement with true groups (ARI) 1.000

k   inertia   silhouette
2   18162.5   0.594
3    5018.2   0.773
4     913.2   0.830
5     826.1   0.696
6     745.7   0.571
7     663.4   0.467

random   : best inertia 913.2, worst 13985.8, runs stuck in a worse solution 43%
k-means++: best inertia 913.2, worst 913.2, runs stuck in a worse solution 0%

two moons: k-means ARI 0.274

The from-scratch version is about ten lines and recovers the four true groups exactly (adjusted Rand index 1.000, a score of agreement between two labelings where 1 means identical). The last line shows the classic failure: on two interleaved moons k-means scores only 0.274.

Assumptions and limits

  • Round, similar-sized clusters. Using distance to the mean implies clusters are roughly spherical and of similar spread. Elongated, nested or moon-shaped clusters are cut in the wrong places; use DBSCAN or Gaussian mixtures ( Hierarchical clustering and DBSCAN , Gaussian mixture models and EM ).
  • Scale matters. A feature measured in dollars swamps one measured in years. Standardize features first ( Feature engineering ).
  • Every point is assigned. Outliers get a cluster anyway and drag its centroid. k-medians or k-medoids are more robust.
  • Cost is about n x k x d per iteration, so k-means scales to millions of points; mini-batch k-means updates centroids from small random batches for even larger data.

Trade-offs

OptionWins whenLoses when
k-means Many points, roughly round clusters, k known or easy to pick Odd shapes, very different cluster sizes, outliers
Mini-batch k-means Millions of points or streaming data Slightly worse inertia than full k-means
DBSCAN Arbitrary shapes and noise points, k unknown Clusters with very different densities; needs eps tuning
Gaussian mixture Elliptical, overlapping clusters with soft membership Slower; can collapse without regularization

Common mistakes

  • Running k-means on unscaled features, so one large-valued feature decides everything.
  • Using a single random initialization and trusting the result.
  • Picking k by minimising inertia, which always favours more clusters.
  • Applying k-means to clearly non-spherical clusters and trusting the boundaries.
  • Interpreting clusters as real-world categories without checking them against domain knowledge.

In the wild

k-means turns up in customer segmentation, grouping documents or products by their embeddings, compressing images by reducing colours to k palette entries, and as a building block elsewhere: vector databases often use it to partition embeddings into buckets for fast approximate search (the inverted-file index behind many retrieval systems), and speech and vision pipelines use it to build codebooks of typical feature vectors.

Check yourself

  1. What are the two alternating steps of k-means?

    Show answer

    B. Assign points to the nearest centroid; move centroids to the mean of their points Assign then update, repeated until assignments stop changing. Each step lowers inertia.

  2. Inertia keeps falling as k goes from 2 to 10. What does that tell you?

    Show answer

    B. Nothing on its own: inertia always falls with more clusters, so look for an elbow or use silhouette More centroids always reduce distances. You need a criterion that penalises extra clusters or a visible elbow.

  3. Two runs of k-means on the same data give different clusters. Why?

    Show answer

    B. Different starting centroids led to different local minima k-means converges to a local minimum that depends on initialization. Use k-means++ and several restarts, keeping the lowest inertia.

  4. Customers are described by annual income (dollars) and age (years). What should you do before k-means?

    Show answer

    B. Standardize both features Income differences in the thousands would dominate distances; standardizing puts both features on a comparable scale.

  5. Which data is k-means worst at clustering?

    Show answer

    B. Two interleaved crescent shapes k-means draws straight boundaries halfway between centroids, so it cuts crescents in the wrong place. Density-based methods handle such shapes.

Recap

  • k-means finds k clusters by alternating: assign each point to its nearest centroid, then move centroids to their points' means.
  • It minimises inertia, the sum of squared distances to centroids, and always converges, but only to a local minimum.
  • k-means++ initialization and several restarts avoid most bad solutions.
  • Choose k with an elbow plot, the silhouette score or the needs of the application, never by inertia alone.
  • It assumes round, similar-sized clusters on comparable feature scales; scale features first.

Get new lessons by email

Get new posts on backend and AI internals. No spam, unsubscribe anytime.

Your email is only used to send new posts. Privacy