Εισαγωγή δεδομένων

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 έχουν εντελώς διαφορετική κλίμακα και θα κυριαρχούσε το εισόδημα στην απόσταση.

Ερώτημα 1-2: Πόσες ομάδες υπάρχουν και ποιο είναι το προφίλ τους

Δουλεύουμε πρώτα με τις 2 βασικές μεταβλητές (Income, Spending Score), όπως λέει η εκφώνηση.

X <- df[, c("Annual.Income..k..", "Spending.Score..1.100.")]
X_scaled <- scale(X)

Elbow method

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) γίνεται πολύ πιο ήπια.

Silhouette method

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, και τα δύο κριτήρια συμφωνούν.

Τελικό clustering (k=5)

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 συντηρητικοί, χαμηλή προτεραιότητα

Ερώτημα 3: Ομάδα “υψηλό εισόδημα - χαμηλές δαπάνες”

Ναι, υπάρχει ξεκάθαρα τέτοια ομάδα: 35 πελάτες, με μέσο εισόδημα ~88k$ αλλά spending score μόλις ~17. Είναι η ομάδα με το υψηλότερο εισόδημα από όλες, αλλά με το χαμηλότερο δεύτερο spending score.

Αυτή η ομάδα αξίζει ειδική στόχευση γιατί έχουν την αγοραστική δύναμη αλλά για κάποιο λόγο δεν ξοδεύουν στο εμπορικό κέντρο — πιθανόν να μην τους “μιλάνε” οι τρέχουσες προσφορές/brands, ή να προτιμούν άλλα κανάλια αγορών (πχ online, άλλα malls). Είναι η ομάδα με το μεγαλύτερο περιθώριο ανάπτυξης αν καταφέρουμε να τους πείσουμε να ξοδέψουν περισσότερο.

Ερώτημα 4: Προσθήκη ηλικίας ως 3η διάσταση

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.

Ερώτημα 5: Ποια ομάδα για premium καμπάνια

Θα στόχευα πρώτα την ομάδα “υψηλό εισόδημα - υψηλή δαπάνη” (τα ~39 άτομα, μέσο εισόδημα ~86k$, spending score ~82, σχετικά νεαρή ηλικία ~33 έτη).

Επιχείρημα: Είναι η μόνη ομάδα που συνδυάζει και τα δύο πράγματα που χρειάζεται μια premium καμπάνια για να είναι κερδοφόρα: έχουν τα χρήματα (υψηλό εισόδημα) ΚΑΙ έχουν αποδεδειγμένα ήδη την τάση/διάθεση να ξοδεύουν (υψηλό spending score, όχι απλά υποθετική δύναμη αγοράς). Η ομάδα με υψηλό εισόδημα αλλά χαμηλό spending (ερώτημα 3) είναι ενδιαφέρουσα ευκαιρία μακροπρόθεσμα, αλλά ρίσκο για μια πρώτη premium καμπάνια αφού δεν έχουν δείξει ακόμα διάθεση να ξοδέψουν — θα χρειαζόταν πρώτα ξεχωριστή στρατηγική “activation” γι’ αυτούς, όχι απευθείας premium προσφορά.