Python Tutorial
K-means Clustering
Partition data into k groups by repeatedly assigning points to the nearest center and recomputing those centers.
The Algorithm
K-means is an unsupervised method that splits data into k clusters. It works in a loop:
- Place k initial centroids.
- Assign each point to its nearest centroid.
- Move each centroid to the mean of its assigned points.
- Repeat steps 2–3 until assignments stop changing.
You must choose k in advance. It is fast and scales well, but assumes roughly spherical, similarly-sized clusters.
Running K-means
import numpy as np
from sklearn.cluster import KMeans
x = [4, 5, 10, 4, 3, 11, 14, 6, 10, 12]
y = [21, 19, 24, 17, 16, 25, 24, 22, 21, 21]
data = np.array(list(zip(x, y)))
kmeans = KMeans(n_clusters=2, n_init=10, random_state=0)
kmeans.fit(data)
print("labels: ", kmeans.labels_)
print("centroids:", kmeans.cluster_centers_)
print("predict new:", kmeans.predict([[5, 20]]))n_init=10 restarts the algorithm 10 times from different seeds and keeps the best result, guarding against poor random initialization. scikit-learn uses the smart k-means++ seeding by default.
Choosing k: The Elbow Method
Plot the within-cluster sum of squares (inertia_) for a range of k. The "elbow" — where adding clusters stops helping much — is a good choice.
import matplotlib.pyplot as plt
inertias = []
for k in range(1, 8):
km = KMeans(n_clusters=k, n_init=10, random_state=0).fit(data)
inertias.append(km.inertia_)
plt.plot(range(1, 8), inertias, marker="o")
plt.xlabel("k")
plt.ylabel("inertia")
plt.title("Elbow Method")
plt.show()Validating with Silhouette Score
The silhouette score (−1 to 1) measures how well each point fits its cluster versus the next-nearest one. Higher is better; pick the k that maximizes it.
from sklearn.metrics import silhouette_score
for k in range(2, 6):
km = KMeans(n_clusters=k, n_init=10, random_state=0).fit(data)
print(k, silhouette_score(data, km.labels_).round(3))Limitations and Alternatives
- Assumes spherical clusters of similar size — struggles with elongated or nested shapes.
- Sensitive to feature scale: always standardize first.
- Sensitive to outliers, which drag centroids. Consider
KMedoidsorDBSCAN. - For non-spherical clusters, try
DBSCANor Gaussian Mixture Models.
Best Practices
- Scale features before clustering.
- Use the elbow and silhouette methods together to choose k.
- Keep
n_inithigh (10+) for stable results. - Use
MiniBatchKMeansfor very large datasets.
Try It Yourself
Exercise 1: Cluster 6 points into 2 groups and print the centroids.
Show solution
import numpy as np
from sklearn.cluster import KMeans
data = np.array([[1, 1], [1, 2], [2, 1], [8, 8], [9, 8], [8, 9]])
km = KMeans(n_clusters=2, n_init=10, random_state=0).fit(data)
print(km.cluster_centers_.round(1))Exercise 2: What does the "elbow" in an inertia-vs-k plot help you choose?
Show solution
A good value of k — the point where adding more clusters stops meaningfully reducing inertia.
📘 Real-World Deep Dive
Knowing <strong>ML Kmeans (scikit-learn)</strong> well is what turns scikit-learn from a curiosity into a daily tool — you'll reach for it in nearly every real project.
Real-Life Scenario
An end-to-end usage of ML Kmeans that you'd actually see in a data pipeline or analytics notebook.
Real-Life Example
import numpy as np
from sklearn.cluster import KMeans
rng = np.random.default_rng(0)
X = rng.normal(0, 1, (60, 2))
km = KMeans(n_clusters=3, n_init=10, random_state=0).fit(X)
print("inertia:", round(km.inertia_, 3))
print("labels[:10]:", km.labels_[:10])Expected Output
(see source)Common mistakes
fitexpects numeric arrays;OneHotEncoder/LabelEncoderare easy to forget for categorical features.- Calling
predicton a model trained on unscaled data and then scaling inputs at inference time silently degrades accuracy. train_test_split(X, y)requires both arrays;train_test_split(X)for unsupervised learning slips past static checkers.- Treating ML Kmeans as a black box without reading the docs — the API has subtle defaults that bite when you scale.
🚀 Performance & Best Practices
- Wrap preprocessing + estimator in a
Pipelinesofit/predictstay reproducible. - Use
joblib/picklefor serialising models, not the entire Python state. - For > 100 k rows, switch to
HistGradientBoostingClassifierorcuML. - When working with scikit-learn, prefer vectorised / batched operations over Python loops.
🧪 Try It Yourself
- Reproduce the snippet on a representative slice of your own data.
- Profile the snippet with
cProfileortimeitand find the single biggest improvement. - Generalise the snippet into a small, reusable function you can drop into future projects.