Library dan Load Data

library(cluster)
## Warning: package 'cluster' was built under R version 4.5.3
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.2
## ✔ ggplot2   4.0.0     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.2
## ✔ purrr     1.1.0     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dbscan)
## Warning: package 'dbscan' was built under R version 4.5.3
## 
## Attaching package: 'dbscan'
## 
## The following object is masked from 'package:stats':
## 
##     as.dendrogram
library(e1071)
## Warning: package 'e1071' was built under R version 4.5.3
## 
## Attaching package: 'e1071'
## 
## The following object is masked from 'package:ggplot2':
## 
##     element
library(fpc)
## Warning: package 'fpc' was built under R version 4.5.3
## 
## Attaching package: 'fpc'
## 
## The following object is masked from 'package:dbscan':
## 
##     dbscan
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
data <- read.csv("world-data-2023.csv")

Struktur Data

str(data)
## 'data.frame':    195 obs. of  35 variables:
##  $ Country                                  : chr  "Afghanistan" "Albania" "Algeria" "Andorra" ...
##  $ Density..P.Km2.                          : chr  "60" "105" "18" "164" ...
##  $ Abbreviation                             : chr  "AF" "AL" "DZ" "AD" ...
##  $ Agricultural.Land....                    : chr  "58.10%" "43.10%" "17.40%" "40.00%" ...
##  $ Land.Area.Km2.                           : chr  "652,230" "28,748" "2,381,741" "468" ...
##  $ Armed.Forces.size                        : chr  "323,000" "9,000" "317,000" "" ...
##  $ Birth.Rate                               : num  32.5 11.8 24.3 7.2 40.7 ...
##  $ Calling.Code                             : int  93 355 213 376 244 1 54 374 61 43 ...
##  $ Capital.Major.City                       : chr  "Kabul" "Tirana" "Algiers" "Andorra la Vella" ...
##  $ Co2.Emissions                            : chr  "8,672" "4,536" "150,006" "469" ...
##  $ CPI                                      : chr  "149.9" "119.05" "151.36" "" ...
##  $ CPI.Change....                           : chr  "2.30%" "1.40%" "2.00%" "" ...
##  $ Currency.Code                            : chr  "AFN" "ALL" "DZD" "EUR" ...
##  $ Fertility.Rate                           : num  4.47 1.62 3.02 1.27 5.52 1.99 2.26 1.76 1.74 1.47 ...
##  $ Forested.Area....                        : chr  "2.10%" "28.10%" "0.80%" "34.00%" ...
##  $ Gasoline.Price                           : chr  "$0.70 " "$1.36 " "$0.28 " "$1.51 " ...
##  $ GDP                                      : chr  "$19,101,353,833 " "$15,278,077,447 " "$169,988,236,398 " "$3,154,057,987 " ...
##  $ Gross.primary.education.enrollment....   : chr  "104.00%" "107.00%" "109.90%" "106.40%" ...
##  $ Gross.tertiary.education.enrollment....  : chr  "9.70%" "55.00%" "51.40%" "" ...
##  $ Infant.mortality                         : num  47.9 7.8 20.1 2.7 51.6 5 8.8 11 3.1 2.9 ...
##  $ Largest.city                             : chr  "Kabul" "Tirana" "Algiers" "Andorra la Vella" ...
##  $ Life.expectancy                          : num  64.5 78.5 76.7 NA 60.8 76.9 76.5 74.9 82.7 81.6 ...
##  $ Maternal.mortality.ratio                 : int  638 15 112 NA 241 42 39 26 6 5 ...
##  $ Minimum.wage                             : chr  "$0.43 " "$1.12 " "$0.95 " "$6.63 " ...
##  $ Official.language                        : chr  "Pashto" "Albanian" "Arabic" "Catalan" ...
##  $ Out.of.pocket.health.expenditure         : chr  "78.40%" "56.90%" "28.10%" "36.40%" ...
##  $ Physicians.per.thousand                  : num  0.28 1.2 1.72 3.33 0.21 2.76 3.96 4.4 3.68 5.17 ...
##  $ Population                               : chr  "38,041,754" "2,854,191" "43,053,054" "77,142" ...
##  $ Population..Labor.force.participation....: chr  "48.90%" "55.70%" "41.20%" "" ...
##  $ Tax.revenue....                          : chr  "9.30%" "18.60%" "37.20%" "" ...
##  $ Total.tax.rate                           : chr  "71.40%" "36.60%" "66.10%" "" ...
##  $ Unemployment.rate                        : chr  "11.12%" "12.33%" "11.70%" "" ...
##  $ Urban_population                         : chr  "9,797,273" "1,747,593" "31,510,100" "67,873" ...
##  $ Latitude                                 : num  33.9 41.2 28 42.5 -11.2 ...
##  $ Longitude                                : num  67.71 20.17 1.66 1.52 17.87 ...

