Dataset: Mall Customer Segmentation (Kaggle) — 200 πελάτες, μεταβλητές: CustomerID, Gender, Age, Annual Income (k$), Spending Score (1-100).

library(cluster)         
library(scatterplot3d)   
mall = read.csv("Mall_Customers.csv")
colnames(mall) = c("customer","Gender","Age","Income","Spending")
str(mall)
## 'data.frame':    200 obs. of  5 variables:
##  $ customer: int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Gender  : chr  "Male" "Male" "Female" "Female" ...
##  $ Age     : int  19 21 20 23 31 22 35 23 64 30 ...
##  $ Income  : int  15 15 16 16 17 17 18 18 19 19 ...
##  $ Spending: int  39 81 6 77 40 76 6 94 3 72 ...
summary(mall[,c("Age","Income","Spending")])
##       Age            Income          Spending    
##  Min.   :18.00   Min.   : 15.00   Min.   : 1.00  
##  1st Qu.:28.75   1st Qu.: 41.50   1st Qu.:34.75  
##  Median :36.00   Median : 61.50   Median :50.00  
##  Mean   :38.85   Mean   : 60.56   Mean   :50.20  
##  3rd Qu.:49.00   3rd Qu.: 78.00   3rd Qu.:73.00  
##  Max.   :70.00   Max.   :137.00   Max.   :99.00

1 Επιλογή μεθόδου

Γιατί K-means. Το διάλεξα διότι δεν θέλω κατι supervised:

  • Δεν υπάρχει μεταβλητή-στόχος => δεν κάνει sense classification/regression. Το πρόβλημα είναι καθαρά unsupervised: πρέπει να ανακαλύψουμε δομή, όχι να προβλέψουμε κάτι γνωστό.

  • Οι μεταβλητές (Income, Spending, Age) είναι συνεχείς αριθμητικές => K-means δουλεύει φυσικά πάνω σε ευκλείδεια απόσταση.

  • Το dataset είναι μικρό όπου n=200 και θέλουμε ξεκάθαρα, ερμηνεύσιμα clusters για να τα παρουσιάσουμε στο marketing team => K-means με λίγα, spherical clusters είναι ιδανικό, σε αντίθεση με πιο πολύπλοκες μεθόδους ’οπως DBSCAN και GMM που προσφέρουν ευελιξία κάτι που εδώ δεν χρειαζόμαστε.

  • Εναλλακτικά θα μπορούσε να χρησιμοποιηθεί hierarchical clustering . Δείχνουμε dendrogram παρακάτω σαν επιβεβαίωση αλλά το K-means προτιμάται εδώ γιατί πρότων θέλουμε συγκεκριμένο, σταθερό αριθμό ομάδων για καμπάνιες και δεύτερον θέλουμε επίσης scale καλύτερα αν αυξηθεί ο αριθμός πελατών στο μέλλον.

standardization. Το Income είναι σε κλίμακα 15-137 (χιλιάδες $) ενώ το Spending Score σε 1-100 — χωρίς scale, το Income θα κυριαρχούσε τεχνητά στην απόσταση. Άρα χρησιμοποιούμε scale().


2 Ερώτηση 1 — Πόσες ομάδες αναδεικνύονται;

2.1 Elbow method (WCSS)

X = scale(mall[, c("Income","Spending")])

set.seed(123)
wcss = sapply(1:10, function(k) kmeans(X, centers = k, nstart = 25)$tot.withinss)

plot(1:10, wcss, type = "b", pch = 19, col = "steelblue",
     xlab = "Αριθμός clusters (k)", ylab = "WCSS (Within-Cluster SS)",
     main = "Elbow Method - Income & Spending")

2.2 Silhouette method (επιβεβαίωση)

sil = sapply(2:10, function(k) {
  km = kmeans(X, centers = k, nstart = 25)
  mean(silhouette(km$cluster, dist(X))[, 3])
})

plot(2:10, sil, type = "b", pch = 19, col = "darkorange",
     xlab = "Αριθμός clusters (k)", ylab = "Μέσο Silhouette Width",
     main = "Silhouette Method - Income & Spending")

data.frame(k = 2:10, silhouette = round(sil, 3))
##    k silhouette
## 1  2      0.286
## 2  3      0.467
## 3  4      0.494
## 4  5      0.555
## 5  6      0.540
## 6  7      0.528
## 7  8      0.457
## 8  9      0.459
## 9 10      0.441

