Library

# Data manipulation & preprocessing
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.2.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(stringr)
## Warning: package 'stringr' was built under R version 4.2.3
library(forcats)
## Warning: package 'forcats' was built under R version 4.2.3
library(readr)
## Warning: package 'readr' was built under R version 4.2.3
library(readxl)
## Warning: package 'readxl' was built under R version 4.2.3
# Modeling & Evaluation
library(caret)        # Framework modeling 
## Warning: package 'caret' was built under R version 4.2.3
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.2.3
## Loading required package: lattice
library(randomForest) # Random Forest
## Warning: package 'randomForest' was built under R version 4.2.3
## randomForest 4.7-1.1
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
## 
##     margin
## The following object is masked from 'package:dplyr':
## 
##     combine
library(xgboost)      # XGBoost
## Warning: package 'xgboost' was built under R version 4.2.3
## 
## Attaching package: 'xgboost'
## The following object is masked from 'package:dplyr':
## 
##     slice
library(pROC)         # ROC Curve & AUC
## Warning: package 'pROC' was built under R version 4.2.3
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
library(smotefamily)  # SMOTE
## Warning: package 'smotefamily' was built under R version 4.2.3
# Visualization
library(ggplot2)
library(corrplot)
## Warning: package 'corrplot' was built under R version 4.2.3
## corrplot 0.92 loaded
# Preprocessing / Outlier Handling
library(DescTools)    # Winsorize
## Warning: package 'DescTools' was built under R version 4.2.3
## 
## Attaching package: 'DescTools'
## The following objects are masked from 'package:caret':
## 
##     MAE, RMSE
library(robustHD)     # Outlier detection
## Warning: package 'robustHD' was built under R version 4.2.3
## Loading required package: perry
## Warning: package 'perry' was built under R version 4.2.3
## Loading required package: parallel
## Loading required package: robustbase
## Warning: package 'robustbase' was built under R version 4.2.3

Load

rumah_tangga <- read.csv("rumah_tangga.csv")
rumah_tangga

Tahap 2

Deskriptif Numerik

summary(rumah_tangga[, c("jumlah_art", "konsumsi_listrik_kwh", "jumlah_mobil", "jumlah_motor")])
##    jumlah_art     konsumsi_listrik_kwh  jumlah_mobil     jumlah_motor  
##  Min.   : 1.000   Min.   :   0.0       Min.   :0.0000   Min.   :0.000  
##  1st Qu.: 3.000   1st Qu.: 117.0       1st Qu.:0.0000   1st Qu.:1.000  
##  Median : 4.000   Median : 211.0       Median :0.0000   Median :1.000  
##  Mean   : 4.319   Mean   : 296.9       Mean   :0.2504   Mean   :1.525  
##  3rd Qu.: 5.000   3rd Qu.: 360.0       3rd Qu.:0.0000   3rd Qu.:2.000  
##  Max.   :28.000   Max.   :6256.0       Max.   :2.0000   Max.   :7.000  
##                   NA's   :69                            NA's   :68

Deskriptif Kategorik

data_kat <- rumah_tangga %>% select(where(~is.character(.) | is.factor(.)))
data_kat <- data.frame(lapply(data_kat, as.factor))
tabel<- function(var) {
  var_data <- data_kat[[var]]
  freq_table <- table(var_data, useNA = "no")  # hanya hitung non-NA
  top_value <- names(which.max(freq_table))
  top_freq <- max(freq_table)
  data.frame(
    variable = var,
    count = sum(!is.na(var_data)),         # jumlah data non-NA
    na = sum(is.na(var_data)),             # jumlah NA
    unique = length(unique(na.omit(var_data))),
    top = top_value,
    freq = top_freq
  )
}

tabel_des <- lapply(names(data_kat), tabel) %>% bind_rows()
tabel_des

Visualisasi Data

Status Kemiskinan

#Status Kemiskinan
miskin <- rumah_tangga %>%
  count(status_miskin) %>%
  mutate(
    prop = n / sum(n),
    label = paste0(round(prop * 100, 1), "%")
  )

# Pie chart
ggplot(miskin, aes(x = "", y = prop, fill = status_miskin)) +
  geom_col(width = 1, color = "white") +
  coord_polar(theta = "y") +
  labs(title = "Proporsi Status Kemiskinan") +
  scale_fill_manual(values = c("Miskin" = "red", "Tidak Miskin" = "lightgreen")) +
  theme_void() +
  geom_text(aes(label = label),
            position = position_stack(vjust = 0.5),
            color = "black", size = 5) +
  theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold")
  )

