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")
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.