Lecture 13 · room 204
HEMA · Università Cattolica del Sacro Cuore, Rome
Friday 13 November 2026
Lecture 12 · Hierarchical clustering · Mon 9 Nov
| Time | Min | |
|---|---|---|
| 09:00 | Last time · Sets 10–11, the In class questions | 20’ |
| 09:20 | Step 1 · load and look | 25’ |
| 09:45 | Step 2 · prepare: transform, then scale | 20’ |
| 10:05 | Step 3 · explore with a tree on a sample | 25’ |
| 10:30 | Break | 15’ |
| 10:45 | Step 4 · decide k, deploy with k-means | 35’ |
| 11:20 | Step 5 · profile, name, hand over | 30’ |
| 11:50 | The deliverable | 10’ |
One table, one deliverable: a segmentation a marketing director could act on Monday. No new theory.
customers.csv — on Blackboard, folder Prof. Salvini, and linked from the schedule
| Column | Meaning |
|---|---|
id |
customer |
recency |
days since last purchase |
frequency |
purchases in the last 12 months |
monetary |
total spend, last 12 months (€) |
tenure |
months since first purchase |
returns |
items returned, last 12 months |
channel_online |
share of purchases made online |
6 000 rows. Save it in your project’s data/ folder, then open the project.
library(tidyverse); library(cluster); library(factoextra); library(here)
cust <- read_csv(here("data", "customers.csv"))
glimpse(cust) # types right? id is not a feature
summary(cust) # ranges, NAs, and the first surprise
cust |> select(-id) |> pivot_longer(everything()) |>
ggplot(aes(value)) + geom_histogram(bins = 40) + facet_wrap(~name, scales = "free")Before any model: what does monetary look like? Almost certainly skewed — a few customers spend a hundred times the median. k-means will build a cluster around them alone.
Answer on paper, one line each:
id leaves now)monetary = 0 with frequency = 12?Attention
Ten minutes here save an afternoon of explaining a segment that is “the three people who bought a car”.
feat <- cust |>
select(-id) |>
mutate(monetary = log1p(monetary), # tame the tail
frequency = log1p(frequency))
X <- scale(feat) # mean 0, sd 1, every column
attr(X, "scaled:center") # keep these: you need them to profile
colMeans(X) |> round(3) # all zero. Good.log1p because log(0) is not a number and some customers spent nothingscale() because distance in euros² + distance in days² is not a distanceSix thousand rows is 18 million pairwise distances — fine. Sixty thousand would not be. The habit: explore the structure on a sample, deploy on everything.
set.seed(26)
idx <- sample(nrow(X), 1500)
hc <- hclust(dist(X[idx, ]), method = "ward.D2")
fviz_dend(hc, k = 5, rect = TRUE, show_labels = FALSE)
fviz_nbclust(X[idx, ], hcut, method = "silhouette", k.max = 8)Look at the tree. Where is the big vertical gap? Two groups? Four? Six? Write down your k before you run the silhouette, then compare.
fviz_nbclust(X, kmeans, method = "wss", k.max = 8) # elbow, full data
fviz_nbclust(X, kmeans, method = "silhouette", k.max = 8)
k <- 4 # your decision, with a reason
set.seed(26)
km <- kmeans(X, centers = k, nstart = 50)Then, the test that matters more than any index:
set.seed(27); km2 <- kmeans(X, centers = k, nstart = 50)
table(km$cluster, km2$cluster) # same groups, different seed?If the table is not close to a permutation matrix, the structure is not stable at this k. Try k - 1.
cust$segment <- factor(km$cluster)
profile <- cust |>
group_by(segment) |>
summarise(n = n(), share = n() / nrow(cust),
across(c(recency, frequency, monetary, tenure, returns, channel_online),
~ round(median(.x), 1)))
profile
fviz_cluster(km, data = X, geom = "point", ellipse.type = "norm")Medians, not means — you logged monetary for a reason, and the mean is back under the tail’s control.
One table, four rows, in the language of the person who will use it:
| Segment | Name | Who they are | What to do |
|---|---|---|---|
| 1 | Lapsed high-value | high monetary, recency > 180 d | win-back, personal, now |
| 2 | Online regulars | frequent, small, 90% online | app push, bundle offers |
| 3 | New and undecided | tenure < 6 mo, moderate | onboarding journey; watch churn |
| 4 | Store loyalists | frequent, in-store, long tenure | protect, do not discount |
Note
Names from the numbers, not from imagination. Every adjective in the table must point at a column in profile.
Push to the homework/ set for this week, or just keep it:
profile tableWe open Monday’s lecture with three of yours on the screen.
Statistics & Big Data · HEMA, Università Cattolica del Sacro Cuore