# =========================================================================
# PEMODELAN TINGKAT KEMISKINAN KABUPATEN/KOTA DI INDONESIA MENGGUNAKAN PENDEKATAN REGRESI LOGISTIK ORDINAL
# =========================================================================
# =========================
# LOAD PACKAGE
# =========================
library(readxl)
## Warning: package 'readxl' was built under R version 4.3.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.3.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.3.3
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.3.3
library(corrplot)
## Warning: package 'corrplot' was built under R version 4.3.3
## corrplot 0.95 loaded
library(car)    
## Loading required package: carData
## Warning: package 'carData' was built under R version 4.3.3
## 
## Attaching package: 'car'
## The following object is masked from 'package:dplyr':
## 
##     recode
library(MASS)
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
library(brant) 
## Warning: package 'brant' was built under R version 4.3.3
library(scales)
# =========================
# IMPORT DATA
# =========================
data <- read_excel(
  "C:/Users/ASUS/OneDrive/Documents/ADK K10/Data tubes ADK.xlsx",   
  sheet = "Sheet1"
)
 
# buang kolom kosong tambahan (jika ada) dan ambil 12 kolom utama
data <- data[, 1:12]
# =========================
# RENAME KOLOM
# =========================
colnames(data) <- c(
  "Provinsi",       # Provinsi
  "KabKota",        # Kabupaten/Kota
  "Kemiskinan",     # Persentase Penduduk Miskin (P0) - dasar pembentukan Y
  "Sekolah",        # Rata-rata Lama Sekolah Penduduk 15+ (Tahun)
  "Pengeluaran",    # Pengeluaran per Kapita Disesuaikan (Ribu Rupiah/Orang/Tahun)
  "IPM",            # Indeks Pembangunan Manusia -> DIKELUARKAN dari model (VIF tinggi)
  "UHH",            # Umur Harapan Hidup (Tahun)
  "Sanitasi",       # % rumah tangga akses sanitasi layak
  "AirMinum",       # % rumah tangga akses air minum layak
  "Pengangguran",   # Tingkat Pengangguran Terbuka
  "TPAK",           # Tingkat Partisipasi Angkatan Kerja
  "PDRB"            # PDRB atas Dasar Harga Konstan menurut Pengeluaran (Rupiah)
)
# =========================
# CLEANING DATA
# =========================
# beberapa kolom numerik terbaca sebagai karakter dengan format koma (mis. "18,98") karena format desimal Indonesia, sehingga perlu dikonversi ke numerik
 
kolom_numerik <- c("Kemiskinan", "Sekolah", "Pengeluaran", "IPM", "UHH",
                    "Sanitasi", "AirMinum", "Pengangguran", "TPAK", "PDRB")
 
data <- data %>%
  mutate(across(all_of(kolom_numerik),
                ~ as.numeric(gsub(",", ".", as.character(.)))))
 
# cek missing value
cat("Jumlah missing value per kolom:\n")
## Jumlah missing value per kolom:
print(colSums(is.na(data)))
##     Provinsi      KabKota   Kemiskinan      Sekolah  Pengeluaran          IPM 
##            0            0            0            0            0            0 
##          UHH     Sanitasi     AirMinum Pengangguran         TPAK         PDRB 
##            0            0            0            0            0            0
# buang baris dengan missing value (jika ada)
data <- data %>% drop_na(all_of(kolom_numerik))
 
cat("\nJumlah baris data setelah cleaning:", nrow(data), "\n")
## 
## Jumlah baris data setelah cleaning: 514
# =========================
# KLASIFIKASI VARIABEL Y (KEMISKINAN) MENJADI 3 KATEGORI
# =========================
# Y dibentuk dari variabel Kemiskinan (persentase penduduk miskin),
# sehingga Kemiskinan asli TIDAK diikutkan lagi sebagai variabel X.
# Pembagian kategori menggunakan TERSIL (33% & 66%) agar jumlah kab/kota di tiap kategori relatif seimbang.
 
