Python Tutorial
Unsupervised Learning: Clustering with k-Means
Sometimes there is no label to predict — you want to discover structure. Clustering groups similar examples together: customer segments for marketing, similar documents, store locations with similar demand, unusual behaviour that fits no group.
k-means is the most widely used clustering algorithm. You choose the number of clusters k; it places k centres, assigns every point to its nearest centre, moves each centre to the mean of its points, and repeats until nothing changes. This lesson covers k-means, choosing k with the elbow method and silhouette score, and a customer segmentation example.
Using k-means Well
k-means uses distances, so scale the features first — otherwise income in rupees drowns out age in years. Set random_state for reproducible results (n_init reruns with different starting centres and keeps the best). k-means finds round, similar-sized clusters; for irregular shapes or noise, DBSCAN or hierarchical clustering (AgglomerativeClustering) may fit better.
Choosing k
The elbow method plots inertia_ (total squared distance of points to their centres) for several k; it always decreases, but the "elbow" where improvement slows suggests a good k. The silhouette score (−1 to 1) measures how well each point fits its own cluster compared with the nearest other cluster; higher is better. Finally, clusters must make sense to people — describe each one with groupby means and give it a name.
Examples
k-means on simple 2-D data, with the elbow and silhouette scores
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
X, true_groups = make_blobs(n_samples=300, centers=4, cluster_std=0.8, random_state=7)
print(" k inertia silhouette")
for k in range(2, 7):
km = KMeans(n_clusters=k, n_init=10, random_state=0).fit(X)
print(f"{k:>2} {km.inertia_:>8.1f} {silhouette_score(X, km.labels_):.3f}")
best = KMeans(n_clusters=4, n_init=10, random_state=0).fit(X)
print("cluster sizes:", sorted([int((best.labels_ == c).sum()) for c in range(4)]))
print("centres:", best.cluster_centers_.round(1).tolist())
print("new points go to clusters:", best.predict([[0, 0], [-8, 8]]))
k inertia silhouette
2 8867.6 0.605
3 2313.8 0.786
4 365.6 0.846
5 327.1 0.735
6 293.0 0.595
cluster sizes: [75, 75, 75, 75]
centres: [[9.5, 0.6], [-1.3, 4.5], [0.1, -8.7], [-8.4, 5.5]]
new points go to clusters: [1 3]
Customer segmentation with scaled features
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
rng = np.random.default_rng(11)
def group(n, orders, spend, days):
return pd.DataFrame({"orders_per_year": rng.normal(orders, 2, n).clip(1).round(),
"avg_order_value": rng.normal(spend, 150, n).clip(100).round(),
"days_since_last_order": rng.normal(days, 10, n).clip(1).round()})
customers = pd.concat([group(120, 4, 600, 120), # occasional shoppers
group(80, 24, 900, 10), # loyal regulars
group(40, 6, 3500, 30)], # big spenders
ignore_index=True)
model = make_pipeline(StandardScaler(), KMeans(n_clusters=3, n_init=10, random_state=0))
customers["segment"] = model.fit_predict(customers)
profile = customers.groupby("segment").agg(
customers=("orders_per_year", "size"),
orders=("orders_per_year", "mean"),
order_value=("avg_order_value", "mean"),
recency_days=("days_since_last_order", "mean"),
).round(0).sort_values("order_value")
print(profile)
names = dict(zip(profile.index, ["Occasional", "Loyal regulars", "Big spenders"]))
print(customers["segment"].map(names).value_counts().to_dict())
customers orders order_value recency_days
segment
1 120 4.0 596.0 121.0
0 80 24.0 900.0 10.0
2 40 6.0 3475.0 33.0
{'Occasional': 120, 'Loyal regulars': 80, 'Big spenders': 40}
Common Mistakes
- Clustering unscaled features, so one large-valued column dominates.
- Picking k without checking the elbow, silhouette or whether the clusters make business sense.
- Treating cluster numbers as meaningful labels — they are arbitrary and can change between runs.
- Using k-means for elongated or irregular clusters where DBSCAN fits better.
- Forgetting that k-means is sensitive to outliers.
Key Points to Remember
- Clustering finds groups in unlabelled data; k-means is the standard starting point.
- Scale features and set random_state/n_init.
- Choose k with the elbow (inertia) and silhouette score, then sanity-check the groups.
- Profile clusters with groupby to turn them into named, actionable segments.
- DBSCAN and hierarchical clustering handle other cluster shapes.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local project environment for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.