The previous module ended on an uncomfortable note: all seven supervised algorithms demanded a y column, and MercaFresh has no column saying which segment each customer belongs to — the segments are precisely what it wants to discover. This lesson opens unsupervised learning with its flagship algorithm: K-means. You will learn what clustering is and how it differs radically from classification, how K-means works step by step (down to running it by hand), how to decide how many groups to look for using inertia and the elbow method, how to measure quality with the silhouette score, and how to apply it with scikit-learn to MercaFresh's RFM features to obtain customer segments with a business reading. This is the lesson that turns "we want to segment our customers" into a concrete procedure.
Contents
- Clustering: grouping without labels
- The K-means algorithm step by step
- A worked numeric example by hand
- How many clusters? Inertia and the elbow method
- Silhouette score: measuring the quality of the partition
- k-means++ and
n_init: the initialization problem - Segmenting MercaFresh customers with scikit-learn
- Interpretation: centroid profiles
- Limitations of K-means
Clustering: grouping without labels
In 01-03 we distinguished supervised from unsupervised; now the difference becomes operational. Compare MercaFresh's two problems:
| Classification (04-02) | Clustering (this lesson) | |
|---|---|---|
| Question | Will this customer churn? | What groups of customers exist? |
Label y |
Yes: churn (0/1), observed in the history |
Doesn't exist: nobody knows the groups in advance |
| "Right answer" | Yes: we can compare prediction against reality | No: there is no ground truth to correct against |
| The model learns | A boundary features → label | The internal structure of the features |
| Evaluation | Hits against known labels | Internal measures of cohesion/separation |
Two practical consequences of there being no "right answer":
- There is no error to minimize against labels. The algorithm optimizes an internal criterion: points in the same group should be close to each other (cohesion) and far from the other groups (separation).
- The final validation is human. A clustering is useful if the groups it discovers mean something to the business ("VIP customers", "dormant", "new"). Two different partitions can both be defensible; in classification that never happens.
Clustering answers discovery questions: customer segments, families of products bought together, behavior patterns nobody had named yet.
The K-means algorithm step by step
K-means aims to split $n$ points into $K$ groups, each represented by its centroid (the group's mean point). The whole idea fits in a two-step cycle that repeats until nothing changes:
- Initialization: choose $K$ (the analyst sets it!) and place $K$ initial centroids.
- Assignment: each point is assigned to its nearest centroid (Euclidean distance, the same one from 04-05).
- Update: each centroid is recomputed as the mean of the points assigned to it (hence means).
- Convergence: if no assignment has changed (or the centroids barely move), stop; otherwise, go back to step 2.
flowchart TD
A["Choose K and place<br/>K initial centroids"] --> B["ASSIGNMENT:<br/>each point goes to the<br/>nearest centroid"]
B --> C["UPDATE:<br/>each centroid = mean<br/>of its assigned points"]
C --> D{"Did any assignment<br/>change?"}
D -- "Yes" --> B
D -- "No" --> E["Convergence:<br/>final clusters"]
Notice the parallel with gradient descent from 04-01: here too there is a loop that improves an objective function on every pass. The function K-means reduces at each iteration is the inertia:
$$\text{Inertia} = \sum_{i=1}^{n} \lVert x_i - c_{k(i)} \rVert^2$$
that is, the sum of squared distances from each point to its centroid. Every assignment and every update either reduces it or leaves it unchanged, so the algorithm always converges — although, as we will see, it may converge to a local optimum.
And the clarification promised in 04-05: K-NN and K-means share nothing but the letter K. K-NN is supervised (it votes among labeled neighbors) and its K is the number of neighbors consulted; K-means is unsupervised and its K is the number of groups sought. All they share is the use of Euclidean distance — which is why both demand scaling (03-05).
A worked numeric example by hand
Let's run K-means on 6 MercaFresh customers using a single feature, recency_days, with $K=2$:
Initialization (deliberately bad, to see the algorithm recover): centroids $c_1 = 3$ and $c_2 = 8$.
Iteration 1 — assignment: each point goes to its nearest centroid.
| Point | Dist. to $c_1=3$ | Dist. to $c_2=8$ | Cluster |
|---|---|---|---|
| 3 | 0 | 5 | 1 |
| 5 | 2 | 3 | 1 |
| 8 | 5 | 0 | 2 |
| 40 | 37 | 32 | 2 |
| 45 | 42 | 37 | 2 |
| 52 | 49 | 44 | 2 |
Iteration 1 — update: $c_1 = \text{mean}(3,5) = 4$; $c_2 = \text{mean}(8,40,45,52) = 36.25$.
Iteration 2 — assignment: point 8 is now at distance 4 from $c_1$ and 28.25 from $c_2$ → it switches to cluster 1. The others stay put.
Iteration 2 — update: $c_1 = \text{mean}(3,5,8) = 5.33$; $c_2 = \text{mean}(40,45,52) = 45.67$.
Iteration 3 — assignment: nobody moves. Convergence.
Result: {3, 5, 8} (recent customers) and {40, 45, 52} (cold customers). Even though the initialization put both centroids inside the same natural group, the assign-update cycle pulled them apart in two iterations. With two features the procedure is identical, except the distances are Euclidean in 2D — a good moment to revisit the calculation from 04-05.
How many clusters? Inertia and the elbow method
K-means does not discover K: it demands it as input. How do you choose it? Inertia alone cannot arbitrate, because it always drops as K grows (with $K=n$ every point is its own centroid and inertia = 0). What is informative is how it drops:
- While K is smaller than the number of real groups, adding a cluster splits a genuine group and inertia falls sharply.
- Once K exceeds the number of real groups, new clusters merely chop up groups that were already compact and the gain is marginal.
The elbow method consists of plotting inertia against K and looking for the point where the curve goes from plunging to easing off — the "elbow".
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
inertias = []
k_range = range(1, 11)
for k in k_range:
km = KMeans(n_clusters=k, random_state=42)
km.fit(X_esc) # X_esc: RFM features, already scaled
inertias.append(km.inertia_) # attribute holding the final inertia
plt.plot(k_range, inertias, "o-")
plt.xlabel("Number of clusters (K)")
plt.ylabel("Inertia")
plt.title("Elbow method — MercaFresh customers")
plt.show()What the code does: it trains one K-means for each K from 1 to 10 and stores inertia_, which scikit-learn computes after converging. On the MercaFresh customer data the curve typically bends around K=4: from 1 to 4, each new cluster cuts the inertia a lot; beyond 4, hardly at all. The elbow is not always sharp — it is a guide, not an oracle; the final decision combines the elbow, the silhouette (next section) and business interpretability.
Silhouette score: measuring the quality of the partition
Without labels there can be no accuracy, but we can measure whether the partition is geometrically good. The silhouette score evaluates, for each point $i$:
- $a(i)$: the mean distance from $i$ to the points of its own cluster (cohesion: the smaller, the better).
- $b(i)$: the mean distance from $i$ to the points of the nearest foreign cluster (separation: the larger, the better).
$$s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}$$
The interpretation is straightforward:
| $s(i)$ | Reading |
|---|---|
| Close to +1 | The point is much closer to its own kind than to the neighboring cluster: well assigned |
| Close to 0 | It sits on the boundary between two clusters |
| Negative | It is closer to another cluster than to its own: probably misassigned |
The overall score is the mean of all the $s(i)$. As a rule of thumb: above 0.5 the structure is clear; between 0.25 and 0.5, reasonable; below that, weak or nonexistent. On real customer data (which never forms perfect islands) values of 0.3-0.5 are common and perfectly usable.
from sklearn.metrics import silhouette_score
for k in range(2, 8): # silhouette requires K >= 2
labels = KMeans(n_clusters=k, random_state=42).fit_predict(X_esc)
print(f"K={k} | silhouette = {silhouette_score(X_esc, labels):.3f}")Used alongside the elbow, the silhouette usually breaks the tie: if the elbow hesitates between 3 and 4, the K with the better silhouette wins (or the one that yields the most actionable segments for the business).
k-means++ and n_init: the initialization problem
K-means always converges, but not necessarily to the best result: unlucky initial centroids (say, two inside the same natural group and none in another) can leave the algorithm trapped in a local optimum with worse inertia. Two standard defenses, both on by default in scikit-learn:
- k-means++ (
init="k-means++"): instead of purely random centroids, it picks the first at random and each subsequent one with probability proportional to the squared distance to those already chosen — that is, it tends to spread them far apart. Well-spread starts, faster and better convergence. n_init: runs the complete algorithm several times with different initializations and keeps the run with the lowest inertia. In modern versions of scikit-learn the default isn_init="auto".
With both enabled, K-means is very stable in practice; even so, set random_state so your results are reproducible.
Segmenting MercaFresh customers with scikit-learn
Time for the flagship example. We start from the rfm table built in 03-06 — recency_days, orders_per_month, avg_order_spend, plus inactivity_ratio — and from lesson 03-05 we know that a distance-based algorithm demands StandardScaler: without it, avg_order_spend (tens of euros) would crush orders_per_month (single digits) by sheer accident of units.
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
features = ["recency_days", "orders_per_month",
"avg_order_spend", "inactivity_ratio"]
X = rfm[features]
# 1. Scale: non-negotiable for a distance-based algorithm (03-05)
scaler = StandardScaler()
X_esc = scaler.fit_transform(X)
# 2. Train with the K chosen via elbow + silhouette
km = KMeans(n_clusters=4, init="k-means++", n_init="auto", random_state=42)
rfm["segment"] = km.fit_predict(X_esc)
print(silhouette_score(X_esc, rfm["segment"]).round(3)) # e.g. 0.41
print(rfm["segment"].value_counts())Code details, line by line:
fit_predicttrains and directly returns each customer's cluster label (0 to 3). Careful: these labels are made up by the algorithm, with no inherent meaning or order — today's "segment 2" may be called "0" if you retrain with a different seed. We will supply the meaning ourselves when we interpret.- Note a key difference from module 4: there is no
train_test_splithere, because there are no labels to predict on new data; we use all the customers to discover their structure. (Evaluating supervised models, splits and all, arrives in module 6.) - We scale with
fit_transformover the whole dataset for the same reason: with no test set, there is no leakage to avoid.
Interpretation: centroid profiles
A clustering without interpretation is a number from 0 to 3 stuck onto each customer: useless. The key tool is the unscaled centroids: since each centroid is the mean of its group, describing it in original units paints a portrait of the segment's typical customer.
# Centroids in original units (undoing the scaling)
centroids = pd.DataFrame(scaler.inverse_transform(km.cluster_centers_),
columns=features).round(2)
centroids["n_customers"] = rfm["segment"].value_counts().sort_index().values
print(centroids)km.cluster_centers_ lives in the scaled space (means 0, standard deviations 1); inverse_transform brings it back to days, orders and euros. A typical result at MercaFresh:
| Segment | recency_days | orders_per_month | avg_order_spend | inactivity_ratio | n_customers | Business reading |
|---|---|---|---|---|---|---|
| 0 | 9 | 4.1 | €78 | 0.05 | 180 | VIP: buy a lot, often, and just bought |
| 1 | 15 | 2.2 | €34 | 0.10 | 410 | Regulars: the healthy base of the business |
| 2 | 95 | 0.6 | €41 | 0.62 | 150 | Dormant: valuable but cooling off — churn risk |
| 3 | 21 | 1.1 | €22 | 0.28 | 260 | Occasional: sporadic purchases, low ticket |
This table is the real deliverable of the segmentation: the marketing team can design one action per row (a loyalty program for VIPs, a reactivation campaign for the dormant...). Note the circle closing: segment 2 matches the profile that the logistic regression of 04-02 flagged as likely churn — the unsupervised method discovers the group the supervised one was predicting, without ever seeing a single label.
Limitations of K-means
K-means is fast, simple and scalable, but it pays for those virtues with strong assumptions:
- Spherical clusters of similar size. By assigning on distance to the centroid, K-means draws boundaries that carve the space into convex regions: two elongated, nested or moon-shaped groups will be split badly no matter how obvious they are to the eye (we will see it graphically in 05-04, where DBSCAN handles them).
- K fixed up front. The elbow and the silhouette help, but the decision is the analyst's; with ambiguous structure, two different Ks can both be defensible. Hierarchical clustering (05-02) partially sidesteps the problem by showing all the grouping scales at once.
- Sensitivity to outliers. The centroid is a mean, and we saw in 02-01 what an extreme value does to a mean: a customer with a huge spend drags its centroid along or even hijacks a whole cluster for itself. Mitigations: treat outliers beforehand (03-01) or use a log transformation (03-03).
- Sensitivity to scale. Without
StandardScaler, the highest-magnitude feature dominates the distance and the resulting clustering is an artifact of the units (03-05). It is not optional: it is a requirement. - Every point ends up in a cluster. K-means has no concept of "noise": the most anomalous order in the world will still be assigned to some centroid. For anomaly detection we will need DBSCAN (05-04).
Common Mistakes and Tips
- Forgetting to scale. Mistake number one. If your segments seem ordered purely by spend, you almost certainly clustered euros against unit counts without scaling. Always check that you applied
StandardScalerbeforefit. - Treating the cluster number as a meaningful category. The 0/1/2/3 label is arbitrary and can change between runs. Name segments by their profile (the centroid table), never by their number.
- Hunting for an elbow in structureless data. If the customers form a continuous cloud with no groups, the elbow curve will be smooth with no bend and the silhouette will hover near 0. That is also a result: it means there are no natural segments with those features — try other features before forcing a K.
- Interpreting centroids in the scaled space. A centroid with
spend = 1.8does not mean €1.80: those are standard deviations. Useinverse_transformbefore presenting profiles to the business. - Tip: run the clustering two or three times with different
random_statevalues (keepingn_init). If the segments shift a lot, the structure is weak and any fine-grained interpretation deserves suspicion.
Exercises
Exercise 1. Continue the lesson's worked example by hand with the same 6 points (3, 5, 8, 40, 45, 52) but with the initialization $c_1 = 40$, $c_2 = 45$ and $K=2$. Iterate until convergence. Do you reach the same final partition as in the lesson? What does this tell you about local optima?
Exercise 2. Using the MercaFresh rfm table (or an equivalent synthetic DataFrame), compute the elbow and the silhouette for K from 2 to 8 using only two features: recency_days and orders_per_month. Choose a K, justifying it with both criteria, and plot the clusters in a scatter with plt.scatter(..., c=labels).
Exercise 3. Add to the rfm table an outlier customer with avg_order_spend = 5000 (all other features at average values) and re-run the lesson's K-means with K=4, with and without StandardScaler. Compare the centroid tables. What happens to the outlier in each case, and why?
Solutions
Exercise 1
- Iteration 1 — assignment: 3, 5, 8 and 40 go with $c_1=40$ (they are closer to 40 than to 45); 45 and 52 go with $c_2=45$. Update: $c_1 = \text{mean}(3,5,8,40) = 14$; $c_2 = \text{mean}(45,52) = 48.5$.
- Iteration 2 — assignment: 40 is at distance 26 from $c_1$ and 8.5 from $c_2$ → it switches to cluster 2. Update: $c_1 = 5.33$; $c_2 = 45.67$.
- Iteration 3: nobody moves. Convergence at {3,5,8} and {40,45,52} — the same partition as in the lesson despite an opposite initialization. In this dataset the structure is so clear that both starting points reach the same optimum; with ambiguous data that is not guaranteed, which is why k-means++ and
n_initexist.
Exercise 2
import numpy as np
from sklearn.metrics import silhouette_score
X2 = StandardScaler().fit_transform(rfm[["recency_days", "orders_per_month"]])
for k in range(2, 9):
km = KMeans(n_clusters=k, random_state=42)
lab = km.fit_predict(X2)
print(f"K={k} | inertia={km.inertia_:8.1f} | sil={silhouette_score(X2, lab):.3f}")
km = KMeans(n_clusters=3, random_state=42) # adjust to your chosen K
lab = km.fit_predict(X2)
plt.scatter(X2[:, 0], X2[:, 1], c=lab, s=15)
plt.scatter(*km.cluster_centers_.T, marker="X", s=200, c="red")
plt.xlabel("recency (scaled)"); plt.ylabel("orders/month (scaled)")
plt.show()With only these two features, elbow and silhouette usually point to K=3 (frequent actives, sporadic actives, dormant): removing spend makes the VIP/regular distinction disappear. The implicit lesson: the segments depend on the features chosen, not just on the algorithm.
Exercise 3
Without scaling, the spend column dominates the distance completely: the outlier ends up alone in its own cluster (or brutally drags a centroid), and the other three clusters divide up by spend, ignoring recency and frequency. With StandardScaler the damage shrinks (the outlier sits a few standard deviations away, not three orders of magnitude), but it is still the most influential point in its cluster and biases its centroid upward — the mean forgives nothing (02-01). The right treatment: detect and handle the outlier during cleaning (03-01) or soften it with log1p (03-03) before segmenting.
Conclusion
You have crossed the border into unsupervised learning: with no labels and no "right answer", K-means discovers groups by alternating two steps — assigning each point to its nearest centroid and recomputing centroids as means — until convergence. You know how to choose K by combining the elbow (inertia) with the silhouette, you know that k-means++ and n_init protect you from bad initializations, and you have segmented MercaFresh's customers with their scaled RFM features, turning cluster numbers into named profiles with a business action. You also know the fine print: spherical clusters, K up front, an allergy to outliers and to mismatched scales.
Among those limitations there is one that is especially elegant to solve: what if we did not have to fix K in advance, but could instead see every way of grouping the customers — from 2 groups to 20 — in a single drawing and cut wherever suits us best? That drawing exists, it is called a dendrogram, and it is the heart of the next lesson: hierarchical clustering.
Machine Learning Course
Module 1: Introduction to Machine Learning
- What is Machine Learning?
- History and evolution of Machine Learning
- Types of Machine Learning
- Applications of Machine Learning
- The Machine Learning project workflow
Module 2: Foundations of Statistics and Probability
- Basic statistics concepts
- Probability distributions
- Correlation and covariance
- Statistical inference
- Bayes' theorem
Module 3: Data Preprocessing
- Data cleaning
- Handling missing data
- Data transformation
- Encoding categorical variables
- Normalization and standardization
- Feature engineering
Module 4: Supervised Machine Learning Algorithms
- Linear regression
- Logistic regression
- Decision trees
- Support Vector Machines (SVM)
- K-Nearest Neighbors (K-NN)
- Naive Bayes
- Neural networks
Module 5: Unsupervised Machine Learning Algorithms
- Clustering: K-means
- Hierarchical clustering
- Principal Component Analysis (PCA)
- DBSCAN clustering
- Data visualization with t-SNE and UMAP
Module 6: Model Evaluation and Validation
- Data splitting: training, validation and test
- Evaluation metrics
- Cross-validation
- ROC curve and AUC
- Overfitting and underfitting
Module 7: Advanced Techniques and Optimization
- Regularization: Ridge, Lasso and Elastic Net
- Ensemble Learning
- Gradient Boosting
- Deep neural networks (Deep Learning)
- Hyperparameter optimization
Module 8: Model Implementation and Deployment
- Popular frameworks and libraries
- Deploying models to production
- Model maintenance and monitoring
- Ethical and privacy considerations
Module 9: Hands-On Projects
- Project 1: Housing price prediction
- Project 2: Image classification
- Project 3: Sentiment analysis on social media
- Project 4: Fraud detection
- Project 5: Customer segmentation