batas <- quantile(data$Kemiskinan, probs = c(0, 1/3, 2/3, 1))
cat("\nBatas kategori (tersil):\n")
## 
## Batas kategori (tersil):
print(batas)
##        0% 33.33333% 66.66667%      100% 
##      2.38      7.99     13.41     41.66
data$Kategori <- cut(
  data$Kemiskinan,
  breaks = batas,
  include.lowest = TRUE,
  labels = c("Rendah", "Sedang", "Tinggi")
)
data$Kategori <- factor(data$Kategori,
                         levels = c("Rendah", "Sedang", "Tinggi"),
                         ordered = TRUE)
 
cat("\nDistribusi kategori kemiskinan:\n")
## 
## Distribusi kategori kemiskinan:
print(table(data$Kategori))
## 
## Rendah Sedang Tinggi 
##    172    171    171
# =========================
# STATISTIKA DESKRIPTIF
# =========================
str(data)
## tibble [514 × 13] (S3: tbl_df/tbl/data.frame)
##  $ Provinsi    : chr [1:514] "ACEH" "ACEH" "ACEH" "ACEH" ...
##  $ KabKota     : chr [1:514] "Simeulue" "Aceh Singkil" "Aceh Selatan" "Aceh Tenggara" ...
##  $ Kemiskinan  : num [1:514] 19 20.4 13.2 13.4 14.4 ...
##  $ Sekolah     : num [1:514] 9.48 8.68 8.88 9.67 8.21 ...
##  $ Pengeluaran : num [1:514] 7148 8776 8180 8030 8577 ...
##  $ IPM         : num [1:514] 66.4 69.2 67.4 69.4 67.8 ...
##  $ UHH         : num [1:514] 65.3 67.4 64.4 68.2 68.7 ...
##  $ Sanitasi    : num [1:514] 71.6 69.6 62.5 62.7 66.8 ...
##  $ AirMinum    : num [1:514] 87.5 78.6 79.7 86.7 83.2 ...
##  $ Pengangguran: num [1:514] 5.71 8.36 6.46 6.43 7.13 2.61 7.09 7.7 7.28 4.32 ...
##  $ TPAK        : num [1:514] 71.2 62.9 60.9 69.6 59.5 ...
##  $ PDRB        : num [1:514] 1648096 1780419 4345784 3487157 8433526 ...
##  $ Kategori    : Ord.factor w/ 3 levels "Rendah"<"Sedang"<..: 3 3 2 2 3 3 3 3 3 2 ...
summary(data[, c(kolom_numerik, "Kategori")])
##    Kemiskinan       Sekolah        Pengeluaran         IPM       
##  Min.   : 2.38   Min.   : 1.420   Min.   : 3976   Min.   :32.84  
##  1st Qu.: 7.15   1st Qu.: 7.510   1st Qu.: 8574   1st Qu.:66.64  
##  Median :10.46   Median : 8.305   Median :10196   Median :69.61  
##  Mean   :12.27   Mean   : 8.437   Mean   :10325   Mean   :69.93  
##  3rd Qu.:14.89   3rd Qu.: 9.338   3rd Qu.:11719   3rd Qu.:73.11  
##  Max.   :41.66   Max.   :12.830   Max.   :23888   Max.   :87.18  
##       UHH           Sanitasi        AirMinum       Pengangguran   
##  Min.   :55.43   Min.   : 0.00   Min.   :  0.00   Min.   : 0.000  
##  1st Qu.:67.39   1st Qu.:70.22   1st Qu.: 79.04   1st Qu.: 3.180  
##  Median :69.97   Median :81.80   Median : 89.80   Median : 4.565  
##  Mean   :69.66   Mean   :77.20   Mean   : 85.14   Mean   : 5.059  
##  3rd Qu.:72.04   3rd Qu.:89.88   3rd Qu.: 96.40   3rd Qu.: 6.530  
##  Max.   :77.73   Max.   :99.97   Max.   :100.00   Max.   :13.370  
##       TPAK            PDRB             Kategori  
##  Min.   :56.39   Min.   :   147485   Rendah:172  
##  1st Qu.:65.07   1st Qu.:  3654292   Sedang:171  
##  Median :68.95   Median :  8814926   Tinggi:171  
##  Mean   :69.46   Mean   : 21964077               
##  3rd Qu.:72.34   3rd Qu.: 19735101               
##  Max.   :97.93   Max.   :460081046
# =========================
# EKSPLORASI DATA (EDA)
# =========================
# variabel X yang dipakai dalam model (semua kecuali Kemiskinan & IPM)
var_x <- c("Sekolah", "Pengeluaran", "UHH", "Sanitasi",
           "AirMinum", "Pengangguran", "TPAK", "PDRB")
 