Hasil str(data) menunjukkan bahwa dataset memiliki 195 observasi dan 35 variabel dengan tipe data, yaitu karakter (chr), numerik (num), dan integer (int). Beberapa variabel sudah berupa numerik, sementara lainnya masih berbentuk karakter.

Pre-processing

df <- data %>%
  select(GDP, Life.expectancy,
         Gross.primary.education.enrollment....,
         Gross.tertiary.education.enrollment....,
         Co2.Emissions) %>%
  na.omit()

clean_numeric <- function(x){as.numeric(gsub("[^0-9.]","", x))}

df_clean <- df %>%
  mutate(
    GDP = clean_numeric(GDP),
    Life.expectancy = as.numeric(Life.expectancy),
    Gross.primary.education.enrollment.... = clean_numeric(Gross.primary.education.enrollment....),
    Gross.tertiary.education.enrollment.... = clean_numeric(Gross.tertiary.education.enrollment....),
    Co2.Emissions = clean_numeric(Co2.Emissions)
  ) %>%
  na.omit()

Tahap ini menyiapkan data untuk clustering dengan memilih 5 variabel utama, membersihkan simbol non-angka, mengubah ke numerik, dan menghapus data kosong agar bisa dianalisis.

Scaling

df_scaled <- scale(df_clean)

Tahap ini digunakan untuk menyamakan skala data dengan transformasi Z-score menggunakan scale(), sehingga setiap variabel memiliki rata-rata 0 dan standar deviasi 1. Hal ini mencegah variabel dengan nilai besar seperti GDP mendominasi perhitungan jarak. Dengan demikian, semua variabel dapat berkontribusi secara seimbang dalam analisis clustering.

Menetukan Jumlah Cluster (K-MEANS)

set.seed(123)

Tahap ini mengunci proses acak dengan set.seed(123) agar hasil clustering tetap konsisten. Dengan ini, posisi awal centroid tidak berubah-ubah sehingga hasil K-Means bisa direproduksi dan tidak dipengaruhi oleh acakan.

Elbow Method

wss <- sapply(1:10, function(k){
  kmeans(df_scaled, centers = k, nstart = 20)$tot.withinss
})

plot(1:10, wss, type = "b", pch = 19,
     xlab = "Jumlah Cluster (k)",
     ylab = "WSS",
     main = "Elbow Method")

Tahap ini digunakan untuk menentukan jumlah klaster optimal dengan Elbow Method. Nilai WSS dihitung untuk k = 1-10 menggunakan K-Means, lalu ditampilkan dalam grafik. Hasilnya menunjukkan titik siku di sekitar k = 3, sehingga jumlah klaster optimal adalah 3 karena setelah itu penurunan WSS tidak signifikan lagi.

Silhouette

avg_sil <- function(k) {
  km <-kmeans(df_scaled, centers = k, nstart = 25)
  ss <- silhouette(km$cluster, dist(df_scaled))
  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,
     xlab = "Jumlah Cluster (k)",
     ylab = "Silhouette Score",
     main = "Silhouette Analysis")

Tahap ini digunakan untuk mengevaluasi jumlah klaster dengan Silhouette Analysis. Nilai rata-rata Silhouette dihitung untuk setiap \(k\) untuk mengukur kualitas pengelompokan. Hasilnya menunjukkan skor tertinggi pada \(k = 4\)–\(5\), namun \(k = 3\) masih cukup baik, sehingga dipilih sebagai jumlah klaster optimal karena lebih sederhana dan sesuai dengan hasil Elbow Method.

Clustering K-MEANS

