Universitas Negeri Surabaya
Fakultas Matematika dan Ilmu Pengetahuan Alam
S1 Sains Data
Anggota Kelompok:
Indeks kebahagiaan global merupakan salah satu indikator penting yang digunakan untuk menilai kesejahteraan dan kualitas hidup masyarakat suatu negara. World Happiness Report secara rutin mempublikasikan skor kebahagiaan berdasarkan berbagai faktor penentu seperti Produk Domestik Bruto per kapita (GDP per capita), dukungan sosial (social support), harapan hidup sehat (healthy life expectancy), kebebasan dalam membuat keputusan hidup (freedom to make life choices), kemurahan hati (generosity), serta persepsi terhadap korupsi (perceptions of corruption). Skor kebahagiaan ini bersifat ordinal, mencerminkan tingkat atau peringkat kebahagiaan suatu negara dari perspektif masyarakatnya [1].
Dalam penelitian dan perumusan kebijakan publik, pengembangan model klasifikasi yang akurat terhadap tingkat kebahagiaan menjadi sangat relevan. Model tersebut memungkinkan pemangku kebijakan untuk memahami variabel-variabel sosial-ekonomi yang paling berpengaruh terhadap kesejahteraan masyarakat, serta mengarahkan intervensi secara lebih tepat sasaran.
Sejalan dengan itu, terdapat beberapa pertanyaan kunci yang perlu dijawab dalam kajian ini. Pertama, bagaimana cara mengklasifikasikan tingkat kebahagiaan negara secara otomatis berdasarkan indikator sosial-ekonomi menggunakan metode Linear Discriminant Analysis (LDA) dan Ordinal Logistic Regression (OLR)? Kedua, apakah metode regresi logistik ordinal lebih efektif dibandingkan analisis diskriminan dalam mengklasifikasikan skor kebahagiaan ke dalam kategori rendah, sedang, dan tinggi? Ketiga, sejauh mana pengaruh faktor-faktor seperti GDP per capita, dukungan sosial, harapan hidup sehat, dan persepsi terhadap korupsi terhadap akurasi klasifikasi skor kebahagiaan global?.
Untuk menjawab pertanyaan-pertanyaan tersebut, proyek ini bertujuan untuk mengembangkan model klasifikasi terhadap tingkat kebahagiaan negara berdasarkan data indikator sosial-ekonomi menggunakan dua pendekatan statistik, yaitu analisis diskriminan dan regresi logistik ordinal. Selain itu, proyek ini bertujuan untuk menganalisis kontribusi masing-masing variabel independen dalam memengaruhi klasifikasi skor kebahagiaan ke dalam tiga kategori: rendah, sedang, dan tinggi. Terakhir, proyek ini juga membandingkan performa kedua metode tersebut dengan menggunakan metrik evaluasi seperti akurasi, 95% confidence interval (CI), No Information Rate (NIR), p-value, dan Kappa Score.
Beberapa pendekatan statistik telah digunakan untuk menganalisis skor kebahagiaan secara global, antara lain visualisasi data [2], klasifikasi status negara [3], hingga identifikasi faktor-faktor penyebab utama kebahagiaan [4]. Selain itu, studi terkini menunjukkan bahwa status imigran serta faktor sosial lainnya juga memiliki kontribusi signifikan terhadap tingkat kebahagiaan masyarakat [5].
Dua pendekatan statistik yang populer dalam analisis klasifikasi data ordinal adalah Ordinal Logistic Regression (OLR) dan Linear Discriminant Analysis (LDA). OLR banyak digunakan karena kemampuannya dalam memodelkan probabilitas kumulatif dari variabel target dengan skala ordinal, sedangkan LDA efektif dalam membedakan antar kelompok berdasarkan kombinasi linier dari variabel prediktor dengan asumsi distribusi normal antar kelas dan homogenitas varians [6][7][8][9][10].
Data diambil dari World Happiness Report [11] tahun 2024
data <- read.csv("E:\\Project_Anmul\\whr_2024.csv")
knitr::kable(head(data, 5))
| Country.name | Score | GDPpc | Socsup | HLE | FtMLC | Gens | PoC |
|---|---|---|---|---|---|---|---|
| Finland | 7.741 | 1.844 | 1.572 | 0.695 | 0.859 | 0.142 | 0.546 |
| Denmark | 7.583 | 1.908 | 1.520 | 0.699 | 0.823 | 0.204 | 0.548 |
| Iceland | 7.525 | 1.881 | 1.617 | 0.718 | 0.819 | 0.258 | 0.182 |
| Sweden | 7.344 | 1.878 | 1.501 | 0.724 | 0.838 | 0.221 | 0.524 |
| Israel | 7.341 | 1.803 | 1.513 | 0.740 | 0.641 | 0.153 | 0.193 |
str(data)
## 'data.frame': 143 obs. of 8 variables:
## $ Country.name: chr "Finland" "Denmark" "Iceland" "Sweden" ...
## $ Score : num 7.74 7.58 7.53 7.34 7.34 ...
## $ GDPpc : num 1.84 1.91 1.88 1.88 1.8 ...
## $ Socsup : num 1.57 1.52 1.62 1.5 1.51 ...
## $ HLE : num 0.695 0.699 0.718 0.724 0.74 0.706 0.704 0.708 0.747 0.692 ...
## $ FtMLC : num 0.859 0.823 0.819 0.838 0.641 0.725 0.835 0.801 0.759 0.756 ...
## $ Gens : num 0.142 0.204 0.258 0.221 0.153 0.247 0.224 0.146 0.173 0.225 ...
## $ PoC : num 0.546 0.548 0.182 0.524 0.193 0.372 0.484 0.432 0.498 0.323 ...
Keterangan variabel:
GDPpc | GDP per capita: Indikator ekonomi yang
menunjukkan pendapatan rata-rata per orang di suatu negara atau
wilayah.Socsup | Social support: Bantuan atau dukungan sosial
yang diberikan oleh orang lain kepada individu lain, yang bisa berupa
dukungan emosional, instrumental, informasi, atau penghargaan.HLE | Healthy life expectancy: Rata-rata jumlah tahun
yang diharapkan seseorang untuk hidup dalam kesehatan penuh.FtMLC | Freedom to make life choices: Hak asasi manusia
yang memungkinkan seseorang untuk memutuskan bagaimana hidup mereka,
termasuk bagaimana membelanjakan uang, membuat keputusan perawatan
kesehatan, memilih pekerjaan, dan membangun hubungan.Gens | Generosity: Sifat atau tindakan memberikan
sesuatu kepada orang lain tanpa mengharapkan balasan atau keuntungan
pribadi.PoC | Perceptions of corruption: Persepsi atau
penilaian masyarakat terhadap tingkat korupsi di suatu negara atau
wilayah, yang sering diukur melalui survei seperti Indeks Persepsi
Korupsi (IPK).summary_rounded <- sapply(data, function(x) {
if (is.numeric(x)) {
round(summary(x), 4)
} else {
summary(x)
}
})
print(summary_rounded)
## $Country.name
## Length Class Mode
## 143 character character
##
## $Score
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.721 4.726 5.785 5.528 6.416 7.741
##
## $GDPpc
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.000 1.078 1.431 1.379 1.742 2.141 3
##
## $Socsup
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0000 0.9218 1.2375 1.1343 1.3832 1.6170 3
##
## $HLE
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0000 0.3980 0.5495 0.5209 0.6485 0.8570 3
##
## $FtMLC
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0000 0.5275 0.6410 0.6206 0.7360 0.8630 3
##
## $Gens
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0000 0.0910 0.1365 0.1463 0.1925 0.4010 3
##
## $PoC
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0000 0.0688 0.1205 0.1541 0.1937 0.5750 3
cat("Dimensi data:", dim(data)[1], "baris x", dim(data)[2], "kolom\n")
## Dimensi data: 143 baris x 8 kolom
print("Nilai yang hilang: ")
## [1] "Nilai yang hilang: "
colSums(is.na(data))
## Country.name Score GDPpc Socsup HLE FtMLC
## 0 0 3 3 3 3
## Gens PoC
## 3 3
cat("Total data yang hilang: ", sum(is.na(data)), "\n")
## Total data yang hilang: 18
cat("Jumlah duplikasi:", sum(duplicated(data)), "\n")
## Jumlah duplikasi: 0
features <- c("Score", "GDPpc", "Socsup", "HLE", "FtMLC", "Gens", "PoC")
numeric_with_na <- data %>%
dplyr::select(where(is.numeric)) %>%
dplyr::select(where(~ any(is.na(.))))
skew_df <- data.frame(
Feature = names(numeric_with_na),
Skewness = sapply(numeric_with_na, skewness, na.rm = TRUE)
)
print(skew_df)
## Feature Skewness
## GDPpc GDPpc -0.5016776
## Socsup Socsup -0.9816194
## HLE HLE -0.5401925
## FtMLC FtMLC -1.0078842
## Gens Gens 0.6559284
## PoC PoC 1.5085388
h <- hist(data$Score,
main = "Distribusi Skor Kebahagiaan",
xlab = "Skor",
col = "skyblue",
border = "white")
text(x = h$mids,
y = h$counts,
labels = h$counts,
pos = 3,
cex = 0.8,
col = "black")
boxplot(data$Score, main="Boxplot Skor Kebahagiaan", col="lightblue", horizontal=TRUE)
top_10 <- data %>% arrange(desc(Score)) %>% slice_head(n = 10)
cat("10 Negara dengan Skor Kebahagiaan Tertinggi:\n")
## 10 Negara dengan Skor Kebahagiaan Tertinggi:
print(top_10[, c("Country.name", "Score")])
## Country.name Score
## 1 Finland 7.741
## 2 Denmark 7.583
## 3 Iceland 7.525
## 4 Sweden 7.344
## 5 Israel 7.341
## 6 Netherlands 7.319
## 7 Norway 7.302
## 8 Luxembourg 7.122
## 9 Switzerland 7.060
## 10 Australia 7.057
bottom_10 <- data %>% arrange(Score) %>% slice_head(n = 10)
cat("10 Negara dengan Skor Kebahagiaan Terendah:\n")
## 10 Negara dengan Skor Kebahagiaan Terendah:
print(bottom_10[, c("Country.name", "Score")])
## Country.name Score
## 1 Afghanistan 1.721
## 2 Lebanon 2.707
## 3 Lesotho 3.186
## 4 Sierra Leone 3.245
## 5 Congo (Kinshasa) 3.295
## 6 Zimbabwe 3.341
## 7 Botswana 3.383
## 8 Malawi 3.421
## 9 Zambia 3.502
## 10 Eswatini 3.502
plot_list <- lapply(names(numeric_with_na), function(col) {
ggplot(data, aes_string(x = col)) +
geom_histogram(aes(y = ..density..), bins = 30, fill = "skyblue", color = "white", alpha = 0.7) +
geom_density(color = "red", size = 1) +
labs(title = paste("Distribusi dan Density:", col), x = col, y = "Density") +
theme_minimal()
})
do.call("grid.arrange", c(plot_list, ncol = 2))
ggplot(top_10, aes(x = reorder(Country.name, Score), y = Score)) +
geom_bar(stat = "identity", fill = "darkgreen") +
geom_text(aes(label = round(Score, 2)), hjust = -0.1, size = 3) +
labs(title = "10 Negara dengan Skor Kebahagiaan Tertinggi", x = "Negara", y = "Skor Kebahagiaan") +
coord_flip() +
theme_minimal()
ggplot(bottom_10, aes(x = reorder(Country.name, -Score), y = Score)) +
geom_bar(stat = "identity", fill = "darkred") +
geom_text(aes(label = round(Score, 2)), hjust = -0.1, size = 3) +
labs(title = "10 Negara dengan Skor Kebahagiaan Terendah", x = "Negara", y = "Skor Kebahagiaan") +
coord_flip() +
theme_minimal()
top_5 <- data %>% arrange(desc(Score)) %>% slice_head(n = 5)
top_5_long <- top_5 %>%
dplyr::select(Country.name, GDPpc, Socsup, HLE, FtMLC, Gens, PoC) %>%
pivot_longer(cols = -Country.name, names_to = "Factor", values_to = "Value")
ggplot(top_5_long, aes(x = Factor, y = Value, fill = Country.name)) +
geom_bar(stat = "identity", position = "dodge") +
labs(title = "Kontribusi Faktor pada 5 Negara Teratas", x = "Faktor", y = "Nilai") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Untuk melihat faktor-faktor yang berpengaruh pada 5 negara teratas
numeric_data <- data %>% dplyr::select(where(is.numeric))
# Tambahkan Country jika tersedia
if ("Country.name" %in% colnames(data)) {
numeric_data <- cbind(Country.name = data$Country.name, numeric_data)
}
# Ubah ke long format untuk ggplot
numeric_long <- pivot_longer(numeric_data, cols = -Country.name, names_to = "Feature", values_to = "Value")
# Plot boxplot per fitur
ggplot(numeric_long, aes(x = Feature, y = Value)) +
geom_boxplot(fill = "#69b3a2", outlier.colour = "red", outlier.shape = 16) +
theme_minimal(base_size = 13) +
labs(title = "Boxplot Setiap Fitur Numerik", x = "Fitur", y = "Nilai") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# Imputasi dengan Median
numeric_cols <- sapply(data, is.numeric)
for (col in names(data)[numeric_cols]) {
data[[col]][is.na(data[[col]])] <- median(data[[col]], na.rm = TRUE)
cat("Imputasi median untuk:", col, "\n")
}
## Imputasi median untuk: Score
## Imputasi median untuk: GDPpc
## Imputasi median untuk: Socsup
## Imputasi median untuk: HLE
## Imputasi median untuk: FtMLC
## Imputasi median untuk: Gens
## Imputasi median untuk: PoC
# Cek Missing Values kembali
print("Nilai yang hilang: ")
## [1] "Nilai yang hilang: "
colSums(is.na(data))
## Country.name Score GDPpc Socsup HLE FtMLC
## 0 0 0 0 0 0
## Gens PoC
## 0 0
cat("Total data yang hilang: ", sum(is.na(data)), "\n")
## Total data yang hilang: 0
handle_outliers_iqr <- function(x, multiplier = 1.5) {
if(sum(!is.na(x)) <= 1) {
return(x)
}
q1 <- quantile(x, 0.25, na.rm = TRUE)
q3 <- quantile(x, 0.75, na.rm = TRUE)
iqr <- q3 - q1
lower_bound <- q1 - (multiplier * iqr)
upper_bound <- q3 + (multiplier * iqr)
x[!is.na(x) & x < lower_bound] <- lower_bound
x[!is.na(x) & x > upper_bound] <- upper_bound
return(x)
}
numeric_columns <- sapply(data, is.numeric)
# Handle jika tidak ada kolom numerik
if(sum(numeric_columns) > 0) {
for (col in names(data)[numeric_columns]) {
data[[col]] <- handle_outliers_iqr(data[[col]])
}
numeric_data <- data %>% dplyr::select(where(is.numeric))
if ("Country.name" %in% colnames(data)) {
numeric_data <- cbind(Country.name = data$Country.name, numeric_data)
numeric_long <- pivot_longer(numeric_data, cols = -Country.name, names_to = "Feature", values_to = "Value")
} else {
numeric_long <- pivot_longer(numeric_data, cols = everything(), names_to = "Feature", values_to = "Value")
}
# Plot boxplot per fitur
ggplot(numeric_long, aes(x = Feature, y = Value)) +
geom_boxplot(fill = "#69b3a2", outlier.colour = "red", outlier.shape = 16) +
theme_minimal(base_size = 13) +
labs(title = "Boxplot Setiap Fitur Numerik (Setelah Penanganan IQR)", x = "Fitur", y = "Nilai") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
} else {
print("Dataset tidak memiliki kolom numerik untuk divisualisasikan.")
}
Untuk mengidentifikasi dan menghapus nilai outlier dari data, digunakan metode Interquartile Range (IQR). IQR merupakan selisih antara kuartil ketiga (Q3) dan kuartil pertama (Q1), yang merepresentasikan rentang tengah 50% data. Nilai-nilai yang dianggap outlier adalah data yang berada di bawah Q1 - 1.5×IQR atau di atas Q3 + 1.5×IQR.
correlation_matrix <- cor(data[features])
corrplot(correlation_matrix, method = "color", type = "upper", order = "hclust",
tl.col = "black", tl.srt = 45, addCoef.col = "black", number.cex = 0.7,
title = "Korelasi antar Fitur")
data$Happiness_Level <- cut(data$Score, breaks = c(0, 5, 6, Inf),
labels = c("Low", "Medium", "High"),
include.lowest = TRUE)
data$Happiness_Level <- ordered(data$Happiness_Level, levels = c("Low", "Medium", "High"))
Menggunakan Binning untuk mengubah target dari numerik kontinu ke kategorik. Menggunakan acuan dari hasil penelitian sebelumnya [12]. Setelah dilakukan binning, selanjutnya dilakukan perubahan agar menjadi data ordinal.
table_happiness <- table(data$Happiness_Level)
print(table_happiness)
##
## Low Medium High
## 46 41 56
barplot(table_happiness, main = "Distribusi Kategori Kebahagiaan",
col = c("red", "blue", "green"), xlab = "Kategori", ylab = "Jumlah Negara")
# Daftar fitur dan nilai skewness
fitur_skew <- c("GDPpc", "Socsup", "HLE", "FtMLC", "Gens", "PoC")
skewness_values <- c(-0.50, -0.98, -0.54, -1.01, 0.66, 1.51)
names(skewness_values) <- fitur_skew
# Transformasi berbasis arah skewness
for (fitur in fitur_skew) {
nilai <- data[[fitur]]
if (skewness_values[fitur] > 0) {
# Positively skewed → log10(x)
data[[paste0("trans_", fitur)]] <- log10(nilai + 1e-8) # Hindari log(0)
} else {
# Negatively skewed → log10(max(x+1) - x)
data[[paste0("trans_", fitur)]] <- log10(max(nilai + 1) - nilai + 1e-8)
}
}
# Cek Variabel baru hasil Transformasi
names(data)
## [1] "Country.name" "Score" "GDPpc" "Socsup"
## [5] "HLE" "FtMLC" "Gens" "PoC"
## [9] "Happiness_Level" "trans_GDPpc" "trans_Socsup" "trans_HLE"
## [13] "trans_FtMLC" "trans_Gens" "trans_PoC"
Menangani normalitas data dengan trasnformasi log untuk dua kondisi yang berbeda, yaitu nilai skew yang positif dan negatif [13].
OLRvif_model <- lm(as.numeric(Happiness_Level) ~ trans_GDPpc + trans_Socsup + trans_HLE +
trans_FtMLC + trans_Gens + trans_PoC, data = data)
vif(vif_model)
## trans_GDPpc trans_Socsup trans_HLE trans_FtMLC trans_Gens trans_PoC
## 4.206114 2.676294 3.718712 1.331593 1.024596 1.065375
VIF (Variance Inflation Factor) digunakan untuk mendeteksi adanya korelasi yang tinggi antar variabel bebas (independent) dalam model regresi.
Uji Hipotesis:
VIF <= 5 atau 10).VIF > 5 atau 10).Kriteria Keputusan:
VIF < 5: Tidak ada indikasi
multikolinearitas serius.VIF > 5 (moderate) atau > 10
(severe): Ada indikasi multikolinearitas.LDA# Ambil hanya fitur numerik hasil transformasi
fitur <- data[, c("trans_GDPpc", "trans_Socsup", "trans_HLE",
"trans_FtMLC", "trans_Gens", "trans_PoC")]
# Jalankan uji normalitas multivariat Mardia
hasil_mvn <- mvn(data = fitur, mvnTest = "mardia", multivariatePlot = "qq")
# Ambil hasilnya dalam bentuk data frame
hasil_mvn_df <- hasil_mvn$multivariateNormality
print(hasil_mvn_df)
## Test Statistic p value Result
## 1 Mardia Skewness 3740.42662773086 0 NO
## 2 Mardia Kurtosis 104.929091737487 0 NO
## 3 MVN <NA> <NA> NO
Mardia digunakan untuk menguji apakah data multivariat terdistribusi normal. Uji ini berbasis pada perhitungan kemiringan (skewness) dan keruncingan (kurtosis) data multivariat.
Uji Hipotesis:
Kriteria Keputusan:
p-value > 0.05, Terima H0, Data normal.p-value <= 0.05, Tolak H0, Data tidak normal
multivariat.numeric_vars <- c("trans_GDPpc", "trans_Socsup", "trans_HLE",
"trans_FtMLC", "trans_Gens", "trans_PoC")
complete_data <- data[, c(numeric_vars, "Happiness_Level")]
box_test <- biotools::boxM(complete_data[, numeric_vars], complete_data$Happiness_Level)
cat("\nBox's M Test for Homogeneity of Covariance Matrices:\n")
##
## Box's M Test for Homogeneity of Covariance Matrices:
print(box_test)
##
## Box's M-test for Homogeneity of Covariance Matrices
##
## data: complete_data[, numeric_vars]
## Chi-Sq (approx.) = 322.49, df = 42, p-value < 2.2e-16
Box’s M adalah uji statistik yang digunakan untuk menguji asumsi homogenitas varians-kovarians dalam analisis MANOVA (Multivariate Analysis of Variance) atau analisis multivariat lainnya.
Uji Hipotesis:
Kriteria Keputusan:
p-value > 0.05, Terima H0, Homogen.p-value <= 0.05, Tolak H0, Tidak homogen.cat("\n===== TRANSFORMASI BOX-COX =====\n")
##
## ===== TRANSFORMASI BOX-COX =====
# Hanya menggunakan data numerik untuk transformasi
prediktor <- c("trans_GDPpc", "trans_Socsup", "trans_HLE", "trans_FtMLC", "trans_Gens", "trans_PoC")
for (var in prediktor) {
# Menambahkan konstanta untuk memastikan semua nilai positif jika ada nilai negatif
nilai <- data[[var]]
if (min(nilai, na.rm = TRUE) <= 0) {
offset <- abs(min(nilai, na.rm = TRUE)) + 1
nilai <- nilai + offset
cat(sprintf("Menambahkan offset %f ke variabel %s untuk memastikan semua nilai positif\n", offset, var))
}
# Mencari parameter lambda optimal untuk transformasi Box-Cox
bc <- car::powerTransform(nilai)
lambda <- bc$lambda
# Menerapkan transformasi Box-Cox
if (abs(lambda) < 0.01) { # Mendekati 0
data[[paste0("boxcox_", var)]] <- log(nilai)
cat(sprintf("Variabel %s: lambda ≈ 0, menerapkan transformasi logaritmik\n", var))
} else {
data[[paste0("boxcox_", var)]] <- (nilai^lambda - 1) / lambda
cat(sprintf("Variabel %s: lambda = %.4f\n", var, lambda))
}
}
## Variabel trans_GDPpc: lambda = 0.7030
## Variabel trans_Socsup: lambda = 0.4741
## Variabel trans_HLE: lambda = 0.6025
## Variabel trans_FtMLC: lambda = 0.5420
## Menambahkan offset 9.000000 ke variabel trans_Gens untuk memastikan semua nilai positif
## Variabel trans_Gens: lambda = 9.0354
## Menambahkan offset 9.000000 ke variabel trans_PoC untuk memastikan semua nilai positif
## Variabel trans_PoC: lambda = 6.3184
# Cek Variabel baru hasil Transformasi
names(data)
## [1] "Country.name" "Score" "GDPpc"
## [4] "Socsup" "HLE" "FtMLC"
## [7] "Gens" "PoC" "Happiness_Level"
## [10] "trans_GDPpc" "trans_Socsup" "trans_HLE"
## [13] "trans_FtMLC" "trans_Gens" "trans_PoC"
## [16] "boxcox_trans_GDPpc" "boxcox_trans_Socsup" "boxcox_trans_HLE"
## [19] "boxcox_trans_FtMLC" "boxcox_trans_Gens" "boxcox_trans_PoC"
# Ambil hanya fitur numerik hasil transformasi
fitur <- data[, c("boxcox_trans_GDPpc", "boxcox_trans_Socsup", "boxcox_trans_HLE",
"boxcox_trans_FtMLC", "boxcox_trans_Gens", "boxcox_trans_PoC")]
# Jalankan uji normalitas multivariat Mardia
hasil_mvn <- mvn(data = fitur, mvnTest = "mardia", multivariatePlot = "qq")
# Ambil hasilnya dalam bentuk data frame
hasil_mvn_df <- hasil_mvn$multivariateNormality
print(hasil_mvn_df)
## Test Statistic p value Result
## 1 Mardia Skewness 168.470360400258 3.41115253380214e-13 NO
## 2 Mardia Kurtosis 5.33494386042621 9.55741226160711e-08 NO
## 3 MVN <NA> <NA> NO
Uji Hipotesis:
Kriteria Keputusan:
p-value > 0.05, Terima H0, Data normal.p-value <= 0.05, Tolak H0, Data tidak normal
multivariat.numeric_vars <- c("boxcox_trans_GDPpc", "boxcox_trans_Socsup", "boxcox_trans_HLE",
"boxcox_trans_FtMLC", "boxcox_trans_Gens", "boxcox_trans_PoC")
complete_data <- data[, c(numeric_vars, "Happiness_Level")]
box_test <- biotools::boxM(complete_data[, numeric_vars], complete_data$Happiness_Level)
cat("\nBox's M Test for Homogeneity of Covariance Matrices:\n")
##
## Box's M Test for Homogeneity of Covariance Matrices:
print(box_test)
##
## Box's M-test for Homogeneity of Covariance Matrices
##
## data: complete_data[, numeric_vars]
## Chi-Sq (approx.) = 91.004, df = 42, p-value = 1.79e-05
Uji Hipotesis:
Kriteria Keputusan:
p-value > 0.05, Terima H0, Homogen.p-value <= 0.05, Tolak H0, Tidak homogen.fitur dan
Score# --- Uji Korelasi Spearman ---
fitur_full <- c("boxcox_trans_GDPpc", "boxcox_trans_Socsup", "boxcox_trans_HLE",
"boxcox_trans_FtMLC", "boxcox_trans_Gens", "boxcox_trans_PoC")
spearman_test_results <- lapply(fitur_full, function(fitur) {
result <- cor.test(data[[fitur]], data$Score, method = "spearman", exact = FALSE)
data.frame(
Feature = fitur,
Correlation = round(result$estimate, 4),
p_value = round(result$p.value, 5)
)
})
spearman_df <- do.call(rbind, spearman_test_results)
# --- Tambahkan label nama fitur lebih singkat jika perlu ---
spearman_df$Short_Feature <- gsub("boxcox_trans_", "", spearman_df$Feature)
# --- Plot hasil korelasi ---
ggplot(spearman_df, aes(x = reorder(Short_Feature, Correlation), y = Correlation)) +
geom_bar(stat = "identity", fill = "#69b3a2") +
geom_text(aes(label = paste0("r = ", Correlation, "\n(p = ", p_value, ")")),
vjust = -0.2, size = 3) +
labs(
title = "Korelasi Spearman antara Fitur dan Skor Kebahagiaan",
x = "Fitur",
y = "Koefisien Korelasi Spearman"
) +
theme_minimal() +
coord_flip() # Supaya fitur tampil vertikal
# Pemilihan Fitur
fitur <- data %>% dplyr::select(boxcox_trans_GDPpc, boxcox_trans_Socsup, boxcox_trans_HLE, boxcox_trans_FtMLC, boxcox_trans_Gens, boxcox_trans_PoC)
# Feature Scale
fitur_scaled <- scale(fitur)
# Cek Duplikasi setelah Feature Scale
duplicated_rows <- which(duplicated(as.data.frame(fitur_scaled)) | duplicated(as.data.frame(fitur_scaled), fromLast = TRUE))
data[duplicated_rows, c("Country.name", "Happiness_Level")]
## Country.name Happiness_Level
## 62 Bahrain Medium
## 88 Tajikistan Medium
## 103 State of Palestine Low
# Hapus baris duplikat dari fitur dan data asli
fitur_nodup <- fitur_scaled[-duplicated_rows, ]
data_nodup <- data[-duplicated_rows, ]
# Membuat dataframe
df_model <- cbind(as.data.frame(fitur_nodup),
Happiness_Level = data_nodup$Happiness_Level)
# Tampilan data final
dfinal_head <- cbind("Country.name" = data_nodup$Country.name, "Happiness_Level" = data_nodup$Happiness_Level, as.data.frame(round(fitur_nodup, 4)))
knitr::kable(head(dfinal_head, 5))
| Country.name | Happiness_Level | boxcox_trans_GDPpc | boxcox_trans_Socsup | boxcox_trans_HLE | boxcox_trans_FtMLC | boxcox_trans_Gens | boxcox_trans_PoC |
|---|---|---|---|---|---|---|---|
| Finland | High | -1.1615 | -2.1247 | -1.1035 | -2.4790 | 0.1058 | 1.6839 |
| Denmark | High | -1.4230 | -1.5707 | -1.1394 | -1.6222 | 0.8411 | 1.6839 |
| Iceland | High | -1.3090 | -3.4347 | -1.3166 | -1.5595 | 1.3812 | 0.5673 |
| Sweden | High | -1.2967 | -1.4140 | -1.3751 | -1.8898 | 1.0193 | 1.6839 |
| Israel | High | -1.0083 | -1.5110 | -1.5376 | 0.0927 | 0.2481 | 0.6502 |
# Mengubah respons ke faktor
df_model$Happiness_Level <- as.factor(df_model$Happiness_Level)
dist_matrix <- dist(fitur_nodup)
mds_result <- cmdscale(dist_matrix, k = 2)
# Hitung jarak asli dan jarak hasil MDS
dist_original <- as.matrix(dist_matrix)
dist_mds <- as.matrix(dist(mds_result))
# Hitung skor stress
stress_numerator <- sum((dist_original - dist_mds)^2)
stress_denominator <- sum(dist_original^2)
stress <- sqrt(stress_numerator / stress_denominator)
# Buat data frame MDS
mds_df <- as.data.frame(mds_result)
colnames(mds_df) <- c("Dim1", "Dim2")
mds_df$Country <- data_nodup$Country.name
mds_df$Happiness_Level <- data_nodup$Happiness_Level
ggplot(mds_df, aes(x = Dim1, y = Dim2, color = Happiness_Level, label = Country)) +
geom_point(size = 3) +
geom_text(check_overlap = TRUE, size = 2) +
theme_minimal() +
labs(
title = "MDS Visualization of World Happiness Report",
subtitle = paste0("Kruskal's Stress-1 = ", round(stress, 4)),
x = "Dimension 1",
y = "Dimension 2",
color = "Happiness Level"
)
Peta Multidimensi (MDS) menunjukkan hubungan antarnegara
berdasarkan tingkat kebahagiaan. Negara-negara seperti
Finlandia, Denmark, dan Swiss
(kuning) berkumpul di kuadran positif, menunjukkan kebahagiaan tinggi.
Negara-negara seperti Afghanistan dan
Sierra Leone (ungu) berada di kuadran negatif, menandakan
kebahagiaan rendah. Negara-negara “Medium” (hijau) tersebar di
antaranya.
pca_model <- prcomp(fitur_nodup, center = TRUE, scale. = TRUE)
# Ubah ukuran plot dan sesuaikan parameter visual
biplot <- ggbiplot(pca_model,
labels = data_nodup$Country.name,
groups = data_nodup$Happiness_Level,
ellipse = TRUE,
circle = TRUE,
var.scale = 1, # Skala untuk variabel
varname.size = 4, # Ukuran teks untuk variabel
varname.adjust = 1.5, # Jarak teks variabel dari panah
labels.size = 2.5, # Ukuran teks label negara
ellipse.prob = 0.68) + # Tingkat kepercayaan ellipse
theme_minimal(base_size = 12) + # Ukuran font dasar
theme(legend.position = "right",
plot.margin = margin(1, 1, 1, 1, "cm")) + # Margin plot
xlim(c(-4, 4)) + # Batas sumbu x
ylim(c(-3, 3)) # Batas sumbu y
options(repr.plot.width = 20, repr.plot.height = 20)
# Tampilkan plot
print(biplot)
Analisis Komponen Utama (PCA) menunjukkan kontribusi variabel terhadap variasi data. Variabel seperti GDP per capita (49.5%) dan SocSup memiliki pengaruh kuat pada pemisahan tingkat kebahagiaan. Negara-negara dengan kebahagiaan tinggi (kuning) berkorelasi positif dengan variabel ini, sementara negara dengan kebahagiaan rendah (ungu) menunjukkan korelasi negatif atau lemah.
summary_rounded <- sapply(df_model, function(x) {
if (is.numeric(x)) {
round(summary(x), 4)
} else {
summary(x)
}
})
print(summary_rounded)
## $boxcox_trans_GDPpc
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.0143 -0.7955 0.0670 -0.0014 0.7922 2.1338
##
## $boxcox_trans_Socsup
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.4347 -0.6855 -0.0693 0.0015 0.8213 1.9400
##
## $boxcox_trans_HLE
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.6874 -0.7155 -0.0230 0.0005 0.8176 2.2690
##
## $boxcox_trans_FtMLC
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -2.8284 -0.6212 0.0927 -0.0020 0.7351 1.9266
##
## $boxcox_trans_Gens
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.7942 -0.6542 0.0322 -0.0007 0.7156 2.0870
##
## $boxcox_trans_PoC
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.6507 -0.6243 0.0218 -0.0005 0.6557 1.6839
##
## $Happiness_Level
## Low Medium High
## 45 39 56
model_olr <- polr(Happiness_Level ~ ., data = df_model, Hess = TRUE)
summary(model_olr)
## Call:
## polr(formula = Happiness_Level ~ ., data = df_model, Hess = TRUE)
##
## Coefficients:
## Value Std. Error t value
## boxcox_trans_GDPpc -1.1537 0.5258 -2.1944
## boxcox_trans_Socsup -1.9517 0.4336 -4.5014
## boxcox_trans_HLE -0.3778 0.3937 -0.9596
## boxcox_trans_FtMLC -1.1865 0.2958 -4.0108
## boxcox_trans_Gens -0.1405 0.2300 -0.6108
## boxcox_trans_PoC 0.6442 0.2640 2.4397
##
## Intercepts:
## Value Std. Error t value
## Low|Medium -2.4875 0.4331 -5.7434
## Medium|High 1.2442 0.3462 3.5936
##
## Residual Deviance: 132.1816
## AIC: 148.1816
model_null <- update(model_olr, . ~ 1)
# Uji Likelihood Ratio Test
lrtest(model_olr, model_null)
## Likelihood ratio test
##
## Model 1: Happiness_Level ~ boxcox_trans_GDPpc + boxcox_trans_Socsup +
## boxcox_trans_HLE + boxcox_trans_FtMLC + boxcox_trans_Gens +
## boxcox_trans_PoC
## Model 2: Happiness_Level ~ 1
## #Df LogLik Df Chisq Pr(>Chisq)
## 1 8 -66.091
## 2 2 -152.232 -6 172.28 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Hipotesis:
Kriteria Keputusan:
p-value < 0.05, Tolak H0, artinya model dengan
prediktor secara keseluruhan signifikan.p-value >= 0.05, Gagal tolak H0, artinya model
dengan prediktor tidak signifikan secara keseluruhan.ctable <- round(coef(summary(model_olr)), 4)
p_vals <- pnorm(abs(ctable[, "t value"]), lower.tail = FALSE) * 2
ctable <- cbind(ctable, "p value" = round(p_vals, 4))
print(ctable)
## Value Std. Error t value p value
## boxcox_trans_GDPpc -1.1537 0.5258 -2.1944 0.0282
## boxcox_trans_Socsup -1.9517 0.4336 -4.5014 0.0000
## boxcox_trans_HLE -0.3778 0.3937 -0.9596 0.3373
## boxcox_trans_FtMLC -1.1865 0.2958 -4.0108 0.0001
## boxcox_trans_Gens -0.1405 0.2300 -0.6108 0.5413
## boxcox_trans_PoC 0.6442 0.2640 2.4397 0.0147
## Low|Medium -2.4875 0.4331 -5.7434 0.0000
## Medium|High 1.2442 0.3462 3.5936 0.0003
Hipotesis:
Kriteria Keputusan:
p-value < 0.05, Tolak H0, prediktor
signifikan.p-value >= 0.05, Gagal tolak H0, prediktor
tidak signifikan..pred_olr <- predict(model_olr, df_model)
confusionMatrix(pred_olr, df_model$Happiness_Level)
## Confusion Matrix and Statistics
##
## Reference
## Prediction Low Medium High
## Low 37 8 0
## Medium 8 24 7
## High 0 7 49
##
## Overall Statistics
##
## Accuracy : 0.7857
## 95% CI : (0.7084, 0.8505)
## No Information Rate : 0.4
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.6749
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: Low Class: Medium Class: High
## Sensitivity 0.8222 0.6154 0.8750
## Specificity 0.9158 0.8515 0.9167
## Pos Pred Value 0.8222 0.6154 0.8750
## Neg Pred Value 0.9158 0.8515 0.9167
## Prevalence 0.3214 0.2786 0.4000
## Detection Rate 0.2643 0.1714 0.3500
## Detection Prevalence 0.3214 0.2786 0.4000
## Balanced Accuracy 0.8690 0.7334 0.8958
Diperoleh informasi sebagai berikut:
Accuracy : 0.7857 | Nilai ini menunjukkan akurasi
prediksi model
95% CI : (0.7084, 0.8505) | Rentang nilai tergolong
sempit sekitar 15%, menandakan model yang Bagus dan Stabil
No Information Rate : 0.4 | Akurasi baseline suatu
model, atau akurasi pada saat asal menebak
P-Value : < 0.05 | Tolak Hipotesis Nol (H0),
dengan kata lain model berkinerja jauh lebih baik daripada sekadar
menebak kelas terbanyak.
Kappa Score : 0.6749 | Mediocrie atau sedang,
kesepakatan substansial, bukan hanya karena kebetulan
model_lda <- lda(Happiness_Level ~ ., data = df_model)
pred_lda <- predict(model_lda)$class
confusionMatrix(pred_lda, df_model$Happiness_Level)
## Confusion Matrix and Statistics
##
## Reference
## Prediction Low Medium High
## Low 39 8 0
## Medium 6 25 7
## High 0 6 49
##
## Overall Statistics
##
## Accuracy : 0.8071
## 95% CI : (0.7319, 0.8689)
## No Information Rate : 0.4
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.7075
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: Low Class: Medium Class: High
## Sensitivity 0.8667 0.6410 0.8750
## Specificity 0.9158 0.8713 0.9286
## Pos Pred Value 0.8298 0.6579 0.8909
## Neg Pred Value 0.9355 0.8627 0.9176
## Prevalence 0.3214 0.2786 0.4000
## Detection Rate 0.2786 0.1786 0.3500
## Detection Prevalence 0.3357 0.2714 0.3929
## Balanced Accuracy 0.8912 0.7562 0.9018
Diperoleh informasi sebagai berikut:
Accuracy : 0.8071 | Nilai ini menunjukkan akurasi
prediksi model
95% CI : (0.7319, 0.8689) | Rentang nilai tergolong
sempit sekitar 13%, menandakan model yang Bagus dan Stabil
No Information Rate : 0.4 | Akurasi baseline suatu
model, atau akurasi pada saat asal menebak
P-Value : < 0.05 | Tolak Hipotesis Nol (H0),
dengan kata lain model berkinerja jauh lebih baik daripada sekadar
menebak kelas terbanyak.
Kappa Score : 0.7075 | Middling atau menengah,
kesepakatan substansial, bukan hanya karena kebetulan
cm_olr <- as.data.frame(table(Predicted = pred_olr, Actual = df_model$Happiness_Level))
ggplot(cm_olr, aes(Actual, Predicted, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white", size = 5) +
labs(title = "Confusion Matrix - Ordinal Logistic Regression", x = "Actual", y = "Predicted") +
scale_fill_gradient(low = "skyblue", high = "darkblue")
cm_lda <- as.data.frame(table(Predicted = pred_lda, Actual = df_model$Happiness_Level))
ggplot(cm_lda, aes(Actual, Predicted, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white", size = 5) +
labs(title = "Confusion Matrix - Linear Discriminant Analysis", x = "Actual", y = "Predicted") +
scale_fill_gradient(low = "lightgreen", high = "darkgreen")
ggplot(data, aes(x = Happiness_Level, y = Score, fill = Happiness_Level)) +
geom_boxplot() +
theme_minimal()
Dapat dilihat pada Boxplot, rentang Score dari setiap
kelas:
Low : < 5Medium : >= 5 y < 6High : > 6Menunjukkan skor rata-rata untuk setiap tingkat kebahagiaan. “High” memiliki skor tertinggi (sekitar 7), diikuti “Medium” (sekitar 6), dan “Low” (sekitar 4), dengan sedikit tumpang tindih, menunjukkan perbedaan yang jelas antara kategori.
faktor_long <- df_model %>%
dplyr::select(Happiness_Level, boxcox_trans_GDPpc, boxcox_trans_Socsup, boxcox_trans_HLE, boxcox_trans_Gens, boxcox_trans_FtMLC, boxcox_trans_PoC) %>%
pivot_longer(-Happiness_Level, names_to = "Faktor", values_to = "Nilai")
ggplot(faktor_long, aes(x = Happiness_Level, y = Nilai, fill = Happiness_Level)) +
geom_violin(trim = FALSE) +
facet_wrap(~ Faktor, scales = "free", ncol = 3) +
theme_minimal()
Visualisasi Violin menunjukkan beberapa informasi
statistik yang dijadikan dalam satu plot, Violin Plot
berisi nilai Max, Min, IQR,
Density, Median.
Plot menunjukkan distribusi variabel seperti FtMLC, GDP per capita, Generosity, HLE, PoC, dan SocSup berdasarkan tingkat kebahagiaan (Low, Medium, High). Tingkat kebahagiaan “High” cenderung memiliki distribusi yang lebih terpusat dan simetris dengan nilai yang lebih tinggi untuk semua variabel, terutama pada GDP per capita, Generosity, dan SocSup. “Low” kebahagiaan menunjukkan distribusi yang lebih lebar dan miring ke nilai negatif, menunjukkan variabilitas lebih tinggi dan nilai yang lebih rendah secara konsisten.
Proyek ini membahas penerapan dua metode klasifikasi multivariat Ordinal Logistic Regression (OLR) dan Linear Discriminant Analysis (LDA) untuk mengklasifikasikan tingkat kebahagiaan negara berdasarkan indikator sosial-ekonomi dari World Happiness Report 2024. Target klasifikasi berupa variabel ordinal dengan tiga tingkat kebahagiaan: Low, Medium, dan High.
Hasil menunjukkan bahwa: Model OLR dan LDA sama-sama menghasilkan akurasi yang besar, 78.57% untuk model OLR dan 80.71% untuk model LDA. dengan Kappa Score > 0.65, yang menandakan adanya kesepakatan klasifikasi yang substansial. Fitur paling signifikan dalam memengaruhi klasifikasi skor kebahagiaan adalah Social support, Freedom to make life choices, dan Perceptions of corruption.
Secara keseluruhan, proyek ini menegaskan pentingnya faktor sosial dan tata kelola pemerintahan dalam memengaruhi persepsi kebahagiaan masyarakat. Penggunaan pendekatan statistik yang tepat dapat mendukung pengambilan keputusan berbasis data dalam lingkup kebijakan publik.
Berdasarkan hasil penelitian, terdapat beberapa saran yang dapat diajukan untuk pengembangan lebih lanjut:
1. Penggunaan Dataset Multitahun
2. Ekspansi Fitur Non-Numerik
3. Validasi Model dengan Data Eksternal
4. Integrasi ke Sistem Pengambilan Keputusan
Proyek ini diharapkan dapat menjadi dasar pengembangan lanjutan dalam bidang ilmu data sosial dan kebijakan berbasis bukti (evidence-based policy making).
[1] E. Sivari and S. Sürücü, “Prediction of heart attack risk using linear discriminant analysis methods,” Journal of Computer & Electrical and Electronics Engineering Sciences, vol. 1, no. 1, pp. 5–9, Apr. 2023, doi: 10.51271/jceees-0002.
[2] F. Marshall, R. Bond, and S. Zhang, “Measuring and Visualising Global Happiness,” BCS Learning & Development, 2018. doi: 10.14236/ewic/hci2018.210.
[3] S. F. Al Maula et al., “Classification Of Country Status In 2022 Based On Social Indicators With Ordinal Logistic Regression,” Jurnal Matematika, Statistika dan Komputasi, vol. 20, no. 3, pp. 538–551, May 2024, doi: 10.20956/j.v20i3.32356.
[4] P. Oberoi, S. Chopra, and Y. Seth, “A Comparative Analysis Of The Factors Affecting Happiness Index”, [Online]. Available: www.ijstr.org
[5] S. Chen, M. Yang, and Y. Lin, “Predicting happiness levels of European immigrants and natives: An application of Artificial Neural Network and Ordinal Logistic Regression,” Front Psychol, vol. 13, Oct. 2022, doi: 10.3389/fpsyg.2022.1012796.
[6] A. Jannani, N. Sael, and F. Benabbou, “Machine learning for the analysis of quality of life using the World Happiness Index and Human Development Indicators,” Mathematical Modeling and Computing, vol. 10, no. 2, pp. 534–546, 2023, doi: 10.23939/mmc2023.02.534.
[7] P. Boedeker and N. T. Kearns, “Linear Discriminant Analysis for Prediction of Group Membership: A User-Friendly Primer,” Adv Methods Pract Psychol Sci, vol. 2, no. 3, pp. 250–263, Sep. 2019, doi: 10.1177/2515245919849378.
[8] D. A. Omondiagbe, S. Veeramani, and A. S. Sidhu, “Machine Learning Classification Techniques for Breast Cancer Diagnosis,” in IOP Conference Series: Materials Science and Engineering, Institute of Physics Publishing, 2019. doi: 10.1088/1757-899X/495/1/012033.
[9] M. Kobus and R. Kurek, “Multidimensional polarization for ordinal data,” J Econ Inequal, vol. 17, no. 3, pp. 301–317, Sep. 2019, doi: 10.1007/s10888-018-9402-1.
[10] F. Lu, F. Ferraro, and E. Raff, “Continuously Generalized Ordinal Regression for Linear and Deep Models,” in Proceedings of the 2022 SIAM International Conference on Data Mining, SDM 2022, Society for Industrial and Applied Mathematics Publications, 2022, pp. 28–36. doi: 10.1137/1.9781611977172.4.
[11] World Happiness Report. 2024. https://worldhappiness.report/ed/2024/#appendices-and-data
[12] Quantifying Happiness: Exploring Factors Affecting Happiness. 2023. https://ucladatares.medium.com/quantifying-happiness-exploring-factors-affecting-happiness-e9376d32c847
[13] Transform Data to Normal Distribution in R 2018. https://www.datanovia.com/en/lessons/transform-data-to-normal-distribution-in-r/