# 1. distribusi kategori kemiskinan
ggplot(data, aes(x = Kategori, fill = Kategori)) +
  geom_bar() +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
  scale_fill_brewer(palette = "YlOrRd") +
  labs(title = "Distribusi Kategori Tingkat Kemiskinan Kab/Kota",
       x = "Kategori", y = "Jumlah Kab/Kota") +
  theme_minimal() +
  theme(legend.position = "none")

ggsave("01_distribusi_kategori.png", width = 6, height = 4)
 
# 2. boxplot tiap variabel X terhadap kategori Y
for (v in var_x) {
  p <- ggplot(data, aes(x = Kategori, y = .data[[v]], fill = Kategori)) +
    geom_boxplot() +
    scale_fill_brewer(palette = "YlOrRd") +
    labs(title = paste("Sebaran", v, "Menurut Kategori Kemiskinan"),
         x = "Kategori", y = v) +
    theme_minimal() +
    theme(legend.position = "none")
  ggsave(paste0("02_boxplot_", v, ".png"), p, width = 6, height = 4)
}
 
# 3. matriks korelasi antar variabel X (cek hubungan & gambaran awal multikolinearitas)
png("03_korelasi_antar_X.png", width = 800, height = 800)
corrplot(cor(data[var_x]),
         method = "color", type = "upper",
         addCoef.col = "black", tl.col = "black", tl.srt = 45,
         col = colorRampPalette(c("firebrick", "white", "steelblue"))(200))
dev.off()
## png 
##   2
# =========================
# UJI MULTIKOLINEARITAS (VIF)
# =========================
# dicek dulu dengan menyertakan IPM untuk membuktikan IPM punya VIF tinggi
model_cek_ipm <- lm(
  Kemiskinan ~ Sekolah + Pengeluaran + IPM + UHH + Sanitasi +
    AirMinum + Pengangguran + TPAK + PDRB,
  data = data
)
cat("\n=== VIF dengan IPM disertakan ===\n")
## 
## === VIF dengan IPM disertakan ===
print(vif(model_cek_ipm))
##      Sekolah  Pengeluaran          IPM          UHH     Sanitasi     AirMinum 
##     9.488522     8.580938    36.070465     4.491066     2.277657     1.637052 
## Pengangguran         TPAK         PDRB 
##     2.159531     1.951017     1.577570
# VIF setelah IPM dikeluarkan
model_cek_noipm <- lm(
  Kemiskinan ~ Sekolah + Pengeluaran + UHH + Sanitasi +
    AirMinum + Pengangguran + TPAK + PDRB,
  data = data
)
cat("\n=== VIF tanpa IPM (model final) ===\n")
## 
## === VIF tanpa IPM (model final) ===
print(vif(model_cek_noipm))
##      Sekolah  Pengeluaran          UHH     Sanitasi     AirMinum Pengangguran 
##     2.365437     2.872825     1.652151     2.206627     1.605275     2.132952 
##         TPAK         PDRB 
##     1.808182     1.443317
# VIF IPM jauh di atas 10 (indikasi multikolinearitas tinggi), sedangkan tanpa IPM seluruh VIF variabel turun jauh di bawah 10. Sehingga IPM dikeluarkan dari variabel X.
# =========================
# PEMODELAN REGRESI LOGISTIK ORDINAL
# =========================
# Variabel X memiliki skala yang sangat berbeda (mis. PDRB hingga ratusan juta, sedangkan Sekolah hanya puluhan), sehingga seluruh prediktor distandarisasi (z-score) agar estimasi model lebih stabil.
# Konsekuensinya: odds ratio nanti diinterpretasikan "per kenaikan 1 SD".
 
data_model <- data
data_model[var_x] <- scale(data_model[var_x])
 
