df <- read.csv("~/Aliyah/GITHUB LOMBA/perlombaan/POISSON 2025/Mall_Customers.csv")
head(df)
## CustomerID Gender Age Annual.Income..k.. Spending.Score..1.100.
## 1 1 Male 19 15 39
## 2 2 Male 21 15 81
## 3 3 Female 20 16 6
## 4 4 Female 23 16 77
## 5 5 Female 31 17 40
## 6 6 Female 22 17 76
Data yang digunakan dalam analisis ini berasal dari dataset Mall Customer Segmentation yang tersedia di platform Kaggle.
Dataset ini berisi informasi mengenai pelanggan dari sebuah pusat perbelanjaan, dengan variabel sebagai berikut:
summary(df)
## CustomerID Gender Age Annual.Income..k..
## Min. : 1.00 Length:200 Min. :18.00 Min. : 15.00
## 1st Qu.: 50.75 Class :character 1st Qu.:28.75 1st Qu.: 41.50
## Median :100.50 Mode :character Median :36.00 Median : 61.50
## Mean :100.50 Mean :38.85 Mean : 60.56
## 3rd Qu.:150.25 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
par(mfrow = c(2, 2)) # layout 2x2
hist(df$Age,
main = "Distribusi Usia Pelanggan",
xlab = "Usia",
col = "skyblue",
border = "white")
hist(df$Annual.Income..k..,
main = "Distribusi Pendapatan Tahunan",
xlab = "Pendapatan (k$)",
col = "lightgreen",
border = "white")
hist(df$Spending.Score..1.100.,
main = "Distribusi Spending Score",
xlab = "Spending Score (1–100)",
col = "lightpink",
border = "white")
plot(df$Annual.Income..k.., df$Spending.Score..1.100.,
main = "Hubungan Pendapatan vs Spending Score",
xlab = "Pendapatan (k$)",
ylab = "Spending Score (1–100)",
pch = 19, col = "orange",
cex = 0.5)
par(mfrow = c(1, 1))
data_cluster <- df %>%
mutate(
Annual_income = Annual.Income..k..,
Spending_score = Spending.Score..1.100.,
) %>%
select(Annual_income, Spending_score)
head(data_cluster)
## Annual_income Spending_score
## 1 15 39
## 2 15 81
## 3 16 6
## 4 16 77
## 5 17 40
## 6 17 76
Pada tahap ini, dilakukan pemilihan variabel yang akan digunakan
dalam proses klasterisasi pelanggan.
Dari dataset Mall Customer Segmentation, dua variabel utama
dipilih karena dianggap paling relevan dalam menggambarkan perilaku
konsumen, yaitu:
Tahap pertama analisis dilakukan dengan metode Hierarchical
Clustering. Sebelum proses klasterisasi dilakukan, data
terlebih dahulu distandarisasi agar seluruh variabel
berada pada skala yang sebanding.
Setelah itu, dibuat matriks jarak (distance matrix)
menggunakan metode Euclidean distance sebagai dasar
pengelompokan antar pengamatan.
library(RColorBrewer)
data <- data_cluster %>% select(where(is.numeric)) %>% na.omit()
data_scaled <- scale(data)
# Matriks jarak Euclidean
dist_matrix <- dist(data_scaled, method = "euclidean")#"euclidean", "maximum", "manhattan", "canberra", "binary" or "minkowski"
# Hierarchical clustering dengan metode Ward
hc_ward <- hclust(dist_matrix, method = "ward.D2") #"single", "complete", "average" (= UPGMA), "mcquitty" "median" or "centroid"
hc_ward
##
## Call:
## hclust(d = dist_matrix, method = "ward.D2")
##
## Cluster method : ward.D2
## Distance : euclidean
## Number of objects: 200
fviz_dend(hc_ward, k = 5, rect = TRUE, palette = "jco",
main = "Dendrogram Hierarchical Clustering (Ward.D2)")
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## ℹ The deprecated feature was likely used in the factoextra package.
## Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the factoextra package.
## Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
## of ggplot2 3.3.4.
## ℹ The deprecated feature was likely used in the factoextra package.
## Please report the issue at <https://github.com/kassambara/factoextra/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Gambar di atas menunjukkan dendrogram hasil Hierarchical Clustering dengan metode pengelompokan Ward.D2. Sumbu vertikal (Height) merepresentasikan jarak atau tingkat perbedaan antar kelompok yang digabungkan. Dari hasil dendrogram, terlihat bahwa data pelanggan dapat dibagi menjadi sekitar lima klaster utama.
cluster_hc <- cutree(hc_ward, k = 5)
table(cluster_hc)
## cluster_hc
## 1 2 3 4 5
## 23 21 85 39 32
Distribusi jumlah anggota pada masing-masing klaster adalah sebagai berikut:
Dari hasil tersebut, klaster 3 memiliki jumlah pelanggan terbanyak, menunjukkan bahwa sebagian besar pelanggan memiliki karakteristik yang mirip dengan kelompok ini. Sementara itu, klaster 2 merupakan yang paling sedikit, menandakan segmen pelanggan yang lebih spesifik atau unik.
eval_cluster <- function(data_scaled, cluster, dist_matrix) {
sil <- mean(silhouette(cluster, dist_matrix)[, 3])
ch <- intCriteria(traj = as.matrix(data_scaled),
part = cluster, crit = "Calinski_Harabasz")$calinski_harabasz
dunn <- intCriteria(traj = as.matrix(data_scaled),
part = cluster, crit = "Dunn")$dunn
return(data.frame(Silhouette = sil,
Calinski_Harabasz = ch,
Dunn = dunn))
}
eval_cluster(data_scaled, cluster_hc, dist_matrix)
## Silhouette Calinski_Harabasz Dunn
## 1 0.5538089 244.4103 0.09029561
Nilai Silhouette di atas 0.5 menunjukkan klaster yang cukup baik, nilai Calinski–Harabasz yang tinggi menandakan klaster terpisah dengan baik, sedangkan Dunn Index menunjukkan pemisahan antar klaster yang masih tergolong moderat.
data_clustered <- data.frame(data, cluster = factor(cluster_hc))
cluster_summary <- data_clustered %>%
group_by(cluster) %>%
summarise(across(where(is.numeric), mean, .names = "{.col}"))
cluster_summary
## # A tibble: 5 × 3
## cluster Annual_income Spending_score
## <fct> <dbl> <dbl>
## 1 1 26.3 20.9
## 2 2 25.1 80.0
## 3 3 55.8 49.1
## 4 4 86.5 82.1
## 5 5 89.4 15.6
heat_data <- cluster_summary %>%
pivot_longer(-cluster, names_to = "Variabel", values_to = "Rata2")
ggplot(heat_data, aes(x = Variabel, y = cluster, fill = Rata2)) +
geom_tile(color = "white") +
scale_fill_gradientn(colors = c("#56B1F7", "white", "#FF6B6B")) +
labs(title = "Heatmap Karakteristik Cluster",
x = "Variabel", y = "Cluster") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplot(heat_data, aes(x = Variabel, y = Rata2, fill = cluster)) +
geom_bar(stat = "identity", position = "dodge") +
scale_fill_brewer(palette = "Set2") +
labs(title = "Bar Plot Karakteristik Cluster",
x = "Variabel", y = "Rata-rata Nilai") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Melalui hasil hierarchical clustering, Terbentuk 5 klaster dengan karakteristik sebagai berikut:
Secara umum, kombinasi ini menunjukkan adanya lima segmen pelanggan dengan perilaku belanja yang berbeda berdasarkan tingkat pendapatan dan pengeluarannya.
Selanjutnya dilakukan proses klasterisasi menggunakan metode non-hierarchical, yaitu K-Means dan K-Medoids (PAM). Metode ini dipilih karena membutuhkan penentuan jumlah klaster (k) di awal proses. Dalam analisis ini, kedua metode diterapkan untuk mengelompokkan pelanggan berdasarkan variabel Annual Income dan Spending Score, sehingga tiap klaster mencerminkan perilaku belanja yang berbeda.
fviz_nbclust(data_scaled, kmeans, method = "wss") +
labs(title = "Elbow Method")
fviz_nbclust(data_scaled, kmeans, method = "silhouette") +
labs(title = "Silhouette Method")
Berdasarkan hasil Silhouette Method, diperoleh bahwa nilai k = 6 memberikan hasil klasterisasi yang paling optimal.
set.seed(123)
kmeans_result <- kmeans(data_scaled, centers = 6, nstart = 25)
kmeans_result
## K-means clustering with 6 clusters of sizes 22, 23, 10, 81, 29, 35
##
## Cluster means:
## Annual_income Spending_score
## 1 -1.3262173 1.12934389
## 2 -1.3042458 -1.13411939
## 3 1.8709508 1.23143545
## 4 -0.2004097 -0.02638995
## 5 0.6850149 1.23811207
## 6 1.0523622 -1.28122394
##
## Clustering vector:
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1
## 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
## 2 1 2 4 2 1 4 4 4 4 4 4 4 4 4 4 4 4 4 4
## 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
## 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
## 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
## 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
## 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
## 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4
## 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
## 4 4 4 5 6 5 4 5 6 5 6 5 4 5 6 5 6 5 6 5
## 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
## 6 5 4 5 6 5 6 5 6 5 6 5 6 5 6 5 6 5 6 5
## 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
## 6 5 6 5 6 5 6 5 6 5 6 5 6 5 6 5 6 5 6 5
## 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
## 6 3 6 3 6 3 6 3 6 3 6 3 6 3 6 3 6 3 6 3
##
## Within cluster sum of squares by cluster:
## [1] 5.217630 7.577407 3.681858 14.485632 5.514889 18.304646
## (between_SS / total_SS = 86.2 %)
##
## Available components:
##
## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
## [6] "betweenss" "size" "iter" "ifault"
table(kmeans_result$cluster)
##
## 1 2 3 4 5 6
## 22 23 10 81 29 35
Distribusi jumlah anggota pada masing-masing klaster hasil K-Means adalah sebagai berikut:
Dari hasil tersebut, klaster 4 memiliki jumlah
pelanggan terbanyak, menunjukkan kelompok dengan karakteristik yang
paling umum di antara seluruh data.
Sebaliknya, klaster 3 memiliki anggota paling sedikit,
menandakan segmen pelanggan yang lebih spesifik atau unik.
head(data.frame(Observasi = 1:length(kmeans_result$cluster),
Cluster = kmeans_result$cluster),10)
## Observasi Cluster
## 1 1 2
## 2 2 1
## 3 3 2
## 4 4 1
## 5 5 2
## 6 6 1
## 7 7 2
## 8 8 1
## 9 9 2
## 10 10 1
cluster_km <- kmeans_result$cluster
dist_matrix <- dist(data_scaled, method = "euclidean")
sil <- mean(silhouette(cluster_km, dist_matrix)[, 3])
ch <- intCriteria(traj = as.matrix(data_scaled),
part = cluster_km,
crit = "Calinski_Harabasz")$calinski_harabasz
dunn <- intCriteria(traj = as.matrix(data_scaled),
part = cluster_km,
crit = "Dunn")$dunn
data.frame(Silhouette = sil,
Calinski_Harabasz = ch,
Dunn = dunn)
## Silhouette Calinski_Harabasz Dunn
## 1 0.5398801 243.0879 0.05989224
Nilai Silhouette mendekati 0.54 menandakan bahwa pemisahan antar klaster cukup baik. Nilai Calinski–Harabasz yang tinggi menunjukkan klaster yang terpisah dengan jelas. Sementara Dunn Index bernilai 0.0599, menunjukkan jarak antar klaster cukup kecil namun masih dapat diterima.
data_clustered <- data.frame(data, cluster = factor(cluster_km))
cluster_summary <- data_clustered %>%
group_by(cluster) %>%
summarise(across(where(is.numeric), mean, .names = "{.col}"))
cluster_summary
## # A tibble: 6 × 3
## cluster Annual_income Spending_score
## <fct> <dbl> <dbl>
## 1 1 25.7 79.4
## 2 2 26.3 20.9
## 3 3 110. 82
## 4 4 55.3 49.5
## 5 5 78.6 82.2
## 6 6 88.2 17.1
Berdasarkan hasil rata-rata tiap klaster, diperoleh karakteristik sebagai berikut:
Berikut adalah beberapa visualisasi karakteristik dari hasil K-Means Clustering:
fviz_cluster(kmeans_result, data = data_scaled,
palette = "jco", geom = "point",
main = "Visualisasi Hasil K-Means Clustering (3 Cluster)")
heat_data <- cluster_summary %>%
pivot_longer(-cluster, names_to = "Variabel", values_to = "Rata2")
heat_data
## # A tibble: 12 × 3
## cluster Variabel Rata2
## <fct> <chr> <dbl>
## 1 1 Annual_income 25.7
## 2 1 Spending_score 79.4
## 3 2 Annual_income 26.3
## 4 2 Spending_score 20.9
## 5 3 Annual_income 110.
## 6 3 Spending_score 82
## 7 4 Annual_income 55.3
## 8 4 Spending_score 49.5
## 9 5 Annual_income 78.6
## 10 5 Spending_score 82.2
## 11 6 Annual_income 88.2
## 12 6 Spending_score 17.1
ggplot(heat_data, aes(x = Variabel, y = cluster, fill = Rata2)) +
geom_tile(color = "white") +
scale_fill_gradientn(colors = c("#56B1F7", "white", "#FF6B6B")) +
labs(title = "Heatmap Karakteristik Cluster",
x = "Variabel", y = "Cluster") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplot(heat_data, aes(x = Variabel, y = Rata2, fill = cluster)) +
geom_bar(stat = "identity", position = "dodge") +
scale_fill_brewer(palette = "Set2") +
labs(title = "Heatmap Karakteristik Cluster",
x = "Variabel", y = "Rata-rata Nilai") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# Membuat matriks jarak (bisa pilih salah satu: Euclidean, Manhattan, Canberra)
dist_euc <- dist(data_scaled, method = "euclidean")
# Klasterisasi dengan metode K-Medoids (PAM)
set.seed(123)
pam_euc <- pam(dist_euc, k = 6)
# Melihat jumlah anggota tiap klaster
table(pam_euc$clustering)
##
## 1 2 3 4 5 6
## 21 21 38 49 39 32
Distribusi jumlah anggota pada masing-masing klaster hasil metode K-Medoids adalah sebagai berikut:
Dari hasil tersebut, klaster 4 memiliki jumlah
pelanggan terbanyak, menunjukkan kelompok dengan karakteristik yang
paling umum.
Sedangkan klaster 1 dan klaster 2
memiliki jumlah anggota paling sedikit, menggambarkan segmen pelanggan
yang lebih spesifik atau berperilaku unik.
# Evaluasi hasil klasterisasi
silhouette_score <- mean(silhouette(pam_euc$clustering, dist_euc)[, 3])
ch_score <- intCriteria(traj = as.matrix(data_scaled),
part = pam_euc$clustering,
crit = "Calinski_Harabasz")$calinski_harabasz
dunn_score <- intCriteria(traj = as.matrix(data_scaled),
part = pam_euc$clustering,
crit = "Dunn")$dunn
# Menampilkan hasil evaluasi
data.frame(
Silhouette = silhouette_score,
Calinski_Harabasz = ch_score,
Dunn = dunn_score
)
## Silhouette Calinski_Harabasz Dunn
## 1 0.4557916 231.72 0.04380516
Nilai Silhouette sebesar 0.46 menandakan bahwa pemisahan antar klaster tergolong cukup baik, meskipun masih bisa ditingkatkan. Nilai Calinski–Harabasz yang cukup tinggi mengindikasikan klaster yang relatif terpisah. Sedangkan Dunn Index menunjukkan jarak antar klaster yang cukup kecil, menandakan adanya beberapa kemiripan antar kelompok pelanggan.
# Membuat data hasil klasterisasi K-Medoids
data_clustered_pam <- data.frame(data_cluster, cluster = factor(pam_euc$clustering))
# Menghitung rata-rata tiap variabel untuk setiap klaster
cluster_summary_pam <- data_clustered_pam %>%
group_by(cluster) %>%
summarise(across(where(is.numeric), mean, .names = "{.col}"))
cluster_summary_pam
## # A tibble: 6 × 3
## cluster Annual_income Spending_score
## <fct> <dbl> <dbl>
## 1 1 25.1 19.5
## 2 2 25.1 80.0
## 3 3 46.0 51.3
## 4 4 62.7 46.9
## 5 5 86.5 82.1
## 6 6 89.4 15.6
Berdasarkan hasil rata-rata tiap klaster pada metode K-Medoids, diperoleh karakteristik sebagai berikut:
Berikut adalah beberapa visualisasi karakteristik dari hasil K-Medoids Clustering:
# Visualisasi hasil K-Medoids dengan data scaled
fviz_cluster(list(data = data_scaled, cluster = pam_euc$clustering),
palette = "jco", geom = "point",
main = "Visualisasi Hasil K-Medoids Clustering (6 Cluster)")
heat_data_pam <- cluster_summary_pam %>%
pivot_longer(-cluster, names_to = "Variabel", values_to = "Rata2")
ggplot(heat_data_pam, aes(x = Variabel, y = cluster, fill = Rata2)) +
geom_tile(color = "white") +
scale_fill_gradientn(colors = c("#56B1F7", "white", "#FF6B6B")) +
labs(title = "Heatmap Karakteristik Cluster (K-Medoids)",
x = "Variabel", y = "Cluster") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplot(heat_data_pam, aes(x = Variabel, y = Rata2, fill = cluster)) +
geom_bar(stat = "identity", position = "dodge") +
scale_fill_brewer(palette = "Set2") +
labs(title = "Perbandingan Karakteristik Tiap Cluster (K-Medoids)",
x = "Variabel", y = "Rata-rata Nilai") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))