Python · Course navigation

k-means clustering

Group unlabelled points by alternating between assigning points to centroids and moving the centroids.

On this page
Before you start

Lists, tuples, and averages.

  • Group unlabelled points by alternating between assigning points to centroids and moving the centroids.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

Clustering is unsupervised learning: the data have no labels, and the goal is to find groups of similar points. k-means, in the form known as Lloyd's algorithm, is the standard method. You choose the number of clusters k and some initial centroids, and then repeat two steps. In the assignment step, every point joins the cluster of its nearest centroid. In the update step, each centroid moves to the mean of the points assigned to it. The algorithm stops when the centroids no longer change.

Each step can only lower, or keep the same, the total squared distance of points to their centroids, so the algorithm always converges. It converges to a local optimum, though, and the result depends on the starting centroids. Practical code runs several starts and keeps the best, and k-means++ chooses initial centroids that are spread out. The number of clusters k must be chosen by you, for example with the elbow method or silhouette scores.

The example clusters seven 2-D points with k = 2, starting from the first and fourth points. It converges in 3 iterations to centroids (1.25, 1.5) and (3.9, 5.1), with 2 and 5 points. k-means works best for compact, roughly round clusters of similar size, and it needs numeric features that are on comparable scales.

Code example

Python
points = [(1.0, 1.0), (1.5, 2.0), (3.0, 4.0), (5.0, 7.0), (3.5, 5.0), (4.5, 5.0), (3.5, 4.5)]


def distance2(a, b):
    return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2


def kmeans(points, centroids):
    iterations = 0
    while True:
        iterations += 1
        clusters = [[] for _ in centroids]
        for point in points:
            nearest = min(range(len(centroids)), key=lambda i: distance2(point, centroids[i]))
            clusters[nearest].append(point)
        updated = [(sum(p[0] for p in c) / len(c), sum(p[1] for p in c) / len(c)) for c in clusters]
        if updated == centroids:
            return centroids, clusters, iterations
        centroids = updated


centroids, clusters, iterations = kmeans(points, [points[0], points[3]])
print([tuple(round(value, 2) for value in c) for c in centroids])
print([len(c) for c in clusters], iterations)
Output
[(1.25, 1.5), (3.9, 5.1)]
[2, 5] 3

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1points = [(1.0, 1.0), (1.5, 2.0), (3.0, 4.0), (5.0, 7.0), (3.5, 5.0), (4.5, 5.0), (3.5, 4.5)]
2
3
4def distance2(a, b):
5 return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2
6
7
8def kmeans(points, centroids):
9 iterations = 0
10 while True:
11 iterations += 1
12 clusters = [[] for _ in centroids]
13 for point in points:
14 nearest = min(range(len(centroids)), key=lambda i: distance2(point, centroids[i]))
15 clusters[nearest].append(point)
16 updated = [(sum(p[0] for p in c) / len(c), sum(p[1] for p in c) / len(c)) for c in clusters]
17 if updated == centroids:
18 return centroids, clusters, iterations
19 centroids = updated
20
21
22centroids, clusters, iterations = kmeans(points, [points[0], points[3]])
23print([tuple(round(value, 2) for value in c) for c in centroids])
24print([len(c) for c in clusters], iterations)

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Assignment step: each point joins the nearest centroid.—
—
Update step: each centroid moves to the mean of its cluster.centroids move toward the data
—
When the centroids stop moving, the assignments are stable and the algorithm has converged.centroids move toward the data
—
Two clusters: a small one near the origin and a larger one near (3.9, 5.1).centroids move toward the data
[(1.25, 1.5), (3.9, 5.1)]
Two points in one cluster and five in the other. It converged after 3 iterations.centroids move toward the data
[(1.25, 1.5), (3.9, 5.1)]
[2, 5] 3

Behind the scenes

How Python processes this code

Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.

Cost and common mistakes

Each iteration costs O(n·k·d) for n points, k clusters, and d dimensions. The number of iterations is usually small, but it has no tight general bound.

  • k-means can converge to a poor local optimum. Run it from several initial centroids and keep the best clustering.
  • A cluster can become empty, which would divide by zero when computing its mean. Real implementations re-seed empty clusters.
  • k is not learned automatically. The data do not tell you the right number of clusters without extra analysis.

Try it yourself

Which points end up in the first cluster?

Reveal answer and reasoning

(1.0, 1.0) and (1.5, 2.0). They are far closer to the centroid (1.25, 1.5) than to (3.9, 5.1).

Further reading