Menunjukkan proporsi rumah tangga miskin vs tidak miskin untuk melihat apakah kelas target imbalanced (tidak seimbang).Distribusi menunjukkan bahwa sebagian besar rumah tangga tergolong tidak miskin. Rumah tangga miskin hanya sekitar ¼ dari total populasi, menandakan bahwa data bersifat imbalanced.

Sebaran Numerik

# Histogram Jumlah ART
ggplot(rumah_tangga, aes(x = jumlah_art)) +
  geom_histogram(bins = 15, fill = "skyblue", color = "white") +
  labs(title = "Distribusi Jumlah Anggota Rumah Tangga", x = "Jumlah ART", y = "Frequency") +
  theme_minimal() +
    theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold")
  )

# Histogram Konsumsi Listrik
ggplot(rumah_tangga, aes(x = konsumsi_listrik_kwh)) +
  geom_histogram(bins = 25, fill = "skyblue", color = "white") +
  labs(title = "Distribusi Konsumsi Listrik (kWh)", x = "Konsumsi Listrik (kWh)", y = "Frequency") +
  theme_minimal() +
    theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold")
  )
## Warning: Removed 69 rows containing non-finite outside the scale range
## (`stat_bin()`).

# Histogram Jumlah Mobil
ggplot(rumah_tangga, aes(x = jumlah_mobil)) +
  geom_histogram(bins = 3, fill = "skyblue", color = "white") +
  labs(title = "Distribusi Jumlah Mobil", x = "Jumlah Mobil", y = "Frequency") +
  theme_minimal() +
    theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold")
  )

# Histogram Jumlah Motor
ggplot(rumah_tangga, aes(x = jumlah_motor)) +
  geom_histogram(bins = 8, fill = "skyblue", color = "white") +
  labs(title = "Distribusi Jumlah Motor", x = "Jumlah Motor", y = "Frequency") +
  theme_minimal() +
    theme(
    plot.title = element_text(hjust = 0.5, size = 16, face = "bold")
  )
## Warning: Removed 68 rows containing non-finite outside the scale range
## (`stat_bin()`).

### Hubungan Variabel Numerik dengan Status Miskin

# Jumlah ART vs Status Miskin
ggplot(rumah_tangga, aes(x = as.factor(status_miskin), y = jumlah_art)) +
  geom_boxplot(fill = c("lightgreen", "tomato")) +
  labs(title = "Jumlah ART berdasarkan Status Kemiskinan",
       x = "Status Miskin", y = "Jumlah Anggota RT") +
  theme_minimal() + 
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"))

# Konsumsi Listrik vs Status Miskin
ggplot(rumah_tangga, aes(x = as.factor(status_miskin), y = konsumsi_listrik_kwh)) +
  geom_boxplot(fill = c("lightgreen", "tomato")) +
  labs(title = "Konsumsi Listrik berdasarkan Status Kemiskinan",
       x = "Status Miskin", y = "Konsumsi Listrik (kWh)") +
  theme_minimal() + 
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"))
## Warning: Removed 69 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

# Jumlah Mobil vs Status Miskin
ggplot(rumah_tangga, aes(x = as.factor(status_miskin), y = jumlah_mobil)) +
  geom_boxplot(fill = c("lightgreen", "tomato")) +
  labs(title = "Jumlah Mobil berdasarkan Status Kemiskinan",
       x = "Status Miskin", y = "Jumlah Mobil") +
  theme_minimal() + 
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"))

# Jumlah Motor vs Status Miskin
ggplot(rumah_tangga, aes(x = as.factor(status_miskin), y = jumlah_motor)) +
  geom_boxplot(fill = c("lightgreen", "tomato")) +
  labs(title = "Jumlah Motor berdasarkan Status Kemiskinan",
       x = "Status Miskin", y = "Jumlah Motor") +
  theme_minimal() + 
  theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"))
## Warning: Removed 68 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

Sebagian besar rumah tangga memiliki 3 hingga 5 anggota. Rumah tangga miskin cenderung memiliki jumlah anggota lebih banyak, yang tercermin dari sebaran nilai jumlah_art yang lebih tinggi. Hal ini menunjukkan beban tanggungan yang lebih besar, yang dapat memengaruhi kondisi ekonomi rumah tangga secara keseluruhan.