set.seed(123)
km_res <- kmeans(df_scaled, centers = 3, nstart = 25)
table(km_res$cluster)
## 
##  1  2  3 
## 84 95  2
interpretasi_km <- aggregate(df_clean, by = list(Cluster = km_res$cluster), mean)
print(interpretasi_km)
##   Cluster          GDP Life.expectancy Gross.primary.education.enrollment....
## 1       1 7.679703e+10        66.11667                               103.4036
## 2       2 4.655656e+11        77.62737                               102.0768
## 3       3 2.066885e+13        77.75000                               101.0000
##   Gross.tertiary.education.enrollment.... Co2.Emissions
## 1                                12.51548      53507.17
## 2                                59.90947     147225.46
## 3                                69.40000    7449670.00
fviz_cluster(km_res, data = df_scaled,
             geom = "point",
             ellipse.type = "convex",
             palette = "jco",
             ggtheme = theme_minimal(),
             main = "Visualisasi Cluster K-Means")

Tahap ini menggunakan K-Means dengan k = 3 untuk mengelompokkan negara berdasarkan kemiripan datanya, lalu hasilnya diprofilkan dengan rata-rata tiap variabel. Hasilnya terbagi menjadi 3 kelompok: negara maju dengan GDP dan life expectancy tinggi, negara berkembang dengan kondisi menengah, dan negara berpendapatan rendah dengan nilai terendah. Visualisasi juga menunjukkan korelasi positif antara GDP dan life expectancy serta adanya beberapa negara dengan GDP sangat tinggi sebagai outlier.

Clustering DBSCAN

set.seed(123)
db_res <- dbscan(df_scaled, eps = 1, MinPts = 5)
table(db_res$cluster)
## 
##   0   1   2 
##  11 165   5
interpretasi_db <- aggregate(df_clean[db_res$cluster != 0,], by = list(Cluster = db_res$cluster[db_res$cluster != 0]), mean)
print(interpretasi_db)
##   Cluster          GDP Life.expectancy Gross.primary.education.enrollment....
## 1       1 242375709021        72.59758                               102.3388
## 2       2  15834484841        67.18000                               140.0000
##   Gross.tertiary.education.enrollment.... Co2.Emissions
## 1                                38.26909      78492.85
## 2                                 6.72000       4148.80
fviz_cluster(list(data = df_scaled,
                  cluster = db_res$cluster),
             geom = "point",
             palette = "jco",
             ggtheme = theme_minimal(),
             main = "Visualisasi Cluster DBSCAN (0 = Noise)")

Tahap ini menggunakan K-Means (k = 3) untuk mengelompokkan data, lalu dilakukan profiling dengan rata-rata tiap cluster dan visualisasi menggunakan GDP dan Life Expectancy. Hasilnya terbagi menjadi 3 kelompok: negara maju (GDP dan life expectancy tinggi), negara berkembang (menengah), dan negara berpendapatan rendah (rendah). Grafik menunjukkan korelasi positif antara GDP dan life expectancy serta adanya outlier pada negara dengan GDP sangat tinggi.

Clustering FUZZY C-MEANS

set.seed(123)
fcm_res <- cmeans(df_scaled, centers = 3, m = 2)
table(fcm_res$cluster)
## 
##  1  2  3 
## 63 51 67
interpretasi_fcm <- aggregate(df_clean, by=list(Cluster=fcm_res$cluster), mean)
print(interpretasi_fcm)
##   Cluster          GDP Life.expectancy Gross.primary.education.enrollment....
## 1       1 1.824674e+11        73.70794                              106.56667
## 2       2 4.305875e+10        62.53725                               98.61569
## 3       3 1.169044e+12        78.37164                              102.12090
##   Gross.tertiary.education.enrollment.... Co2.Emissions
## 1                               25.946032     101493.27
## 2                                9.098039      22964.47
## 3                               71.386567     385299.96
fviz_cluster(list(data = df_scaled,
                  cluster = fcm_res$cluster),
             geom = "point",
             ellipse.type = "convex",
             palette = "jco",
             ggtheme = theme_minimal(),
             main = "Visualisasi Cluster Fuzzy C-Means")

