Cluster Analysis: k-Means in Code¶
Why this matters¶
The first clustering bridge explained the idea:
This page makes that idea visible with one small k-means run.
The goal is not to build the best customer segmentation model. The goal is to read simple code and output, then connect the output back to the theory:
Mental model¶
k-means asks you to choose how many clusters you want.
Then it tries to place that many center points, called centroids, so each row is close to one of them.
For this tiny example, imagine a cafe with six customers:
k-means will not know customer names, preferences, or true customer types. It only sees the two numeric feature columns.
Core ideas¶
- k-means uses numeric features as input.
- The feature table is usually called
X. - Scaling puts numeric features on comparable ranges before distance is used.
n_clusters=3asks k-means to find three groups.fit_predict(X_scaled)learns the centroids and returns one cluster label per row.- Cluster labels such as
0,1, and2are arbitrary identifiers. cluster_centers_stores the learned centroids in the scaled feature space.- Inertia measures how far points are from their assigned centroids.
- Silhouette score compares how close points are to their own cluster versus nearby other clusters.
- Lower inertia alone does not prove that the clustering is more useful.
One tiny k-means run¶
This invented customer dataset has six rows and two numeric feature columns.
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
customers = pd.DataFrame({
"customer": ["A", "B", "C", "D", "E", "F"],
"visits_per_month": [2, 3, 8, 9, 2, 3],
"avg_spend_eur": [6, 7, 14, 15, 28, 30],
})
X = customers[["visits_per_month", "avg_spend_eur"]]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
kmeans = KMeans(n_clusters=3, n_init=10, random_state=0)
customers["cluster"] = kmeans.fit_predict(X_scaled)
print(customers)
print("\nInertia:", round(kmeans.inertia_, 2))
One possible output:
customer visits_per_month avg_spend_eur cluster
0 A 2 6 0
1 B 3 7 0
2 C 8 14 1
3 D 9 15 1
4 E 2 28 2
5 F 3 30 2
Inertia: 0.22
How to read the output¶
The input rows did not disappear¶
The output still has the same six customers:
k-means did not remove rows. It added a new cluster assignment for each row.
The cluster numbers are names¶
The output says:
Do not read this as:
The numbers are just identifiers. The useful interpretation comes from inspecting the rows:
cluster 0 -> low visits, low spend
cluster 1 -> frequent visits, moderate spend
cluster 2 -> low visits, high spend
Those names are human interpretations. k-means only returned the numbers.
Scaling is part of the practical pattern¶
The code uses:
This matters because k-means uses distance.
In the raw table, spend has values like 6, 14, and 30, while visits has values like 2, 3, and 9. Without scaling, the larger spend numbers can dominate distance just because of their units.
The practical habit is:
Scaling does not make the clusters automatically correct. It makes the distance calculation less accidentally controlled by units.
Inertia tells you compactness¶
The output prints:
In plain language:
lower inertia -> points are closer to their assigned centroids
higher inertia -> points are more spread out inside clusters
This value is useful when comparing runs on the same scaled data.
It is not useful as an absolute score by itself. A small inertia number does not automatically mean the clusters are meaningful customer types.
Reading the centroids¶
The learned centroids live in the scaled feature space. For interpretation, it is often easier to convert them back to the original units:
centers_original = scaler.inverse_transform(kmeans.cluster_centers_)
centers = pd.DataFrame(
centers_original,
columns=["visits_per_month", "avg_spend_eur"],
)
print(centers.round(1))
One possible output:
Read these as the approximate centers of the discovered groups:
cluster 0 center -> about 2.5 visits, 6.5 euros
cluster 1 center -> about 8.5 visits, 14.5 euros
cluster 2 center -> about 2.5 visits, 29.0 euros
The centroid is not necessarily a real customer. It is the average position of the customers assigned to that cluster.
What if k changes?¶
k-means needs the number of clusters before training.
The code above chose:
To see why this choice matters, run the same scaled data with several values of k:
for k in range(1, 5):
model = KMeans(n_clusters=k, n_init=10, random_state=0)
model.fit(X_scaled)
print(k, round(model.inertia_, 2))
One possible output:
The important pattern:
That is why the lowest inertia is not enough. If you keep adding clusters, each point can get closer to a centroid, but the result may stop being useful.
For this tiny table, k=3 gives a simple interpretation:
k=4 has lower inertia, but it starts splitting an already tiny group. That may be too detailed for the practical question.
Justifying a good k¶
In an exam, "justify a good number of clusters" means you should connect a metric to a decision.
Do not only write:
Write the evidence pattern:
I tried several k values. Inertia dropped strongly up to k=3 and only slightly after that, so k=3 is the elbow. The silhouette score is also highest at k=3, which means points are closer to their own cluster than to other clusters. Therefore k=3 is a reasonable choice.
Inertia evidence¶
Use inertia to look for an elbow:
The big improvement happens by k=3.
After that, inertia still improves, but only a little:
That supports k=3 because the fourth cluster adds detail without changing the structure much.
Silhouette evidence¶
Silhouette score asks a different question:
Useful reading rule:
near +1 -> well separated
near 0 -> between clusters
near -1 -> probably assigned to the wrong cluster
In code:
from sklearn.metrics import silhouette_score
for k in range(2, 5):
model = KMeans(n_clusters=k, n_init=10, random_state=0)
labels = model.fit_predict(X_scaled)
score = silhouette_score(X_scaled, labels)
print(k, round(model.inertia_, 2), round(score, 2))
One possible output:
This supports k=3 because:
inertia has its elbow at k=3
silhouette is highest at k=3
the three clusters have a simple interpretation
A safe exam answer template¶
Use this structure when the prompt says to justify the number of clusters:
I compared k = ... using inertia and/or silhouette score.
Inertia suggests k = ... because ...
Silhouette suggests k = ... because ...
I choose k = ... because the metric evidence and the cluster interpretation agree.
If the metrics disagree, say that directly:
The metrics do not give a single clear answer. I would treat k=... and k=... as candidates, inspect the cluster plots or centroids, and choose the simpler value unless the extra cluster has a meaningful interpretation.
How this connects to the full lesson¶
The full Cluster Analysis lesson uses larger examples:
The same reading habit applies:
features going in -> what similarity means
scaling or representation -> what distance sees
cluster labels coming out -> arbitrary group IDs
centroids or other output -> clues for interpretation
evaluation pattern -> evidence, not proof
Common traps¶
Do not treat cluster numbers as real categories
Cluster 0 is not automatically a real customer type. Inspect the rows and centroids before naming a group.
Do not skip scaling by habit
For distance-based clustering, raw units can control the result accidentally.
Do not choose k from the lowest inertia alone
Inertia usually decreases when k increases. Use the pattern plus the practical meaning of the groups.
Do not quote silhouette without interpretation
A higher silhouette score is evidence for better separation, but you still need to explain what the chosen clusters mean.
Do not overinterpret tiny examples
This six-row table is a teaching example. Real clustering needs more data, better feature choices, and careful validation.
Check yourself¶
What does fit_predict(X_scaled) return?
One cluster label for each input row.
Why does the code scale the customer features before k-means?
k-means uses distance, and scaling prevents one feature from dominating just because its raw numbers are larger.
What does a centroid represent?
The center position of a cluster. It is usually an average point, not necessarily a real observation.
Why is the lowest inertia not automatically the best clustering?
Adding more clusters usually lowers inertia, even when the extra detail is not useful.
What does a high silhouette score suggest?
Points are generally closer to their own cluster than to nearby other clusters, so the clusters are better separated.
How would you justify choosing k=3 in the tiny customer example?
Inertia drops strongly up to k=3 and only slightly after that, silhouette is highest at k=3, and the three clusters have a simple interpretation.
Who gives semantic names like 'low visits, high spend' to the clusters?
A human analyst does. k-means only returns arbitrary cluster labels.
Next¶
Next: Cluster Analysis
The full lesson expands from this small k-means example to synthetic data, Iris clustering, text clustering, hierarchical clustering, and DBSCAN.