Distribusi konsumsi listrik menceng ke kanan (right-skewed), menunjukkan bahwa sebagian besar rumah tangga menggunakan listrik dalam jumlah kecil. Kelompok miskin memiliki konsumsi listrik yang jauh lebih rendah dibanding tidak miskin, bahkan banyak yang nol, mengindikasikan keterbatasan akses terhadap energi dasar.

Mayoritas rumah tangga tidak memiliki mobil, sedangkan motor lebih umum tetapi tidak merata. Rumah tangga miskin hampir tidak memiliki kendaraan sama sekali, menunjukkan keterbatasan mobilitas dan aset fisik. Perbedaan ini mencerminkan kesenjangan ekonomi yang cukup jelas antar kelompok.

# Korelasi
cor_data <- rumah_tangga[, c("jumlah_art", "jumlah_mobil", "jumlah_motor", "konsumsi_listrik_kwh")]
cor_matrix <- cor(cor_data, use = "complete.obs")

# Buat corrplot
corrplot(cor_matrix,
         method = "color",        # Kotak berwarna
         type = "upper",          # Tampilkan hanya bagian atas
         order = "hclust",        # Urutkan berdasarkan clustering
         addCoef.col = "black",   # Tambah nilai korelasi
         tl.col = "black",        # Warna label
         tl.srt = 45,             # Rotasi label
         col = colorRampPalette(c("red", "white", "blue"))(200),  # Skala warna
         number.cex = 0.9,        # Ukuran angka korelasi
         tl.cex = 0.9,            # Ukuran teks label
         mar = c(0,0,1,0))        # Margin judul

Terdapat korelasi positif moderat antara jumlah_motor, jumlah_mobil, dan konsumsi_listrik_kwh, menunjukkan keterkaitan antar indikator kemampuan ekonomi rumah tangga. Sementara korelasi dengan jumlah_art relatif lebih lemah.

Variabel Kategorik

kategorik <- c(
  "pendidikan_krt", "pendidikan_art_tertinggi", "pekerjaan_krt",
  "jenis_dinding", "jenis_atap", "jenis_lantai", "kepemilikan_toilet",
  "sumber_air_minum", "daya_terpasang_pln", "kepemilikan_kulkas",
  "kepemilikan_mesin_cuci", "kepemilikan_ac", "kepemilikan_komputer",
  "internet_kabel"
)

# Loop untuk buat plot per variabel
for (var in kategorik) {
  p <- ggplot(rumah_tangga, aes_string(x = var, fill = "as.factor(status_miskin)")) +
    geom_bar(position = "fill") +
    scale_fill_manual(values = c("tomato","lightgreen")) +
    labs(
      title = paste("Proporsi Status Kemiskinan menurut", gsub("_", " ", var)),
      x = gsub("_", " ", var),
      y = "Proporsi",
      fill = "Status Miskin"
    ) +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
      axis.text.x = element_text(angle = 20, hjust = 1)
    )
  
  print(p)
}
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

for (var in kategorik) {
  p <- ggplot(rumah_tangga, aes_string(x = var, fill = "as.factor(status_miskin)")) +
    geom_bar(position = "stack") +  # jumlah, bukan proporsi
    scale_fill_manual(values = c("tomato", "lightgreen")) +
    labs(
      title = paste("Jumlah Rumah Tangga menurut", gsub("_", " ", var)),
      x = gsub("_", " ", var),
      y = "Jumlah",
      fill = "Status Miskin"
    ) +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
      axis.text.x = element_text(angle = 20, hjust = 1)
    )
  
  print(p)
}

Rumah tangga miskin lebih banyak memiliki dinding dari bahan tidak permanen seperti bambu atau kayu. Sementara rumah tangga tidak miskin didominasi oleh dinding tembok atau plester. Hal ini menunjukkan bahwa kualitas tempat tinggal menjadi indikator penting kesejahteraan.

Atap dari rumbia atau seng lebih sering ditemukan pada rumah tangga miskin, sedangkan rumah tangga tidak miskin lebih banyak menggunakan genteng atau bahan atap permanen lainnya.

Rumah tangga miskin lebih banyak memiliki lantai dari tanah atau papan, sedangkan rumah tangga tidak miskin cenderung menggunakan keramik atau semen.