model <- polr(
  Kategori ~ Sekolah + Pengeluaran + UHH + Sanitasi +
    AirMinum + Pengangguran + TPAK + PDRB,
  data = data_model,
  Hess = TRUE,
  method = "logistic"   # model logit kumulatif (proportional odds)
)
 
cat("\n=== Ringkasan Model Regresi Logistik Ordinal ===\n")
## 
## === Ringkasan Model Regresi Logistik Ordinal ===
print(summary(model))
## Call:
## polr(formula = Kategori ~ Sekolah + Pengeluaran + UHH + Sanitasi + 
##     AirMinum + Pengangguran + TPAK + PDRB, data = data_model, 
##     Hess = TRUE, method = "logistic")
## 
## Coefficients:
##                  Value Std. Error  t value
## Sekolah      -0.152124     0.1414 -1.07576
## Pengeluaran  -1.204868     0.1729 -6.96781
## UHH          -0.541104     0.1214 -4.45557
## Sanitasi     -0.179214     0.1451 -1.23482
## AirMinum      0.518370     0.1226  4.22943
## Pengangguran -0.006251     0.1380 -0.04528
## TPAK          0.292977     0.1299  2.25548
## PDRB         -0.023296     0.1746 -0.13346
## 
## Intercepts:
##               Value   Std. Error t value
## Rendah|Sedang -1.0052  0.1155    -8.7027
## Sedang|Tinggi  0.9925  0.1161     8.5466
## 
## Residual Deviance: 883.5517 
## AIC: 903.5517
# =========================
# UJI SIGNIFIKANSI PARAMETER (UJI WALD - PARSIAL)
# =========================
koef <- coef(summary(model))
p_value <- pnorm(abs(koef[, "t value"]), lower.tail = FALSE) * 2
tabel_wald <- cbind(koef, "p_value" = round(p_value, 4))
 
cat("\n=== Uji Wald (Parsial) ===\n")
## 
## === Uji Wald (Parsial) ===
print(round(tabel_wald, 4))
##                 Value Std. Error t value p_value
## Sekolah       -0.1521     0.1414 -1.0758  0.2820
## Pengeluaran   -1.2049     0.1729 -6.9678  0.0000
## UHH           -0.5411     0.1214 -4.4556  0.0000
## Sanitasi      -0.1792     0.1451 -1.2348  0.2169
## AirMinum       0.5184     0.1226  4.2294  0.0000
## Pengangguran  -0.0063     0.1380 -0.0453  0.9639
## TPAK           0.2930     0.1299  2.2555  0.0241
## PDRB          -0.0233     0.1746 -0.1335  0.8938
## Rendah|Sedang -1.0052     0.1155 -8.7027  0.0000
## Sedang|Tinggi  0.9925     0.1161  8.5466  0.0000
cat("\nVariabel signifikan (p-value < 0.05):\n")
## 
## Variabel signifikan (p-value < 0.05):
print(rownames(tabel_wald)[tabel_wald[, "p_value"] < 0.05 &
                              !rownames(tabel_wald) %in% c("Rendah|Sedang", "Sedang|Tinggi")])
## [1] "Pengeluaran" "UHH"         "AirMinum"    "TPAK"
# =========================
# UJI SIGNIFIKANSI MODEL (UJI G - SIMULTAN)
# =========================
model_null <- polr(Kategori ~ 1, data = data_model, Hess = TRUE)
 
G_hitung <- as.numeric(-2 * (logLik(model_null) - logLik(model)))
df_g <- length(coef(model))
p_G <- pchisq(G_hitung, df = df_g, lower.tail = FALSE)
 