Απάντηση: Το elbow plot δείχνει καθαρή “κάμψη” (kink) στο k=5 (η WCSS πέφτει απότομα ως το 5, μετά η βελτίωση επιπεδώνεται). Το silhouette το επιβεβαιώνει ποσοτικά: η μεγαλύτερη μέση τιμή silhouette (0.555) είναι ακριβώς στο k=5.

Τεκμηρίωση: 5 διακριτές ομάδες πελατών.

km5 = kmeans(X, centers = 5, nstart = 25)
mall$Cluster = km5$cluster
table(mall$Cluster)
## 
##  1  2  3  4  5 
## 22 23 81 39 35

3 Ερώτηση 2 — Προφίλ κάθε ομάδας (εισόδημα / spending)

profile = aggregate(cbind(Income, Spending, Age) ~ Cluster, data = mall, FUN = mean)
profile$n = as.vector(table(mall$Cluster))
profile[order(profile$Cluster), ]
##   Cluster   Income Spending      Age  n
## 1       1 25.72727 79.36364 25.27273 22
## 2       2 26.30435 20.91304 45.21739 23
## 3       3 55.29630 49.51852 42.71605 81
## 4       4 86.53846 82.12821 32.69231 39
## 5       5 88.20000 17.11429 41.11429 35
plot(mall$Income, mall$Spending, col = mall$Cluster, pch = 19,
     xlab = "Annual Income (k$)", ylab = "Spending Score (1-100)",
     main = "Πελάτες κατά Cluster")
points(km5$centers[,1]*attr(X,"scaled:scale")[1] + attr(X,"scaled:center")[1],
       km5$centers[,2]*attr(X,"scaled:scale")[2] + attr(X,"scaled:center")[2],
       col = 1:5, pch = 8, cex = 2.5, lwd = 3)
legend("topright", legend = paste("Cluster", 1:5), col = 1:5, pch = 19, cex=0.8)

Προφίλ (με βάση τα πραγματικά μέσα):

Cluster Income Spending Age (μέση) n Προφίλ
1 ~26k$ ~79 ~25 22 Χαμηλό εισόδημα, υψηλή δαπάνη — “impulsive/young spenders”
2 ~26k$ ~21 ~45 23 Χαμηλό εισόδημα, χαμηλή δαπάνη — “careful/budget”
3 ~55k$ ~50 ~43 81 Μέσο εισόδημα, μέση δαπάνη — “mainstream/average” (η μεγαλύτερη ομάδα)
4 ~87k$ ~82 ~33 39 Υψηλό εισόδημα, υψηλή δαπάνη — “premium loyal”
5 ~88k$ ~17 ~41 35 Υψηλό εισόδημα, χαμηλή δαπάνη — “untapped high-value”

4 Ερώτηση 3 — Υπάρχει ομάδα «υψηλό εισόδημα – χαμηλές δαπάνες»;

Απάντηση: Ναι — το Cluster 5 (Income ≈ 88k$, Spending ≈ 17). Είναι 35 πελάτες (17.5% της βάσης) που έχουν την οικονομική δυνατότητα αλλά δεν ξοδεύουν στο mall. Αυτή είναι κλασική «ευκαιρία» ομάδα: η αγοραστική δύναμη υπάρχει, κάτι άλλο (εμπειρία, σχετικότητα προϊόντων, απόσταση, brand fit) τους αποτρέπει. Αξίζει ειδική έρευνα/στόχευση δεν είναι απλά “δεν έχουν λεφτά”, το πρόβλημα είναι αλλού.


5 Ερώτηση 4 — Πώς αλλάζει η εικόνα με την ηλικία ως 3η διάσταση;

X3 = scale(mall[, c("Age","Income","Spending")])

set.seed(123)
wcss3 = sapply(1:10, function(k) kmeans(X3, centers = k, nstart = 25)$tot.withinss)
sil3  = sapply(2:10, function(k) {
  km = kmeans(X3, centers = k, nstart = 25)
  mean(silhouette(km$cluster, dist(X3))[, 3])
})

par(mfrow = c(1,2))
plot(1:10, wcss3, type="b", pch=19, col="steelblue", main="Elbow (3D)",
     xlab="k", ylab="WCSS")
plot(2:10, sil3, type="b", pch=19, col="darkorange", main="Silhouette (3D)",
     xlab="k", ylab="avg width")

par(mfrow = c(1,1))