Rumah tangga yang tidak memiliki toilet pribadi yang layak (bersama/tidak layak) lebih sering ditemukan pada kelompok miskin. Sebaliknya, rumah tangga dengan toilet pribadi yang layak mayoritas tidak miskin. Akses terhadap fasilitas sanitasi pribadi yang layak merupakan penentu penting kesejahteraan. Ketiadaan toilet pribadi layak mencerminkan kondisi ekonomi dan kualitas hidup yang lebih rendah.

Rumah tangga yang menggunakan air tidak layak (seperti sungai, sumur terbuka) mayoritas berada dalam kelompok miskin. Keterbatasan akses terhadap air minum yang aman berkaitan erat dengan kemiskinan, baik karena faktor geografis maupun finansial.

Tingkat pendidikan kepala rumah tangga sangat berasosiasi dengan kemiskinan. Pendidikan rendah (tidak sekolah/SD) dominan pada kelompok miskin, sedangkan pendidikan menengah ke atas lebih banyak dimiliki oleh rumah tangga tidak miskin.

Jenis pekerjaan informal seperti buruh, tani, atau tidak bekerja lebih sering ditemukan pada kelompok miskin. Sebaliknya, pegawai, pedagang tetap, dan profesi lainnya lebih umum pada kelompok tidak miskin. Jenis pekerjaan mencerminkan kestabilan pendapatan.

Tahap 3

Duplikat

# Data awal
nrow(rumah_tangga)
## [1] 1366
# Cek jumlah duplikat
sum(duplicated(rumah_tangga))
## [1] 10
# Cek kolom yang duplikat
rumah_tangga[duplicated(rumah_tangga), ] %>%
  arrange(id_rt)
# Hapus duplikat
rumah_tangga <- rumah_tangga %>% 
  distinct(id_rt, .keep_all = TRUE)
# Data setelah penanganan duplikat
nrow(rumah_tangga)
## [1] 1356
rumah_tangga <- rumah_tangga %>% 
  select(-daya_terpasang_watt)

Imputasi

Imputasi Numerik

dat_clear1 <- rumah_tangga
dat_clear1$konsumsi_listrik_kwh[is.na(dat_clear1$konsumsi_listrik_kwh)] <-
  median(dat_clear1$konsumsi_listrik_kwh, na.rm = TRUE)
dat_clear1$jumlah_motor[is.na(dat_clear1$jumlah_motor)] <-
  median(dat_clear1$jumlah_motor, na.rm = TRUE)

Imputasi Kategorik

# Pendidikan ART Tertinggi
# Cek Modus
modus_art <- names(sort(table(dat_clear1$pendidikan_art_tertinggi), decreasing = TRUE))[1]
# Imputasi
dat_clear1$pendidikan_art_tertinggi[is.na(dat_clear1$pendidikan_art_tertinggi)] <- modus_art

# Cek modus 
modus_pekerjaan_krt <- names(sort(table(dat_clear1$pekerjaan_krt), decreasing = TRUE))[1]
# Imputasi
dat_clear1$pekerjaan_krt[is.na(dat_clear1$pekerjaan_krt)] <- modus_pekerjaan_krt

Cek missing value setelah imputasi

sum(is.na(dat_clear1))
## [1] 0

Outlier

p99 <- quantile(dat_clear1$konsumsi_listrik_kwh, 0.99)
dat_clear1$konsumsi_listrik_no_outlier <- pmin(dat_clear1$konsumsi_listrik_kwh, p99)
par(mfrow = c(1, 2))  # Bagi area plot menjadi 1 baris 2 kolom

boxplot(dat_clear1$konsumsi_listrik_kwh,
        main = "Sebelum Winsorizing",
        ylab = "kWh per Bulan",
        col = "skyblue")

boxplot(dat_clear1$konsumsi_listrik_no_outlier,
        main = "Sesudah Winsorizing",
        ylab = "kWh per Bulan",
        col = "tomato")

par(mfrow = c(1, 1))  # Kembalikan layout ke normal

Encoding variabel

