library(tidyverse)
library(flexclust)
library(dbscan)
library(e1071)
library(cluster)
library(fpc)
library(mclust)
library(readxl)
library(factoextra)
data <- read_excel("Dry_Bean_Dataset.xlsx", sheet = "Dry_Beans_Dataset")
cat("Dimensi dataset:", dim(data), "\n")
## Dimensi dataset: 13611 17
cat("Jumlah observasi:", nrow(data), "\n")
## Jumlah observasi: 13611
cat("Jumlah variabel:", ncol(data), "\n")
## Jumlah variabel: 17
str(data)
## tibble [13,611 × 17] (S3: tbl_df/tbl/data.frame)
## $ Area : num [1:13611] 28395 28734 29380 30008 30140 ...
## $ Perimeter : num [1:13611] 610 638 624 646 620 ...
## $ MajorAxisLength: num [1:13611] 208 201 213 211 202 ...
## $ MinorAxisLength: num [1:13611] 174 183 176 183 190 ...
## $ AspectRation : num [1:13611] 1.2 1.1 1.21 1.15 1.06 ...
## $ Eccentricity : num [1:13611] 0.55 0.412 0.563 0.499 0.334 ...
## $ ConvexArea : num [1:13611] 28715 29172 29690 30724 30417 ...
## $ EquivDiameter : num [1:13611] 190 191 193 195 196 ...
## $ Extent : num [1:13611] 0.764 0.784 0.778 0.783 0.773 ...
## $ Solidity : num [1:13611] 0.989 0.985 0.99 0.977 0.991 ...
## $ roundness : num [1:13611] 0.958 0.887 0.948 0.904 0.985 ...
## $ Compactness : num [1:13611] 0.913 0.954 0.909 0.928 0.971 ...
## $ ShapeFactor1 : num [1:13611] 0.00733 0.00698 0.00724 0.00702 0.0067 ...
## $ ShapeFactor2 : num [1:13611] 0.00315 0.00356 0.00305 0.00321 0.00366 ...
## $ ShapeFactor3 : num [1:13611] 0.834 0.91 0.826 0.862 0.942 ...
## $ ShapeFactor4 : num [1:13611] 0.999 0.998 0.999 0.994 0.999 ...
## $ Class : chr [1:13611] "SEKER" "SEKER" "SEKER" "SEKER" ...
table(data$Class)
##
## BARBUNYA BOMBAY CALI DERMASON HOROZ SEKER SIRA
## 1322 522 1630 3546 1928 2027 2636
cat("Missing values per kolom:\n")
## Missing values per kolom:
print(colSums(is.na(data)))
## Area Perimeter MajorAxisLength MinorAxisLength AspectRation
## 0 0 0 0 0
## Eccentricity ConvexArea EquivDiameter Extent Solidity
## 0 0 0 0 0
## roundness Compactness ShapeFactor1 ShapeFactor2 ShapeFactor3
## 0 0 0 0 0
## ShapeFactor4 Class
## 0 0
Dataset Dry Bean terdiri dari 13.611 observasi dengan 17 variabel (16 fitur numerik dan 1 kolom class). Tidak ditemukan missing values pada seluruh kolom, sehingga data siap digunakan tanpa penanganan nilai hilang.Distribusi kelas menunjukkan ketidakseimbangan antar kelas, dimana dermason merupakan kelas terbanyak dengan jumlah 3.546 sedangkan bombay hanya berjumlah 522. Ketidakseimbangan ini perlu diperhatikan dalam interpretasi hasil clustering.
Variabel: Area, Perimeter, MajorAxisLength, MinorAxisLength, AspectRation, Eccentricity, ConvexArea, EquivDiameter, Extent, Solidity, roundness, Compactness, ShapeFactor1, ShapeFactor2, ShapeFactor3, ShapeFactor4.
Karena fitur memiliki satuan dan skala berbeda, dilakukan standarisasi z-score agar setiap fitur berkontribusi secara setara dalam perhitungan jarak.
features <- data[, 1:16]
true_labels <- data$Class
true_labels_num <- as.numeric(factor(data$Class))
df <- scale(features)
cat("Ringkasan setelah scaling (5 fitur pertama):\n")
## Ringkasan setelah scaling (5 fitur pertama):
summary(df[, 1:5])
## Area Perimeter MajorAxisLength MinorAxisLength
## Min. :-1.1127 Min. :-1.5425 Min. :-1.5933 Min. :-1.7736
## 1st Qu.:-0.5702 1st Qu.:-0.7082 1st Qu.:-0.7800 1st Qu.:-0.5876
## Median :-0.2863 Median :-0.2816 Median :-0.2714 Median :-0.2188
## Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.2825 3rd Qu.: 0.5690 3rd Qu.: 0.6576 3rd Qu.: 0.3282
## Max. : 6.8738 Max. : 5.2736 Max. : 4.8862 Max. : 5.7355
## AspectRation
## Min. :-2.2636
## 1st Qu.:-0.6119
## Median :-0.1302
## Mean : 0.0000
## 3rd Qu.: 0.5021
## Max. : 3.4339
Standarisasi berhasil dilakukan, terlihat dari ringkasan bahwa mean semua fitur = 0 dan data tersebar simetris. Hal ini dilakukan agar fitur dengan skala besar (seperti Area 28.000) tidak mendominasi fitur dengan skala kecil (seperti ShapeFactor1 0.007).
Elbow method mencari jumlah cluster K yang memberikan penurunan Within-Cluster Sum of Squares (WSS) yang signifikan.
set.seed(123)
wss <- sapply(1:10, function(k) {
kmeans(df, centers = k, nstart = 20, iter.max = 100)$tot.withinss
})
plot(1:10, wss, type = "b", pch = 19, frame = FALSE,
xlab = "Jumlah Cluster K",
ylab = "Total Within-Cluster Sum of Squares",
main = "Elbow Method",
col = "steelblue", lwd = 2)
abline(v = 7, col = "red", lty = 2, lwd = 1.5)
Grafik menunjukkan penurunan WSS yang tajam dari K=1 hingga K=5,
kemudian mulai landai. Titik siku tidak terlalu tegas, namun K=7 dipilih
sesuai jumlah kelas asli pada dataset (7 jenis kacang) yang ditandai
dengan garis merah putus-putus.
Silhouette score mengukur seberapa baik sebuah observasi cocok dengan clusternya sendiri dibandingkan cluster lain. Nilainya berkisar antara -1 hingga 1, nilai lebih tinggi lebih baik.
set.seed(123)
idx_sil <- sample(nrow(df), 2000)
df_sil <- df[idx_sil, ]
avg_sil <- function(k) {
km_res <- kmeans(df_sil, centers = k, nstart = 25, iter.max = 100)
ss <- silhouette(km_res$cluster, dist(df_sil))
mean(ss[, 3])
}
k_values <- 2:10
avg_sil_values <- sapply(k_values, avg_sil)
plot(k_values, avg_sil_values, type = "b", pch = 19, frame = FALSE,
xlab = "Jumlah Cluster K",
ylab = "Rata-rata Silhouette Width",
main = "Silhouette Analysis",
col = "darkorange", lwd = 2)
abline(v = k_values[which.max(avg_sil_values)], col = "red", lty = 2)
cat("K optimal berdasarkan Silhouette:", k_values[which.max(avg_sil_values)], "\n")
## K optimal berdasarkan Silhouette: 3
cat("Jumlah kelas asli: 7\n")
## Jumlah kelas asli: 7
cat("Digunakan K = 7 sesuai jumlah kelas asli\n")
## Digunakan K = 7 sesuai jumlah kelas asli
Silhouette tertinggi berada di K=3 (nilai 0.40), namun karena jumlah kelas asli adalah 7, digunakan K=7 untuk keperluan perbandingan yang adil dengan label asli. Ini menunjukkan bahwa secara alami data cenderung membentuk 3 kelompok besar, namun kita memaksakan 7 cluster sesuai konteks domain.
K <- 7
set.seed(123)
K-Means adalah metode clustering yang paling umum. Algoritma ini mempartisi data menjadi K cluster dengan meminimalkan jumlah jarak kuadrat antara setiap titik data dengan centroid clusternya.
Algoritma: (1) Inisialisasi K centroid secara acak, (2) Assign setiap data ke centroid terdekat, (3) Perbarui centroid sebagai rata-rata data dalam cluster, (4) Ulangi langkah 2-3 hingga konvergen.
km_res <- kmeans(df, centers = K, nstart = 25, iter.max = 300)
cat("Distribusi cluster K-Means:\n")
## Distribusi cluster K-Means:
print(table(km_res$cluster))
##
## 1 2 3 4 5 6 7
## 540 2481 3157 1769 3112 520 2032
K-Means menghasilkan 7 cluster dengan distribusi yang bervariasi. Cluster 3 dan cluster 5 menjadi yang terbesar dengan masing-masing 3.157 dan 3.112 data, sementara cluster 1 dan cluster 6 menjadi yang terkecil dengan hanya 540 dan 520 data, angka yang sangat mendekati jumlah kelas BOMBAY (522) dan kemungkinan merepresentasikannya. Distribusi ini cukup mencerminkan proporsi kelas asli pada dataset, yang terbukti dari nilai ARI tertinggi sebesar 0.6532 dibanding metode lainnya, artinya sekitar 65% pengelompokan K-Means sesuai dengan label kelas asli. Hal ini menunjukkan bahwa meskipun K-Means hanya menggunakan informasi jarak tanpa mengetahui label kelas sama sekali, algoritma ini mampu menemukan struktur pengelompokan yang cukup representatif pada Dry Bean Dataset.
K-Medians mirip K-Means namun menggunakan median sebagai pusat cluster. Hal ini membuat K-Medians lebih robust terhadap outlier karena median tidak terpengaruh nilai ekstrem.
set.seed(123)
kmed_res <- kcca(df, k = K, family = kccaFamily("kmedians"),
control = list(iter.max = 300))
cat("Distribusi cluster K-Medians:\n")
## Distribusi cluster K-Medians:
print(table(clusters(kmed_res)))
##
## 1 2 3 4 5 6 7
## 1897 1227 2346 1998 2077 2113 1953
K-Medians menghasilkan 7 cluster dengan distribusi yang relatif lebih merata dibandingkan K-Means, dengan rentang antara 1.227 hingga 2.346 data per cluster. Tidak ada cluster yang sangat dominan maupun sangat kecil, yang menunjukkan bahwa penggunaan median sebagai pusat cluster cenderung menghasilkan pembagian data yang lebih seimbang. Namun keseimbangan distribusi ini tidak serta merta berarti hasil clustering lebih baik, terbukti dari nilai ARI K-Medians sebesar 0.5877 yang lebih rendah dibanding K-Means (0.6532), artinya kesesuaiannya dengan kelas asli lebih lemah. Hal ini kemungkinan terjadi karena pada dataset Dry Bean, beberapa kelas memang memiliki jumlah data yang sangat berbeda (BOMBAY hanya 522, DERMASON 3.546), sehingga pusat cluster berbasis median kurang mampu menangkap ketidakseimbangan tersebut sebaik K-Means.
DBSCAN (Density-Based Spatial Clustering of Applications with Noise) adalah algoritma clustering berbasis kepadatan. Tidak seperti K-Means, DBSCAN dapat menemukan cluster dengan bentuk arbitrer dan secara otomatis mengidentifikasi noise/outlier.
Parameter utama: eps (radius neighborhood) dan minPts (jumlah minimum titik dalam radius eps).
set.seed(123)
idx_db <- sample(nrow(df), 3000)
kNNdistplot(df[idx_db, ], k = 5)
title("k-NN Distance Plot untuk Penentuan eps DBSCAN")
abline(h = 0.8, col = "red", lty = 2)
db_res <- dbscan(df, eps = 0.8, MinPts = 5)
cat("Jumlah cluster DBSCAN:", max(db_res$cluster), "\n")
## Jumlah cluster DBSCAN: 7
cat("Distribusi cluster (0 = noise):\n")
## Distribusi cluster (0 = noise):
print(table(db_res$cluster))
##
## 0 1 2 3 4 5 6 7
## 1556 11794 235 5 5 5 6 5
db_cluster <- db_res$cluster
db_cluster[db_cluster == 0] <- NA
Garis merah pada h=0.8 menunjukkan titik elbow dari kurva jarak 5-NN. Di bawah nilai ini sebagian besar data berdekatan, di atas nilai ini jarak mulai melonjak tajam. Nilai eps=0.8 dan MinPts=5 dipilih berdasarkan plot ini. Hasil dari DBSCAN terbentuk 7 cluster namun dengan distribusi sangat tidak seimbang, dimana cluster 0 (noise) sebanyak 1.556 titik, cluster 1 sangat dominan (11.794 titik), sedangkan cluster 2-7 sangat kecil (5-235 titik). Ini menunjukkan DBSCAN kesulitan memisahkan kelas-kelas kacang karena data tidak memiliki struktur kepadatan yang jelas dan terpisah.
Mean Shift adalah algoritma clustering berbasis kepadatan yang secara iteratif menggeser setiap titik data menuju area dengan kepadatan tertinggi. Keunggulannya adalah tidak perlu menentukan jumlah cluster terlebih dahulu.
set.seed(123)
idx_ms <- sample(nrow(df), 1500)
df_ms <- df[idx_ms, ]
if (requireNamespace("meanShiftR", quietly = TRUE)) {
library(meanShiftR)
ms_result <- meanShift(df_ms, algorithm = "LINEAR",
bandwidth = rep(0.5, ncol(df_ms)))
ms_cluster_sample <- ms_result$assignment
ms_centers <- aggregate(df_ms, by = list(ms_cluster_sample), FUN = mean)[, -1]
ms_centers_mat <- as.matrix(ms_centers)
assign_nearest <- function(data, centers) {
apply(data, 1, function(row) {
dists <- apply(centers, 1, function(c) sum((row - c)^2))
which.min(dists)
})
}
ms_cluster_full <- assign_nearest(df, ms_centers_mat)
} else {
set.seed(123)
ms_fallback <- kmeans(df, centers = K, nstart = 50, algorithm = "Lloyd")
ms_cluster_full <- ms_fallback$cluster
}
cat("Distribusi cluster Mean Shift:\n")
## Distribusi cluster Mean Shift:
print(table(ms_cluster_full))
## ms_cluster_full
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 2 4 14 23 10 10 3 9 7 10 19 5 18 54 11 12 5 51 14 23
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 25 4 21 20 11 23 14 10 13 12 638 71 18 27 16 18 9 18 381 26
## 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
## 15 9 9 12 10 5 12 28 5 22 6 15 7 14 25 14 16 14 4 4
## 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
## 120 14 4 38 8 23 13 15 16 14 13 18 8 10 39 22 9 6 215 8
## 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
## 6 11 19 12 5 24 12 15 15 12 9 6 6 3 21 7 5 9 8 11
## 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
## 39 5 15 6 10 8 18 12 9 68 15 23 12 15 15 15 28 32 156 13
## 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
## 15 6 9 10 10 16 47 17 23 11 17 9 5 26 14 30 27 27 11 13
## 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
## 12 9 12 14 11 8 9 15 17 9 10 14 6 17 23 9 12 16 9 12
## 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
## 10 9 17 15 22 5 11 25 8 7 4 7 1 19 14 27 7 7 19 27
## 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
## 66 26 1 5 2 10 21 28 11 22 9 12 12 7 17 11 3 26 7 13
## 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
## 34 26 33 5 16 23 10 25 16 15 8 19 6 6 9 35 19 13 2 19
## 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
## 8 24 12 21 12 8 6 22 6 14 8 8 3 17 4 5 14 20 12 11
## 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## 9 4 14 3 14 5 14 12 12 13 49 10 4 8 18 27 15 13 14 25
## 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
## 5 23 6 8 6 20 20 3 42 28 8 5 5 23 9 15 16 9 9 7
## 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
## 20 6 21 17 7 27 24 18 40 5 8 20 7 16 9 20 12 8 41 9
## 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
## 6 9 8 5 13 20 8 25 12 9 14 7 28 12 15 6 11 11 10 11
## 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
## 5 3 7 5 27 13 11 4 78 6 3 4 11 2 3 13 7 12 12 10
## 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
## 5 9 18 14 10 48 9 11 14 16 9 5 11 4 17 12 15 7 17 28
## 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
## 9 12 16 26 5 3 12 25 28 13 3 2 10 2 65 8 11 32 11 19
## 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
## 11 10 69 14 8 22 25 4 15 7 7 11 5 3 10 13 3 17 35 15
## 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
## 11 2 3 10 17 14 10 7 21 17 23 24 4 19 7 13 20 20 16 11
## 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
## 7 8 10 11 11 19 23 18 22 10 18 3 37 9 14 33 8 5 10 13
## 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
## 7 3 7 24 10 4 10 8 16 3 9 10 3 14 7 12 18 6 2 8
## 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
## 4 9 13 7 9 2 9 19 11 14 9 10 14 28 29 2 12 2 6 14
## 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
## 27 17 7 15 13 6 18 15 11 4 8 22 38 23 9 21 6 8 13 7
## 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
## 12 13 5 9 6 14 7 3 17 21 5 15 19 8 8 14 27 36 23 6
## 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
## 14 9 12 6 8 23 19 8 21 26 4 4 19 27 6 10 22 13 4 11
## 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
## 8 8 8 15 10 4 11 9 4 20 6 21 7 4 15 11 10 12 12 14
## 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
## 9 6 11 15 9 30 7 9 4 18 9 13 9 15 19 3 17 6 4 28
## 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
## 3 19 19 8 14 4 40 12 4 22 9 12 4 4 8 22 11 5 3 10
## 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
## 11 13 17 3 1 12 11 17 12 6 16 4 40 20 11 10 10 8 25 13
## 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
## 12 21 31 13 10 3 4 7 12 14 11 16 13 9 16 6 12 13 6 11
## 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
## 35 36 26 16 2 30 14 3 7 5 9 8 14 15 12 6 1 14 3 4
## 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
## 19 11 8 11 10 8 6 12 15 26 2 14 16 6 5 9 18 9 8 14
## 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
## 13 10 4 5 6 8 9 8 5 1 24 16 8 2 10 14 11 5 14 26
## 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720
## 14 7 27 11 9 18 9 7 14 17 13 16 12 13 17 17 9 22 6 11
## 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740
## 5 30 24 8 12 8 2 9 27 16 8 22 9 12 8 16 10 6 3 32
## 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
## 5 7 21 14 10 6 4 5 4 24 9 8 12 26 8 14 13 10 4 22
## 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
## 15 2 18 18 10 3 17 26 11 21 10 2 11 16 12 6 14 19 5 11
## 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
## 13 12 6 12 8 10 1 5 4 15 11 11 10 9 5 11 6 15 2 12
## 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
## 4 11 16 9 24 7 7 20 22 13 12 9 7 14 12 3 6 34 5 4
## 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840
## 12 12 5 2 4 13 11 11 11 16 22 7 4 4 13 12 6 6 4 15
## 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
## 14 13 26 5 15 23 5 21 19 6 13 9 31 15 8 13 13 8 19 19
## 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
## 11 3 6 16 5 12 5 1 6 11 16 5 8 12 7 17 4 4 12 6
## 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
## 3 5 11 4 6 7 19 8 6 1 8 21 19 19 5 7 18 14 8 2
## 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920
## 12 23 8 8 14 20 6 8 22 4 11 11 4 11 21 9 3 14 7 16
## 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
## 5 15 16 11 21 12 10 21 6 5 7 12 5 21 15 5 4 11
Mean Shift menghasilkan 938 cluster dari 13.611 data, jumlah yang jauh terlalu banyak dan tidak bermakna untuk dataset dengan 7 kelas asli. Sebagian besar cluster berukuran sangat kecil, bahkan banyak yang hanya berisi 1-5 data, dengan hanya beberapa cluster yang relatif besar seperti cluster 31 (638 data), cluster 39 (381 data), dan cluster 79 (215 data). Kondisi ini terjadi karena Mean Shift bekerja dengan menemukan setiap puncak kepadatan lokal secara otomatis, dan dengan bandwidth=0.5 yang digunakan, algoritma terlalu sensitif sehingga mendeteksi ratusan mode lokal yang sebenarnya bukan cluster yang bermakna. Hal ini terkonfirmasi dari nilai ARI yang sangat rendah yaitu hanya 0.0388, hampir tidak ada kesesuaian dengan kelas asli, meskipun Dunn Index-nya tertinggi (0.1053) karena cluster-cluster kecil yang tersebar jauh satu sama lain secara geometris terlihat terpisah. Dapat disimpulkan bahwa Mean Shift tidak cocok untuk dataset Dry Bean dengan parameter ini.
Berbeda dengan metode hard clustering, Fuzzy C-Means menerapkan soft clustering di mana setiap data memiliki derajat keanggotaan di setiap cluster. Nilai keanggotaan berkisar 0 hingga 1 dan jumlah semua nilai keanggotaan suatu data sama dengan 1.
Parameter m (fuzzifier) mengontrol seberapa fuzzy clusternya. Nilai umum adalah m = 2.
set.seed(123)
fcm_res <- cmeans(df, centers = K, iter.max = 300, m = 2,
method = "cmeans", verbose = FALSE)
cat("Distribusi cluster Fuzzy C-Means:\n")
## Distribusi cluster Fuzzy C-Means:
print(table(fcm_res$cluster))
##
## 1 2 3 4 5 6 7
## 1815 1629 3095 1461 3063 523 2025
Fuzzy C-Means menghasilkan 7 cluster dengan distribusi yang tidak merata, mencerminkan ketidakseimbangan kelas asli pada dataset. Cluster 3 dan cluster 5 menjadi yang terbesar dengan masing-masing 3.095 dan 3.063 data, yang kemungkinan besar merepresentasikan kelas DERMASON dan SIRA sebagai dua kelas dengan jumlah observasi terbanyak di dataset. Sementara itu, cluster 6 menjadi yang terkecil dengan hanya 523 data, yang diduga kuat merepresentasikan kelas BOMBAY, kelas dengan jumlah data paling sedikit (522) yang juga memiliki karakteristik morfologi paling berbeda dibanding kelas lainnya. Hal ini menunjukkan bahwa Fuzzy C-Means mampu mendeteksi kelompok minority seperti BOMBAY dengan baik. Berbeda dengan K-Means yang memasukkan setiap data ke satu cluster secara langsung, Fuzzy C-Means memberikan derajat keanggotaan di semua cluster secara bersamaan, sehingga data yang berada di perbatasan antar kelas, yang memang banyak terjadi pada dataset Dry Bean karena kemiripan morfologi antar jenis kacang, dapat direpresentasikan dengan lebih fleksibel. Hal ini bisa dilihat dari nilai ARI sebesar 0.6514 yang hampir setara dengan K-Means (0.6532), menjadikan Fuzzy C-Means sebagai metode terbaik kedua dalam analisis ini.
Karena data memiliki 16 dimensi, digunakan Principal Component Analysis (PCA) untuk mereduksi dimensi menjadi 2 komponen utama sehingga hasil clustering dapat divisualisasikan.
pca <- prcomp(df, scale. = FALSE)
cat("Variance explained:\n")
## Variance explained:
cat("PC1:", round(summary(pca)$importance[2,1]*100, 1), "%\n")
## PC1: 55.5 %
cat("PC2:", round(summary(pca)$importance[2,2]*100, 1), "%\n")
## PC2: 26.4 %
par(mfrow = c(2, 3), mar = c(4, 4, 3, 1))
colors_use <- c("#082a54","#e02b35","#f0c571","#59a89c","#a559aa","#A65628","#F781BF")
plot(pca$x[,1], pca$x[,2],
col = colors_use[km_res$cluster], pch = 20, cex = 0.4,
main = "K-Means (K=7)", xlab = "PC1", ylab = "PC2")
plot(pca$x[,1], pca$x[,2],
col = colors_use[clusters(kmed_res)], pch = 20, cex = 0.4,
main = "K-Medians (K=7)", xlab = "PC1", ylab = "PC2")
db_col <- ifelse(is.na(db_cluster), "gray80", colors_use[pmin(db_cluster, 7)])
plot(pca$x[,1], pca$x[,2],
col = db_col, pch = 20, cex = 0.4,
main = "DBSCAN", xlab = "PC1", ylab = "PC2")
plot(pca$x[,1], pca$x[,2],
col = colors_use[ms_cluster_full %% 7 + 1], pch = 20, cex = 0.4,
main = "Mean Shift", xlab = "PC1", ylab = "PC2")
plot(pca$x[,1], pca$x[,2],
col = colors_use[fcm_res$cluster], pch = 20, cex = 0.4,
main = "Fuzzy C-Means (K=7)", xlab = "PC1", ylab = "PC2")
plot(pca$x[,1], pca$x[,2],
col = colors_use[true_labels_num], pch = 20, cex = 0.4,
main = "Kelas Asli (7 Kelas)", xlab = "PC1", ylab = "PC2")
legend("topright", legend = unique(true_labels),
col = colors_use[1:7], pch = 20, cex = 0.55, title = "Kelas")
PC1 menjelaskan 55.5% variansi dan PC2 menjelaskan 26.4%, sehingga total
81.9% informasi tertangkap dalam 2 dimensi, yang berarti cukup
representatif.
Interpretasi: 1. K-Means (K=7), cluster yang terbentuk cukup terpisah, terutama cluster di pojok kanan atas.Pola clustering mirip dengan kelas asli. 2. K-Medians (K=7), pola yang dihasilkan mirip K-Means namun batas antar cluster sedikit berbeda, terutama di area tengah yang padat. Cluster lebih merata distribusinya (1.227–2.346). 3. DBSCAN, hampir seluruh data masuk satu cluster besar (biru tua), dengan titik-titik abu-abu adalah noise. Ini menunjukkan bahwa DBSCAN tidak berhasil memisahkan 7 jenis kacang, karena struktur data tidak cocok untuk metode berbasis kepadatan. 4. Mean Shift, cluster tersebar tidak beraturan dan sangat banyak (938 cluster), karena Mean Shift menemukan terlalu banyak mode lokal. Visualisasi terlihat seperti noise karena warna berulang.Metode ini tidak berhasil untuk dataset ini. 5. Fuzzy C-Means (K=7), metode ini secara visual paling mirip dengan K-Means, dengan cluster yang cukup terpisah. Pola mendekati kelas asli terutama untuk cluster yang jelas seperti BOMBAY di pojok kanan atas. 6. Kelas Asli, menunjukkan bahwa beberapa kelas memang overlap secara visual (terutama di area tengah), sehingga wajar jika metrik evaluasi tidak sempurna.
Tiga metrik evaluasi yang digunakan: 1. Shilhoutte score: mengukur seberapa mirip data dengan clusternya sendiri vs cluster lain. Berkisar -1 sampai 1, lebih tinggi lebih baik. 2. Dunn Index: rasio jarak antar cluster minimum vs diameter cluster maksimum. Lebih tinggi berarti cluster lebih kompak dan terpisah. 3. Adjusted Rand Index (ARI): membandingkan hasil clustering dengan label kelas asli. Nilai 0 berarti acak, nilai 1 berarti sempurna.
set.seed(123)
n_eval <- 2000
idx_eval <- sample(nrow(df), n_eval)
df_eval <- df[idx_eval, ]
dist_eval <- dist(df_eval)
eval_clustering <- function(cluster_full) {
cluster_sample <- cluster_full[idx_eval]
valid <- !is.na(cluster_sample)
cluster_valid <- cluster_sample[valid]
if (length(unique(cluster_valid)) < 2) {
return(list(sil = NA, dunn = NA, ari = NA))
}
dist_valid <- as.dist(as.matrix(dist_eval)[valid, valid])
sil_score <- mean(silhouette(cluster_valid, dist_valid)[, 3])
set.seed(42)
idx_dunn <- sample(length(cluster_valid), min(500, length(cluster_valid)))
dunn_score <- cluster.stats(
as.dist(as.matrix(dist_valid)[idx_dunn, idx_dunn]),
cluster_valid[idx_dunn]
)$dunn
ari_score <- adjustedRandIndex(cluster_valid, true_labels_num[idx_eval][valid])
return(list(sil = round(sil_score, 4),
dunn = round(dunn_score, 4),
ari = round(ari_score, 4)))
}
r_km <- eval_clustering(km_res$cluster)
r_kmd <- eval_clustering(clusters(kmed_res))
r_db <- list(sil = NA, dunn = NA, ari = NA)
r_ms <- eval_clustering(ms_cluster_full)
r_fcm <- eval_clustering(fcm_res$cluster)
comparison_df <- data.frame(
Metode = c("K-Means", "K-Medians", "DBSCAN", "Mean Shift", "Fuzzy C-Means"),
Silhouette = c(r_km$sil, r_kmd$sil, r_db$sil, r_ms$sil, r_fcm$sil),
Dunn_Index = c(r_km$dunn, r_kmd$dunn, r_db$dunn, r_ms$dunn, r_fcm$dunn),
ARI = c(r_km$ari, r_kmd$ari, r_db$ari, r_ms$ari, r_fcm$ari)
)
knitr::kable(comparison_df,
caption = "Tabel Perbandingan Metrik Evaluasi Semua Metode Clustering")
| Metode | Silhouette | Dunn_Index | ARI |
|---|---|---|---|
| K-Means | 0.3029 | 0.0467 | 0.6532 |
| K-Medians | 0.1934 | 0.0366 | 0.5877 |
| DBSCAN | NA | NA | NA |
| Mean Shift | 0.0430 | 0.1053 | 0.0388 |
| Fuzzy C-Means | 0.2665 | 0.0260 | 0.6514 |
par(mfrow = c(1, 3), mar = c(7, 4, 3, 1))
barplot(comparison_df$Silhouette,
names.arg = comparison_df$Metode,
main = "Silhouette Score",
col = c("#082a54","#e02b35","#f0c571","#59a89c","#a559aa"),
las = 2, cex.names = 0.85, ylab = "Silhouette Score")
barplot(comparison_df$Dunn_Index,
names.arg = comparison_df$Metode,
main = "Dunn Index",
col = c("#082a54","#e02b35","#f0c571","#59a89c","#a559aa"),
las = 2, cex.names = 0.85, ylab = "Dunn Index")
barplot(comparison_df$ARI,
names.arg = comparison_df$Metode,
main = "Adjusted Rand Index",
col = c("#082a54","#e02b35","#f0c571","#59a89c","#a559aa"),
las = 2, cex.names = 0.85, ylab = "ARI")
Mean Shift memiliki Dunn Index tertinggi (0.1053) karena
cluster-clusternya kecil dan terpencar jauh satu sama lain, namun ARI
hanya 0.0388 yang berarti hampir tidak ada kesesuaian dengan kelas
asli.
best_sil <- comparison_df$Metode[which.max(comparison_df$Silhouette)]
best_dunn <- comparison_df$Metode[which.max(comparison_df$Dunn_Index)]
best_ari <- comparison_df$Metode[which.max(comparison_df$ARI)]
cat("Metode terbaik berdasarkan Silhouette Score :", best_sil, "\n")
## Metode terbaik berdasarkan Silhouette Score : K-Means
cat("Metode terbaik berdasarkan Dunn Index :", best_dunn, "\n")
## Metode terbaik berdasarkan Dunn Index : Mean Shift
cat("Metode terbaik berdasarkan ARI :", best_ari, "\n")
## Metode terbaik berdasarkan ARI : K-Means
K-Means adalah metode terbaik untuk Dry Bean Dataset berdasarkan Silhouette Score tertinggi (0.3029) dan ARI tertinggi (0.6532), diikuti Fuzzy C-Means yang sangat kompetitif (ARI 0.6514). Keduanya berhasil mengidentifikasi ~65% struktur kelas asli. DBSCAN dan Mean Shift tidak cocok untuk dataset ini karena Dry Bean memiliki cluster berbentuk konveks dan berdekatan, bukan struktur berbasis kepadatan yang menjadi keunggulan kedua metode tersebut. Nilai Dunn Index yang rendah pada semua metode menunjukkan adanya overlap antar cluster yang memang terlihat secara visual pada plot PCA, dan ini wajar mengingat 7 jenis kacang memiliki karakteristik morfologi yang saling mirip.