data.frame(k = 2:10, silhouette_3D = round(sil3,3))
##    k silhouette_3D
## 1  2         0.335
## 2  3         0.358
## 3  4         0.404
## 4  5         0.417
## 5  6         0.427
## 6  7         0.417
## 7  8         0.407
## 8  9         0.421
## 9 10         0.401
km3D = kmeans(X3, centers = 5, nstart = 25)
mall$Cluster3D = km3D$cluster

profile3D = aggregate(cbind(Age, Income, Spending) ~ Cluster3D, data = mall, FUN = mean)
profile3D$n = as.vector(table(mall$Cluster3D))
profile3D[order(profile3D$Cluster3D), ]
##   Cluster3D      Age   Income Spending  n
## 1         1 39.87179 86.10256 19.35897 39
## 2         2 32.87500 86.10000 81.52500 40
## 3         3 25.18519 41.09259 62.24074 54
## 4         4 46.25000 26.75000 18.35000 20
## 5         5 55.63830 54.38298 48.85106 47
colors5 = c("#E41A1C","#377EB8","#4DAF4A","#984EA3","#FF7F00")
scatterplot3d(mall$Age, mall$Income, mall$Spending,
              color = colors5[mall$Cluster3D], pch = 19,
              xlab = "Age", ylab = "Income (k$)", zlab = "Spending Score",
              main = "3D Clustering: Age + Income + Spending")

Απάντηση: Η εικόνα αλλάζει σημαντικά:

  1. Το silhouette προτείνει πλέον k=6 (0.427) αντί για k=5, αν και το k=5 (0.417) είναι πολύ κοντά, η ηλικία εισάγει επιπλέον διαχωριστική πληροφορία.

  2. Κρατώντας k=5 για συγκρισιμότητα, το μεγάλο “mainstream” cluster (81 άτομα) της 2D ανάλυσης σπάει σε ηλικιακά διαφοροποιημένα υπο-group, δεν είναι πια μία ενιαία μάζα, βλέπουμε νεότερους vs μεγαλύτερους με παρόμοιο εισόδημα/δαπάνη.

  3. Το πρώην “premium” cluster (υψηλό income, υψηλό spending) αποκαλύπτεται ότι είναι σχετικά νεαρό (μέση ηλικία ~33), ενώ το “untapped high-value” cluster (υψηλό income, χαμηλό spending) είναι μεγαλύτερης ηλικίας (~41-46), πληροφορία που το 2D μοντέλο έκρυβε εντελώς.

  4. Business insight: η ηλικία δείχνει ότι το «high-income low-spending» πρόβλημα πιθανόν σχετίζεται με γενιά/lifestyle και όχι απλά τυχαία — άρα η καμπάνια στόχευσης πρέπει να είναι διαφορετική (π.χ. πιο “premium/experience” προσανατολισμένη παρά “νεανικό branding”).


6 Ερώτηση 5 — Ποια ομάδα πρώτη για premium καμπάνια;

Απάντηση: Cluster 4 (Υψηλό εισόδημα ~87k$, Υψηλή δαπάνη ~82, ηλικία ~33).

Στα δεδομένα βλέπουμε πως:

  • Είναι ήδη οι πιο ενεργοί, υψηλής αξίας πελάτες (spending score 82/100) έχουν ήδη αποδείξει ότι εμπιστεύονται και ξοδεύουν στο mall. Το ρίσκο μιας premium καμπάνιας πάνω τους είναι χαμηλό, το προσδοκώμενο ROI υψηλό (upsell σε ήδη αφοσιωμένους πελάτες, όχι πείσμα νέων).

  • Είναι σχετικά νέοι (μέση ηλικία 33) μεγαλύτερο lifetime value μπροστά τους αν χτιστεί loyalty τώρα.

  • Αντίθετα, το Cluster 5 (υψηλό income, χαμηλό spending) είναι πιο ρισκαρισμένη στόχευση: δεν ξέρουμε γιατί δεν ξοδεύουν ήδη· μια premium καμπάνια εκεί χρειάζεται πρώτα διερεύνηση αιτίας (π.χ. survey) πριν επενδύσουμε marketing budget.

Σειρά προτεραιότητας: Cluster 4 (immediate premium/VIP campaign) → Cluster 5 (research-first, then targeted campaign) → Cluster 3 (upsell mainstream, μεγαλύτερος όγκος αλλά χαμηλότερο margin ανά πελάτη).