Unsupervised segmentation of an e-commerce customer base using the RFM (Recency, Frequency, Monetary) method and K-Means, on the public Online Retail II dataset (~1 million real transactions from a UK online retailer, 2009–2011).
Goal: split 5,878 customers into homogeneous, actionable groups to answer a question every business owner asks without being able to answer it — "who are the 20% of customers driving 80% of my revenue, and do I actually know them?"
The result in one sentence: 20% of customers (the Champions) generate 74% of revenue. At the other end, a third of the base accounts for just 3.6%.
One transaction file, one row per purchased product:
| Column | Content |
|---|---|
Invoice |
Order number (C prefix = cancellation) |
StockCode / Description |
Product reference and label |
Quantity |
Quantity (negative for returns) |
InvoiceDate |
Order date and time |
Price |
Unit price (in British pounds £, UK retailer) |
Customer ID |
Customer identifier |
Country |
Shipping country |
Cleaning is the real first job. Out of 1,067,371 raw rows, 261,822 (≈ 25%) are removed before any analysis:
- rows with no
Customer ID(a purchase can't be tied to a customer); - cancellations (
Invoicestarting withC) andQuantity ≤ 0; Price ≤ 0.
That leaves 805,549 clean rows and 5,878 unique customers over ~2 years. Lesson #1: in real e-commerce, a quarter of the raw data is noise — returns, cancellations, anonymous orders. You analyze what's left.
From the clean transactions, we compute three indicators per customer (reference date = the day after the last sale in the dataset):
- Recency — days since their last purchase (lower is better);
- Frequency — number of distinct orders;
- Monetary — total spend.
| Indicator | Median | Mean | Max |
|---|---|---|---|
| Recency (days) | 96 | 201 | 739 |
| Frequency (orders) | 3 | 6.3 | 398 |
| Monetary (£) | 899 | 3,019 | 608,822 |
On all three variables, the mean is far above the median: a handful of customers pull everything up (one single customer spent £608,822). The 25th percentile of frequency is 1 — meaning a quarter of customers ordered only once. Every distribution has a long right tail: it's this skew that dictates what comes next.
K-Means works on distances: left unchecked, the £608,822 customer crushes everything and Monetary (in the thousands) dominates Frequency (in the units). Two moves fix this:
log1pon R, F and M to compress the long tail (on a log scale the distributions become almost symmetric);StandardScalerto put the three axes on the same scale.
We test k from 2 to 8, crossing two criteria:
- Elbow method: inertia drops sharply until k=4, then flattens.
- Silhouette score: local maximum at k=4 (0.365).
We keep k=4: it's the only point where the elbow and the silhouette agree. The silhouette peak at k=2 (0.438) is discarded — mathematically the cleanest split, but it merely cuts "big vs small customers," with no business value.
A cluster labeled "3" means nothing to anyone. By reading each group's average profile, we give them a business-owner-friendly name:
| Segment | Avg. Recency | Avg. Frequency | Avg. Monetary | Reading |
|---|---|---|---|---|
| Champions | 27 d | 19.3 | £11,014 | Recent, very frequent, big baskets |
| Loyal | 28 d | 3.0 | £865 | Come back often, small baskets |
| Cooling down | 228 d | 5.1 | £2,002 | Good customers going quiet |
| Lost / to reactivate | 396 d | 1.4 | £326 | One purchase, gone for ~13 months |
For each segment, we compare its share of customers against its share of revenue:
| Segment | Customers | % of customers | % of revenue |
|---|---|---|---|
| Champions | 1,188 | 20.2% | 73.7% |
| Cooling down | 1,465 | 24.9% | 16.5% |
| Loyal | 1,251 | 21.3% | 6.1% |
| Lost / to reactivate | 1,974 | 33.6% | 3.6% |
The contrast is stark: 1 in 5 customers drives nearly three quarters of revenue, while a third of the base (the Lost) accounts for just 3.6%.
Segmentation doesn't describe — it tells you what to do. Two opposite levers come straight out of the numbers:
- Protect the Champions — 20% of customers, 74% of revenue. Losing them doesn't cost 20% of the top line, it costs 74%. That's where the first euro of retention should go.
- Reactivate the Lost — a third of the base, dormant. They've already bought once: the sleeping potential is huge, and a targeted win-back costs far less than cold acquisition.
Treating every customer the same is watering with a firehose. Segmentation turns an undifferentiated base into four precise marketing actions.
.
├── analyse.py # Cleaning + RFM table + EDA
├── segmentation.py # log + scaling + choosing k + K-Means + charts
├── rfm_table.csv # RFM table (generated by analyse.py)
├── eda_rfm.png # R, F, M distributions
├── choix_k.png # Elbow + silhouette
├── rfm_segments.png # % of customers vs % of revenue by segment
├── data/ # online_retail_II.csv (not versioned)
└── README.md
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install pandas matplotlib scikit-learn openpyxl
python analyse.py # → rfm_table.csv + eda_rfm.png
python segmentation.py # → choix_k.png + rfm_segments.pngDownload the dataset from
Kaggle — Online Retail II
and place it in data/ as online_retail_II.csv.
- Cross the segmentation with country or seasonality (recency shifts with the period).
- Add a quantile-based RFM score (1–5 grid) alongside the clustering — more readable for a marketing team.
- Estimate a customer lifetime value (CLV) per segment to quantify the ROI of a win-back.
- Track how customers drift from one segment to another over time (transition matrix).
Python · pandas · scikit-learn · matplotlib
Built as a learning project: from raw transactional data to four actionable customer segments.