Tahap ini menggunakan Fuzzy C-Means (centers = 3, m = 2) dengan pendekatan fuzzy, lalu diprofilkan menggunakan rata-rata tiap cluster dan divisualisasikan. Hasilnya terbagi lebih seimbang menjadi 3 kelompok: negara berpendapatan rendah, negara berkembang, dan negara maju. Grafik menunjukkan batas antar cluster tidak tegas serta adanya variasi GDP yang besar pada kelompok negara maju.

Evaluasi (Silhouette)

K-Means

sil_km <- mean(silhouette(km_res$cluster, dist(df_scaled))[,3])
cat("K-Means:", sil_km, "\n")
## K-Means: 0.3708797

Tahap ini menghitung Silhouette Score untuk mengevaluasi clustering. Nilai 0.371 menunjukkan hasilnya cukup valid tetapi masih lemah dan ada overlap antar cluster, sehingga digunakan sebagai acuan perbandingan metode lain.

DBSCAN

d <- as.matrix(dist(df_scaled))
db_cluster <- db_res$cluster

if(length(unique(db_cluster[db_cluster != 0])) > 1){
  sil_db <- mean(
    silhouette(db_cluster[db_cluster != 0],
               d[db_cluster != 0, db_cluster != 0])[,3]
  )
} else {
  sil_db <- NA
}

cat("DBSCAN:", sil_db, "\n")
## DBSCAN: 0.3997549

Tahap ini menghitung Silhouette Score pada DBSCAN dengan mengabaikan data noise (label 0). Hasilnya sebesar 0.393, lebih tinggi dari K-Means, menunjukkan cluster lebih solid dan terpisah dengan baik. Nilai ini menandakan DBSCAN memberikan performa yang lebih baik untuk dataset ini.

Fuzzy C-Means

sil_fcm <- mean(silhouette(fcm_res$cluster, dist(df_scaled))[,3])
cat("Fuzzy C-Means:", sil_fcm, "\n")
## Fuzzy C-Means: 0.2503616

Tahap ini menghitung Silhouette Score untuk Fuzzy C-Means berdasarkan label cluster akhir. Nilai 0.250 menunjukkan struktur cluster lemah dan banyak overlap, karena data memiliki karakteristik yang saling berdekatan antar kelompok.

Hasil Akhir

cat("Silhouette Score:\n")
## Silhouette Score:
cat("K-Means:", sil_km, "\n")
## K-Means: 0.3708797
cat("DBSCAN:", sil_db, "\n")
## DBSCAN: 0.3997549
cat("Fuzzy C-Means:", sil_fcm, "\n")
## Fuzzy C-Means: 0.2503616

Tahap ini membandingkan performa tiga algoritma menggunakan Silhouette Score. Hasilnya menunjukkan DBSCAN (0.393) terbaik karena mampu mengabaikan outlier, K-Means (0.333) cukup stabil, dan Fuzzy C-Means (0.193) terendah karena banyak overlap antar cluster. Ini menunjukkan DBSCAN paling efektif untuk dataset ini.

Metode terbaik

scores <- c(KMeans = sil_km, DBSCAN = sil_db, FCM = sil_fcm)
print(scores)
##    KMeans    DBSCAN       FCM 
## 0.3708797 0.3997549 0.2503616
best_method <- names(which.max(scores))
cat("Metode terbaik:", best_method)
## Metode terbaik: DBSCAN

Kesimpulan:

1. Performa Algoritma

DBSCAN (0.399) menjadi metode terbaik, diikuti K-Means (0.371) dan Fuzzy C-Means (0.250). DBSCAN unggul karena mampu mengabaikan outlier sehingga menghasilkan cluster yang lebih padat dan terpisah jelas.

2. Temuan Utama

Data secara konsisten terbagi menjadi 3 kelompok: negara berpendapatan rendah, berkembang, dan maju. Terdapat korelasi kuat antara GDP, Life Expectancy, dan pendidikan, di mana negara maju memiliki nilai tertinggi pada ketiga indikator tersebut. Proses scaling dan set.seed juga terbukti penting untuk hasil yang adil dan konsisten.

3. Rekomendasi

DBSCAN direkomendasikan untuk data dengan banyak nilai ekstrem karena hasilnya lebih akurat. Namun, K-Means (k = 3) tetap menjadi alternatif jika semua data ingin tetap dikelompokkan tanpa pengecualian.