library(ggplot2)
library(cluster)
library(factoextra)
library(readr)
df <- read_csv("/home/gthegour/Desktop/UoM/BA/Mall_Customers.csv")
names(df) <- make.names(names(df))
df <- as.data.frame(df)
names(df)
## [1] "CustomerID" "Gender" "Age"
## [4] "Annual.Income..k.." "Spending.Score..1.100."
str(df)
## 'data.frame': 200 obs. of 5 variables:
## $ CustomerID : num 1 2 3 4 5 6 7 8 9 10 ...
## $ Gender : chr "Male" "Male" "Female" "Female" ...
## $ Age : num 19 21 20 23 31 22 35 23 64 30 ...
## $ Annual.Income..k.. : num 15 15 16 16 17 17 18 18 19 19 ...
## $ Spending.Score..1.100.: num 39 81 6 77 40 76 6 94 3 72 ...
summary(df)
## CustomerID Gender Age Annual.Income..k..
## Min. : 1.00 Length :200 Min. :18.00 Min. : 15.00
## 1st Qu.: 50.75 N.unique : 2 1st Qu.:28.75 1st Qu.: 41.50
## Median :100.50 N.blank : 0 Median :36.00 Median : 61.50
## Mean :100.50 Min.nchar: 4 Mean :38.85 Mean : 60.56
## 3rd Qu.:150.25 Max.nchar: 6 3rd Qu.:49.00 3rd Qu.: 78.00
## Max. :200.00 Max. :70.00 Max. :137.00
## Spending.Score..1.100.
## Min. : 1.00
## 1st Qu.:34.75
## Median :50.00
## Mean :50.20
## 3rd Qu.:73.00
## Max. :99.00
Το dataset έχει 200 πελάτες με μεταβλητές φύλο, ηλικία, ετήσιο εισόδημα (σε χιλιάδες $) και spending score (1-100). Δεν υπάρχει μεταβλητή-στόχος, άρα το πρόβλημα είναι unsupervised.
Αφού δεν υπάρχουν προκαθορισμένες κατηγορίες πελατών, χρειαζόμαστε μέθοδο clustering. Επέλεξα K-means, γιατί οι μεταβλητές που μας ενδιαφέρουν (εισόδημα, spending score, ηλικία) είναι αριθμητικές/συνεχείς, οπότε το K-means δουλεύει καλά με ευκλείδεια απόσταση. Πριν τρέξουμε το K-means κάνουμε scaling των μεταβλητών (standardize), γιατί το εισόδημα και το spending score έχουν εντελώς διαφορετική κλίμακα και θα κυριαρχούσε το εισόδημα στην απόσταση.
Δουλεύουμε πρώτα με τις 2 βασικές μεταβλητές (Income, Spending Score), όπως λέει η εκφώνηση.
X <- df[, c("Annual.Income..k..", "Spending.Score..1.100.")]
X_scaled <- scale(X)
fviz_nbclust(X_scaled, kmeans, method = "wss", k.max = 10) +
labs(title = "Elbow method - Income & Spending Score")
Στο διάγραμμα η κλίση “σπάει” γύρω στο k=5, δηλαδή μετά το 5 η μείωση του WSS (within-cluster sum of squares) γίνεται πολύ πιο ήπια.
fviz_nbclust(X_scaled, kmeans, method = "silhouette", k.max = 10) +
labs(title = "Silhouette method - Income & Spending Score")
Το silhouette score επιβεβαιώνει ότι k=5 δίνει το καλύτερο αποτέλεσμα (silhouette ≈ 0.55, το υψηλότερο ανάμεσα σε όλα τα k). Άρα διαλέγω 5 clusters, και τα δύο κριτήρια συμφωνούν.
set.seed(42)
km5 <- kmeans(X_scaled, centers = 5, nstart = 25)
df$cluster <- as.factor(km5$cluster)
ggplot(df, aes(x = Annual.Income..k.., y = Spending.Score..1.100., color = cluster)) +
geom_point(size = 2) +
labs(title = "Τμηματοποίηση πελατών - Income vs Spending Score",
x = "Annual Income (k$)", y = "Spending Score (1-100)") +
theme_minimal()
Προκύπτουν καθαρά 5 οπτικά διακριτές ομάδες πελατών, κάτι που ταιριάζει με το elbow/silhouette παραπάνω.
profile <- aggregate(
cbind(Age, Annual.Income..k.., Spending.Score..1.100.) ~ cluster,
data = df, FUN = function(x) round(mean(x), 1)
)
n_per_cluster <- table(df$cluster)
profile$n <- as.integer(n_per_cluster[as.character(profile$cluster)])
colnames(profile) <- c("cluster", "mean_age", "mean_income", "mean_spending", "n")
profile
## cluster mean_age mean_income mean_spending n
## 1 1 45.2 26.3 20.9 23
## 2 2 25.3 25.7 79.4 22
## 3 3 32.7 86.5 82.1 39
## 4 4 42.7 55.3 49.5 81
## 5 5 41.1 88.2 17.1 35
Με βάση τους μέσους όρους, τα 5 clusters περιγράφονται ως εξής:
| Cluster | n | Ηλικία | Εισόδημα | Spending | Προφίλ |
|---|---|---|---|---|---|
| Μέτριο εισόδημα - μέτρια δαπάνη | 81 | ~43 | ~55k | ~50 | “μέσος” πελάτης, καμία ακρότητα |
| Υψηλό εισόδημα - υψηλή δαπάνη | 39 | ~33 | ~86k | ~82 | οι πιο value πελάτες, target για premium |
| Χαμηλό εισόδημα - υψηλή δαπάνη | 22 | ~25 | ~26k | ~79 | νέοι, ξοδεύουν πάνω από τις δυνατότητές τους |
| Υψηλό εισόδημα - χαμηλή δαπάνη | 35 | ~41 | ~88k | ~17 | μεγάλη αγοραστική δύναμη αλλά δεν την αξιοποιούν |
| Χαμηλό εισόδημα - χαμηλή δαπάνη | 23 | ~45 | ~26k | ~21 | συντηρητικοί, χαμηλή προτεραιότητα |
Ναι, υπάρχει ξεκάθαρα τέτοια ομάδα: 35 πελάτες, με μέσο εισόδημα ~88k$ αλλά spending score μόλις ~17. Είναι η ομάδα με το υψηλότερο εισόδημα από όλες, αλλά με το χαμηλότερο δεύτερο spending score.
Αυτή η ομάδα αξίζει ειδική στόχευση γιατί έχουν την αγοραστική δύναμη αλλά για κάποιο λόγο δεν ξοδεύουν στο εμπορικό κέντρο — πιθανόν να μην τους “μιλάνε” οι τρέχουσες προσφορές/brands, ή να προτιμούν άλλα κανάλια αγορών (πχ online, άλλα malls). Είναι η ομάδα με το μεγαλύτερο περιθώριο ανάπτυξης αν καταφέρουμε να τους πείσουμε να ξοδέψουν περισσότερο.
X3 <- df[, c("Age", "Annual.Income..k..", "Spending.Score..1.100.")]
X3_scaled <- scale(X3)
fviz_nbclust(X3_scaled, kmeans, method = "wss", k.max = 10) +
labs(title = "Elbow method - Age + Income + Spending")
fviz_nbclust(X3_scaled, kmeans, method = "silhouette", k.max = 10) +
labs(title = "Silhouette method - Age + Income + Spending")
Με 3 διαστάσεις το silhouette score πέφτει σημαντικά σε σχέση με πριν (από ~0.55 σε ~0.42), δηλαδή τα clusters δεν είναι πια τόσο “καθαρά” διαχωρισμένα. Αυτό είναι λογικό γιατί η ηλικία δεν έχει τόσο ισχυρή σχέση με το spending score όσο το εισόδημα.
Για να συγκρίνουμε με πριν, ξανατρέχουμε με k=5:
set.seed(42)
km5_3d <- kmeans(X3_scaled, centers = 5, nstart = 25)
df$cluster3d <- as.factor(km5_3d$cluster)
profile3d <- aggregate(
cbind(Age, Annual.Income..k.., Spending.Score..1.100.) ~ cluster3d,
data = df, FUN = function(x) round(mean(x), 1)
)
n_per_cluster3d <- table(df$cluster3d)
profile3d$n <- as.integer(n_per_cluster3d[as.character(profile3d$cluster3d)])
colnames(profile3d) <- c("cluster3d", "mean_age", "mean_income", "mean_spending", "n")
profile3d
## cluster3d mean_age mean_income mean_spending n
## 1 1 25.2 41.1 62.2 54
## 2 2 55.6 54.4 48.9 47
## 3 3 39.9 86.1 19.4 39
## 4 4 46.2 26.8 18.4 20
## 5 5 32.9 86.1 81.5 40
table(df$cluster, df$cluster3d)
##
## 1 2 3 4 5
## 1 2 1 0 20 0
## 2 22 0 0 0 0
## 3 0 0 0 0 39
## 4 30 45 5 0 1
## 5 0 1 34 0 0
Τι αλλάζει: Η ομάδα με υψηλό εισόδημα και υψηλή δαπάνη (η “καλύτερη” ομάδα) παραμένει σχεδόν ίδια (κοινοί πελάτες σε μεγάλο ποσοστό), δηλαδή η ηλικία δεν την επηρεάζει πολύ. Όμως η μεγάλη ομάδα του “μέσου” πελάτη (τα 81 άτομα από πριν) σπάει πλέον σε υποομάδες με βάση την ηλικία — φαίνεται μια ομάδα μεγαλύτερης ηλικίας (~55-56) με μέτριο εισόδημα/δαπάνη, ξεχωριστή από μια νεότερη ομάδα (~25) με χαμηλότερο εισόδημα αλλά αρκετά υψηλότερο spending score. Άρα η ηλικία προσθέτει πληροφορία κυρίως μέσα στην “γκρίζα ζώνη” των μέτριων πελατών, όχι στα ακραία (πλούσιοι/φτωχοί) clusters.