data_encoded <- dat_clear1 %>%
  mutate(
    # Ordinal encoding
    pendidikan_krt = as.numeric(factor(pendidikan_krt,
                            levels = c("Tidak Sekolah", "SD", "SMP", "SMA", "Diploma/S1+"),
                            ordered = TRUE)),
    
    pendidikan_art_tertinggi = as.numeric(factor(pendidikan_art_tertinggi,
                                      levels = c("Tidak Sekolah", "SD", "SMP", "SMA", "Diploma/S1+"),
                                      ordered = TRUE)),
    
    daya_terpasang_pln = as.numeric(factor(daya_terpasang_pln,
                                levels = c("450", "900", "1300", "2200", ">2200"),
                                ordered = TRUE)),
    # Label encoding
    pekerjaan_krt = as.numeric(factor(pekerjaan_krt)),
    jenis_dinding = as.numeric(factor(jenis_dinding)),
    jenis_atap = as.numeric(factor(jenis_atap)),
    jenis_lantai = as.numeric(factor(jenis_lantai)),
    kepemilikan_toilet = as.numeric(factor(kepemilikan_toilet)),
    sumber_air_minum = as.numeric(factor(sumber_air_minum)),
    kepemilikan_kulkas = as.numeric(factor(kepemilikan_kulkas)),
    kepemilikan_mesin_cuci = as.numeric(factor(kepemilikan_mesin_cuci)),
    kepemilikan_ac = as.numeric(factor(kepemilikan_ac)),
    kepemilikan_komputer = as.numeric(factor(kepemilikan_komputer)),
    internet_kabel = as.numeric(factor(internet_kabel)),
    
    status_miskin = ifelse(status_miskin == "Miskin", 1, 0),
    status_miskin = factor(status_miskin,
                           levels = c(1, 0),
                           labels = c("Miskin", "Tidak_Miskin"))
  )

Kategorisasi

data_fix_clear1 <- data_encoded %>%
  mutate(
    # Kategori jumlah ART
    kategori_art = as.numeric(factor(ifelse(jumlah_art <= 4, 0, 1))),

    # Total kendaraan & keberadaan kendaraan
    total_kendaraan = jumlah_motor + jumlah_mobil,
    keberadaan_kendaraan = ifelse(total_kendaraan > 0, 1, 0),

    # Bangunan tidak permanen 
    bangunan_tidak_permanen = 
      as.integer(jenis_dinding == 1) +   # "Bambu/Lainnya"
      as.integer(jenis_lantai == 3) +    # "Tanah"
      as.integer(jenis_atap == 2),       # "Ijuk/Lainnya"

    kualitas_bangunan = as.numeric(factor(
      ifelse(bangunan_tidak_permanen == 0, 0, 1)
    )),

    # Akses layanan dasar 
    akses_layanan_dasar = as.numeric(factor(
      ifelse(kepemilikan_toilet == 2 & sumber_air_minum == 1,  # "Milik Sendiri/Layak" & "Layak"
             0, 1)
    )),

    # Kategori pendidikan KRT (sudah ordinal 1-5)
    kategori_pendidikan = as.numeric(factor(case_when(
      pendidikan_krt <= 3 ~ 1,   # Tidak Sekolah, SD, SMP
      pendidikan_krt >= 4 ~ 0),
      ordered = TRUE
    )),

    # Kategori pekerjaan KRT berdasarkan angka hasil encoding
    kategori_pekerjaan = as.numeric(factor(case_when(
      pekerjaan_krt == 4 ~ "Tidak Bekerja",   # "Tidak Bekerja"
      pekerjaan_krt %in% c(2, 3) ~ "Informal",# "Informal" dan "Pertanian"
      pekerjaan_krt == 1 ~ "Formal",          # "Formal"
    ))
    )
  )

Data Splitting

set.seed(123)
split <- createDataPartition(data_fix_clear1$status_miskin, p = 0.8, list = FALSE)
train_data <- data_fix_clear1[split, ]
test_data <- data_fix_clear1[-split, ]

SMOTE

X <- train_data[, !names(train_data) %in% c("status_miskin")]
y <- train_data$status_miskin


smote_result <- SMOTE(X = X, target = y, K = 5, dup_size = 3)  # dup_size = perc.over / 100
train_data_smote <- smote_result$data
colnames(train_data_smote)[ncol(train_data_smote)] <- "status_miskin"
train_data_smote$status_miskin <- as.factor(train_data_smote$status_miskin)

Random Forest

# Train Control
ctrl <- trainControl(
  method = "cv", number = 5,
  classProbs = TRUE,
  summaryFunction = twoClassSummary
)

# Pastikan target jadi faktor dengan 2 level
train_data_smote$status_miskin <- as.factor(train_data_smote$status_miskin)
test_data$status_miskin <- as.factor(test_data$status_miskin)
summary(train_data_smote$status_miskin)
##       Miskin Tidak_Miskin 
##          480          965
# Model Random Forest
model_rf <- train(
  status_miskin ~ keberadaan_kendaraan + konsumsi_listrik_no_outlier + daya_terpasang_pln +
    pendidikan_krt + pendidikan_art_tertinggi + pekerjaan_krt +
    kualitas_bangunan + akses_layanan_dasar,
  data = train_data_smote,
  method = "rf",
  trControl = ctrl,
  tuneLength = 5,
  metric = "ROC"
)