cat("\n=== Uji G (Simultan) ===\n")
## 
## === Uji G (Simultan) ===
cat("G hitung  :", round(G_hitung, 4), "\n")
## G hitung  : 245.8178
cat("df        :", df_g, "\n")
## df        : 8
cat("p-value   :", format.pval(p_G, digits = 4), "\n")
## p-value   : < 2.2e-16
cat("Kesimpulan:", ifelse(p_G < 0.05, "Tolak H0, minimal ada satu variabel X yang berpengaruh signifikan terhadap Y", "Gagal tolak H0, tidak ada variabel X yang berpengaruh signifikan terhadap Y"), "\n")
## Kesimpulan: Tolak H0, minimal ada satu variabel X yang berpengaruh signifikan terhadap Y
# =========================
# UJI ASUMSI PROPORTIONAL ODDS (BRANT TEST)
# =========================
cat("\n=== Uji Proportional Odds (Brant Test) ===\n")
## 
## === Uji Proportional Odds (Brant Test) ===
brant_test <- brant(model)
## -------------------------------------------- 
## Test for X2  df  probability 
## -------------------------------------------- 
## Omnibus      21.7    8   0.01
## Sekolah      0.28    1   0.6
## Pengeluaran  0.18    1   0.67
## UHH      3.07    1   0.08
## Sanitasi 1.55    1   0.21
## AirMinum 0.33    1   0.56
## Pengangguran 1.97    1   0.16
## TPAK     4.65    1   0.03
## PDRB     1.65    1   0.2
## -------------------------------------------- 
## 
## H0: Parallel Regression Assumption holds
print(brant_test)
##                      X2 df probability
## Omnibus      21.7039635  8 0.005494826
## Sekolah       0.2823169  1 0.595186643
## Pengeluaran   0.1828822  1 0.668907945
## UHH           3.0723144  1 0.079636020
## Sanitasi      1.5452388  1 0.213839698
## AirMinum      0.3321262  1 0.564409805
## Pengangguran  1.9696357  1 0.160486533
## TPAK          4.6494064  1 0.031064287
## PDRB          1.6496895  1 0.199001197
# H0: asumsi proportional odds terpenuhi
# Jika p-value (Omnibus) > 0.05, asumsi proportional odds terpenuhi sehingga model regresi logistik ordinal layak digunakan.
# =========================
# ODDS RATIO DAN INTERPRETASI
# =========================
OR <- exp(coef(model))
CI_OR <- exp(confint(model))
## Waiting for profiling to be done...
tabel_OR <- data.frame(
  Variabel = names(OR),
  OR = round(OR, 4),
  CI_2.5 = round(CI_OR[, 1], 4),
  CI_97.5 = round(CI_OR[, 2], 4)
)
 
cat("\n=== Odds Ratio (per kenaikan 1 SD) & CI 95% ===\n")
## 
## === Odds Ratio (per kenaikan 1 SD) & CI 95% ===
print(tabel_OR, row.names = FALSE)
##      Variabel     OR CI_2.5 CI_97.5
##       Sekolah 0.8589 0.6502  1.1327
##   Pengeluaran 0.2997 0.2123  0.4185
##           UHH 0.5821 0.4573  0.7366
##      Sanitasi 0.8359 0.6277  1.1097
##      AirMinum 1.6793 1.3229  2.1407
##  Pengangguran 0.9938 0.7582  1.3033
##          TPAK 1.3404 1.0416  1.7344
##          PDRB 0.9770 0.6743  1.3308
# =========================
# EVALUASI MODEL (KLASIFIKASI & AKURASI)
# =========================
prediksi <- predict(model, type = "class")
 
tabel_klasifikasi <- table(Aktual = data_model$Kategori, Prediksi = prediksi)
cat("\n=== Tabel Klasifikasi (Aktual vs Prediksi) ===\n")
## 
## === Tabel Klasifikasi (Aktual vs Prediksi) ===
print(tabel_klasifikasi)
##         Prediksi
## Aktual   Rendah Sedang Tinggi
##   Rendah    107     47     18
##   Sedang     46     88     37
##   Tinggi     11     46    114
akurasi <- sum(diag(tabel_klasifikasi)) / sum(tabel_klasifikasi)
cat("\nAkurasi model:", percent(akurasi), "\n")
## 
## Akurasi model: 60%
# tabel hasil prediksi per kab/kota (opsional, untuk lampiran laporan)
hasil_prediksi <- data.frame(
  Provinsi = data$Provinsi,
  KabKota = data$KabKota,
  Kemiskinan_persen = data$Kemiskinan,
  Kategori_Aktual = data_model$Kategori,
  Kategori_Prediksi = prediksi
)
write.csv(hasil_prediksi, "hasil_prediksi_kabkota.csv", row.names = FALSE)
 
cat("\n=== SELESAI ===\n")
## 
## === SELESAI ===