# Prediksi
pred_class_rf <- predict(model_rf, newdata = test_data)
conf_rf <- confusionMatrix(pred_class_rf, test_data$status_miskin, positive = "Miskin")

# Metrik evaluasi
accuracy <- conf_rf$overall["Accuracy"]
precision <- conf_rf$byClass["Pos Pred Value"]
recall <- conf_rf$byClass["Sensitivity"]
specificity <- conf_rf$byClass["Specificity"]
f1 <- conf_rf$byClass["F1"]

# Output
cat("Akurasi     :", round(accuracy, 3), "\n")
## Akurasi     : 1
cat("Presisi     :", round(precision, 3), "\n")
## Presisi     : 1
cat("Recall      :", round(recall, 3), "\n")
## Recall      : 1
cat("F1 Score    :", round(f1, 3), "\n")
## F1 Score    : 1
cat("Spesifisitas:", round(specificity, 3), "\n\n")
## Spesifisitas: 1
var_imp <- varImp(model_rf)
var_imp
## rf variable importance
## 
##                             Overall
## kualitas_bangunan            100.00
## pendidikan_krt                85.66
## akses_layanan_dasar           77.51
## daya_terpasang_pln            50.02
## konsumsi_listrik_no_outlier   38.95
## keberadaan_kendaraan          25.72
## pekerjaan_krt                  9.64
## pendidikan_art_tertinggi       0.00

XGBoost

# Pastikan target pada SMOTE juga faktor
train_data_smote$status_miskin <- as.factor(train_data_smote$status_miskin)

# Model XGBoost
model_xgb <- train(
  status_miskin ~ keberadaan_kendaraan + konsumsi_listrik_no_outlier + daya_terpasang_pln +
    pendidikan_krt + pendidikan_art_tertinggi + pekerjaan_krt +
    kualitas_bangunan + akses_layanan_dasar,
  data = train_data_smote,
  method = "xgbTree",
  trControl = ctrl,
  metric = "ROC",
  tuneGrid = expand.grid(
    nrounds = 100,
    max_depth = 3,
    eta = 0.1,
    gamma = 0,
    colsample_bytree = 0.8,
    min_child_weight = 1,
    subsample = 0.8
  )
)

# Prediksi probabilitas & kelas
pred_prob_xgb <- predict(model_xgb, newdata = test_data, type = "prob")[, "Miskin"]
pred_class_xgb <- predict(model_xgb, newdata = test_data)

# Confusion matrix
conf_xgb <- confusionMatrix(pred_class_xgb, test_data$status_miskin, positive = "Miskin")
conf_xgb$table
##               Reference
## Prediction     Miskin Tidak_Miskin
##   Miskin           30            0
##   Tidak_Miskin      0          241
# Hitung metrik
TP <- conf_xgb$table["Miskin", "Miskin"]
FP <- conf_xgb$table["Miskin", "Tidak_Miskin"]
FN <- conf_xgb$table["Tidak_Miskin", "Miskin"]
TN <- conf_xgb$table["Tidak_Miskin", "Tidak_Miskin"]

accuracy <- (TP + TN) / (TP + TN + FP + FN)
precision <- TP / (TP + FP)
recall <- TP / (TP + FN)
f1_score <- 2 * precision * recall / (precision + recall)
specificity <- TN / (TN + FP)

# Output
cat("Akurasi      :", round(accuracy, 4), "\n")
## Akurasi      : 1
cat("Presisi      :", round(precision, 4), "\n")
## Presisi      : 1
cat("Recall       :", round(recall, 4), "\n")
## Recall       : 1
cat("F1 Score     :", round(f1_score, 4), "\n")
## F1 Score     : 1
var_imp1 <- varImp(model_xgb)
var_imp1
## xgbTree variable importance
## 
##                               Overall
## kualitas_bangunan           100.00000
## pendidikan_krt               28.75217
## akses_layanan_dasar          27.12496
## daya_terpasang_pln            4.39849
## konsumsi_listrik_no_outlier   0.81043
## keberadaan_kendaraan          0.08468
## pekerjaan_krt                 0.02736
## pendidikan_art_tertinggi      0.00000