1 Persiapan Data

1.1 Load Library

# ── Manipulasi Data ───────────────────────────────────────────────
library(foreign)           # read.dbf
library(dplyr)
library(readxl)            # read_excel
library(tidyr)

# ── Visualisasi ───────────────────────────────────────────────────
library(highcharter)       # interactive charts
library(htmltools)         # tagList, HTML

# ── Pemodelan ─────────────────────────────────────────────────────
library(ResourceSelection) # Hosmer-Lemeshow
library(lmtest)            # lrtest
library(logistf)           # Firth logistic regression
library(smotefamily)       # SMOTE oversampling
library(pROC)              # ROC-AUC
library(car)               # VIF

# ── Output Tabel ──────────────────────────────────────────────────
library(knitr)
library(kableExtra)

1.2 Load Data Utama (SUSENAS 2023)

Data utama yang digunakan adalah data individu SUSENAS 2023 dalam format .dbf.

data_susenas <- read.dbf("D:/Altytan Semester 7/Skripsi Rampunggg/Data Pengolahan/46654/Susenas KOR maret 2023/bv2_2026_02_22_08_59_08_ssn202303kor_ind_1.dbf")

1.3 Filter Populasi Studi

Populasi studi adalah anak penyandang disabilitas usia 13–15 tahun yang berdomisili di Kawasan Timur Indonesia

Variabel kunci yang dibentuk pada tahap ini:

  • JUMLAH_JENIS_DISABILITAS: jumlah jenis disabilitas yang dialami anak (dari R1002–R1009).
  • status (Variabel Y): partisipasi sekolah — 1 = sedang bersekolah, 0 = tidak bersekolah.
allowed_vals <- c(1, 2, 3, 5, 6, 7)

dataku_filtered <- data_susenas %>%
  mutate(
    across(R1002:R1009, ~ as.numeric(.x)),

    JUMLAH_JENIS_DISABILITAS = rowSums(
      across(R1002:R1009, ~ .x %in% allowed_vals),
      na.rm = TRUE
    ),

    prov = as.numeric(R101),
    kabu = as.numeric(R101) * 100 + as.numeric(R102),

    # Y: Partisipasi Sekolah (1 = Bersekolah, 0 = Tidak Bersekolah)
    status = ifelse(
      R407 >= 13 & R407 <= 15 & JUMLAH_JENIS_DISABILITAS > 0,
      ifelse(R610 == 2, 1, 0),
      NA_real_
    )
  ) %>%
  filter(
    R407 >= 13 & R407 <= 15,
    JUMLAH_JENIS_DISABILITAS > 0,
    as.numeric(R101) %in% c(52:53, 71:76, 81:82, 91:97)
  )

cat("Jumlah observasi setelah filter:", nrow(dataku_filtered), "\n")
Jumlah observasi setelah filter: 236 

1.4 Variabel Kepala Rumah Tangga (X4, X5, X6)

Variabel karakteristik kepala rumah tangga (KRT) diekstrak dari baris dengan R403 == 1 (penanda KRT dalam data).

Variabel Deskripsi Kategori
X4 Jenis kelamin KRT 1 = Laki-laki; 0 = Perempuan
X5 Pendidikan KRT 1 = ≥ SMA; 0 = ≤ SMP
X6 Pekerjaan KRT 0 = Tidak bekerja; 1 = Pertanian; 2 = Nonpertanian
id_rt <- c("URUT")

data_krt <- data_susenas %>%
  filter(as.numeric(R403) == 1) %>%
  mutate(
    R405 = as.numeric(R405),
    R614 = as.numeric(R614),
    R705 = as.numeric(R705),
    R706 = as.numeric(R706),

    # X4: Jenis Kelamin KRT
    X4 = case_when(
      R405 == 1 ~ 1L,
      R405 == 2 ~ 0L,
      TRUE      ~ NA_integer_
    ),

    # X5: Tingkat Pendidikan KRT
    X5 = case_when(
      R614 >= 11 & R614 <= 24   ~ 1L,
      R614 %in% c(0:10, 25)     ~ 0L,
      TRUE                      ~ NA_integer_
    ),

    # X6: Pekerjaan KRT
    # 0 = Tidak bekerja; 1 = Pertanian; 2 = Nonpertanian
    X6 = case_when(
      R706 >= 1 & R706 <= 6     ~ 1L,
      R706 >= 7 & R706 <= 26    ~ 2L,
      R705 == 5                 ~ 0L,
      TRUE                      ~ NA_integer_
    )
  ) %>%
  select(all_of(id_rt), X4, X5, X6)

1.5 Variabel Kemiskinan Rumah Tangga (X9)

Data pengeluaran per kapita (KAPITA) dari data KP Blok 4.3 digabung dengan Garis Kemiskinan (GK) provinsi/daerah untuk membentuk variabel status kemiskinan.

files_43 <- c(
  "D:/Altytan Semester 7/Skripsi Rampunggg/Data Pengolahan/46654/Susenas KP Maret 2023/bv2_2026_02_22_08_55_02_ssn202303kp_blok43_51_94.dbf",
  "D:/Altytan Semester 7/Skripsi Rampunggg/Data Pengolahan/46654/Susenas KP Maret 2023/bv2_2026_02_22_08_49_01_ssn202303kp_blok43_11_31.dbf",
  "D:/Altytan Semester 7/Skripsi Rampunggg/Data Pengolahan/46654/Susenas KP Maret 2023/bv2_2026_02_22_08_52_06_ssn202303kp_blok43_32_36.dbf"
)

data_kp_43 <- bind_rows(lapply(files_43, read.dbf, as.is = TRUE)) %>%
  mutate(
    R101 = as.numeric(R101),
    R102 = as.numeric(R102),
    URUT = as.numeric(URUT)
  )

GK <- read_excel(
  "D:/Altytan Semester 7/Skripsi Rampunggg/Data Pengolahan/Garis Kemiskinan (Rupiah_Kapita_Bulan) Menurut Provinsi dan Daerah , 2023.xlsx",
  sheet = "Sheet2"
) %>%
  mutate(R101 = as.numeric(R101))

data_kp_x9 <- data_kp_43 %>%
  left_join(GK, by = c("R101" = "R101", "R105" = "R105")) %>%
  mutate(
    # X9: Status Kemiskinan RT (1 = Miskin, 0 = Tidak Miskin)
    X9 = if_else(KAPITA < GK, 1L, 0L, missing = NA_integer_)
  ) %>%
  select(all_of(id_rt), X9)

1.6 Penggabungan & Pembuatan Variabel Final

Semua variabel level individu dibentuk dan digabung menjadi satu dataset analisis (dataku_final).

Variabel Deskripsi Kategori
Y Partisipasi sekolah anak 1 = Bersekolah; 0 = Tidak
X1 Jenis kelamin anak 1 = Laki-laki; 0 = Perempuan
X2 Jenis disabilitas 1 = Ganda (≥2); 0 = Tunggal
X3 Tingkat kesulitan disabilitas 0 = Sangat kesulitan; 1 = Kesulitan; 2 = Sedikit
X4 Jenis kelamin KRT 1 = Laki-laki; 0 = Perempuan
X5 Pendidikan KRT 1 = ≥ SMA; 0 = ≤ SMP
X6 Pekerjaan KRT 0 = Tidak bekerja; 1 = Pertanian; 2 = Nonpertanian
X7 Daerah tempat tinggal 1 = Perkotaan; 0 = Perdesaan
X8 Kepemilikan KIP/PIP 1 = Memiliki; 0 = Tidak
X9 Status kemiskinan RT 1 = Miskin; 0 = Tidak Miskin
dataku_final <- dataku_filtered %>%
  mutate(
    across(R1002:R1009, ~ as.numeric(.x)),
    R105 = as.numeric(R105),
    R405 = as.numeric(R405),
    R615 = as.numeric(R615),
    R616 = as.numeric(R616),

    # Y: Partisipasi Sekolah
    Y  = as.integer(status),

    # X1: Jenis Kelamin Anak
    X1 = case_when(
      R405 == 1 ~ 1L,
      R405 == 2 ~ 0L,
      TRUE      ~ NA_integer_
    ),

    # X2: Jenis Disabilitas (Tunggal / Ganda)
    X2 = case_when(
      JUMLAH_JENIS_DISABILITAS > 1  ~ 1L,
      JUMLAH_JENIS_DISABILITAS == 1 ~ 0L,
      TRUE                          ~ NA_integer_
    ),

    # X3: Tingkat Kesulitan Disabilitas (prioritas terberat)
    X3 = case_when(
      rowSums(across(R1002:R1009, ~ .x %in% c(1, 5)), na.rm = TRUE) > 0 ~ 0L,
      rowSums(across(R1002:R1009, ~ .x %in% c(2, 6)), na.rm = TRUE) > 0 ~ 1L,
      rowSums(across(R1002:R1009, ~ .x %in% c(3, 7)), na.rm = TRUE) > 0 ~ 2L,
      TRUE ~ NA_integer_
    ),

    # X7: Daerah Tempat Tinggal
    X7 = case_when(
      R105 == 1 ~ 1L,
      R105 == 2 ~ 0L,
      TRUE      ~ NA_integer_
    ),

    # X8: Kepemilikan KIP/PIP
    X8 = if_else(
      R615 %in% c(1, 2) | R616 %in% c(1, 2),
      1L, 0L
    )
  ) %>%
  left_join(data_krt,   by = id_rt) %>%
  left_join(data_kp_x9, by = id_rt) %>%
  select(prov, kabu, Y, X1, X2, X3, X4, X5, X6, X7, X8, X9)

1.7 Encoding Variabel Kategorik (Factor)

dataku_final <- dataku_final %>%
  mutate(
    Y  = factor(Y,  levels = c(0, 1),    labels = c("Tidak bersekolah", "Bersekolah")),
    X1 = factor(X1, levels = c(0, 1),    labels = c("Perempuan", "Laki-laki")),
    X2 = factor(X2, levels = c(0, 1),    labels = c("Disabilitas tunggal", "Disabilitas ganda")),
    X3 = factor(X3, levels = c(0, 1, 2), labels = c("Sangat kesulitan", "Kesulitan", "Sedikit kesulitan")),
    X4 = factor(X4, levels = c(0, 1),    labels = c("Perempuan", "Laki-laki")),
    X5 = factor(X5, levels = c(0, 1),    labels = c("≤ SMP", "≥ SMA")),
    X6 = factor(X6, levels = c(0, 1, 2), labels = c("Tidak bekerja", "Pertanian", "Nonpertanian")),
    X7 = factor(X7, levels = c(0, 1),    labels = c("Perdesaan", "Perkotaan")),
    X8 = factor(X8, levels = c(0, 1),    labels = c("Tidak memiliki", "Memiliki")),
    X9 = factor(X9, levels = c(0, 1),    labels = c("Tidak miskin", "Miskin"))
  )

1.8 Referensi Variabel

data.frame(
  Kode     = c("Y", paste0("X", 1:9)),
  Nama     = c(
    "Partisipasi Sekolah",
    "Jenis Kelamin Anak",
    "Jenis Disabilitas",
    "Tingkat Kesulitan Disabilitas",
    "Jenis Kelamin KRT",
    "Pendidikan KRT",
    "Pekerjaan KRT",
    "Daerah Tempat Tinggal",
    "Kepemilikan KIP/PIP",
    "Status Kemiskinan RT"
  ),
  Kategori = c(
    "Dependen",
    rep("Karakteristik Anak", 3),
    rep("Karakteristik KRT",  3),
    rep("Sosial-Ekonomi RT",  3)
  ),
  Koding = c(
    "1 = Bersekolah; 0 = Tidak bersekolah",
    "1 = Laki-laki; 0 = Perempuan",
    "1 = Ganda (≥2 jenis); 0 = Tunggal",
    "0 = Sangat kesulitan; 1 = Kesulitan; 2 = Sedikit kesulitan",
    "1 = Laki-laki; 0 = Perempuan",
    "1 = ≥ SMA; 0 = ≤ SMP",
    "0 = Tidak bekerja; 1 = Pertanian; 2 = Nonpertanian",
    "1 = Perkotaan; 0 = Perdesaan",
    "1 = Memiliki; 0 = Tidak memiliki",
    "1 = Miskin; 0 = Tidak miskin"
  )
) %>%
  kable(caption = "Referensi Variabel Penelitian (Y dan X1–X9)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  column_spec(1, bold = TRUE, width = "4em") %>%
  column_spec(3, italic = TRUE) %>%
  row_spec(1, background = "#fff3e0")
Referensi Variabel Penelitian (Y dan X1–X9)
Kode Nama Kategori Koding
Y Partisipasi Sekolah Dependen 1 = Bersekolah; 0 = Tidak bersekolah
X1 Jenis Kelamin Anak Karakteristik Anak 1 = Laki-laki; 0 = Perempuan
X2 Jenis Disabilitas Karakteristik Anak 1 = Ganda (≥2 jenis); 0 = Tunggal
X3 Tingkat Kesulitan Disabilitas Karakteristik Anak 0 = Sangat kesulitan; 1 = Kesulitan; 2 = Sedikit kesulitan
X4 Jenis Kelamin KRT Karakteristik KRT 1 = Laki-laki; 0 = Perempuan
X5 Pendidikan KRT Karakteristik KRT 1 = ≥ SMA; 0 = ≤ SMP
X6 Pekerjaan KRT Karakteristik KRT 0 = Tidak bekerja; 1 = Pertanian; 2 = Nonpertanian
X7 Daerah Tempat Tinggal Sosial-Ekonomi RT 1 = Perkotaan; 0 = Perdesaan
X8 Kepemilikan KIP/PIP Sosial-Ekonomi RT 1 = Memiliki; 0 = Tidak memiliki
X9 Status Kemiskinan RT Sosial-Ekonomi RT 1 = Miskin; 0 = Tidak miskin

2 Eksplorasi Data

2.1 Ukuran & Struktur Dataset

cat("Jumlah observasi :", nrow(dataku_final), "\n")
Jumlah observasi : 236 
cat("Jumlah variabel  :", ncol(dataku_final), "\n\n")
Jumlah variabel  : 12 
glimpse(dataku_final)
Rows: 236
Columns: 12
$ prov <dbl> 74, 53, 74, 71, 53, 53, 52, 82, 53, 53, 74, 71, 82, 76, 71, 53, 5…
$ kabu <dbl> 7411, 5302, 7414, 7106, 5313, 5307, 5204, 8207, 5319, 5312, 7472,…
$ Y    <fct> Bersekolah, Bersekolah, Bersekolah, Bersekolah, Bersekolah, Tidak…
$ X1   <fct> Perempuan, Laki-laki, Laki-laki, Perempuan, Perempuan, Laki-laki,…
$ X2   <fct> Disabilitas ganda, Disabilitas ganda, Disabilitas tunggal, Disabi…
$ X3   <fct> Sedikit kesulitan, Sedikit kesulitan, Sedikit kesulitan, Sedikit …
$ X4   <fct> Perempuan, Laki-laki, Laki-laki, Laki-laki, Laki-laki, Laki-laki,…
$ X5   <fct> ≤ SMP, ≤ SMP, ≤ SMP, ≥ SMA, ≤ SMP, ≤ SMP, ≤ SMP, ≤ SMP, ≤ SMP, ≤ …
$ X6   <fct> Pertanian, Nonpertanian, Pertanian, Nonpertanian, Pertanian, Pert…
$ X7   <fct> Perdesaan, Perdesaan, Perdesaan, Perkotaan, Perdesaan, Perdesaan,…
$ X8   <fct> Tidak memiliki, Memiliki, Tidak memiliki, Memiliki, Memiliki, Tid…
$ X9   <fct> Tidak miskin, Tidak miskin, Miskin, Tidak miskin, Tidak miskin, T…

2.2 Statistik Deskriptif Ringkas

summary(dataku_final)
      prov            kabu                     Y               X1     
 Min.   :52.00   Min.   :5201   Tidak bersekolah: 67   Perempuan:107  
 1st Qu.:53.00   1st Qu.:5317   Bersekolah      :169   Laki-laki:129  
 Median :73.00   Median :7316                                         
 Mean   :71.29   Mean   :7147                                         
 3rd Qu.:81.00   3rd Qu.:8107                                         
 Max.   :94.00   Max.   :9436                                         
                   X2                      X3              X4          X5     
 Disabilitas tunggal:129   Sangat kesulitan : 66   Perempuan: 32   ≤ SMP:179  
 Disabilitas ganda  :107   Kesulitan        : 57   Laki-laki:204   ≥ SMA: 57  
                           Sedikit kesulitan:113                              
                                                                              
                                                                              
                                                                              
             X6              X7                   X8                 X9     
 Tidak bekerja: 20   Perdesaan:172   Tidak memiliki:200   Tidak miskin:188  
 Pertanian    :127   Perkotaan: 64   Memiliki      : 36   Miskin      : 48  
 Nonpertanian : 89                                                          
                                                                            
                                                                            
                                                                            

2.3 Pemeriksaan Missing Values

missing_tbl <- dataku_final %>%
  summarise(across(everything(), ~ sum(is.na(.)))) %>%
  pivot_longer(everything(),
               names_to  = "Variabel",
               values_to = "Jumlah_NA") %>%
  mutate(Persen_NA = round(Jumlah_NA / nrow(dataku_final) * 100, 2)) %>%
  arrange(desc(Jumlah_NA))

kable(missing_tbl,
      caption = "Jumlah dan Persentase Missing Value per Variabel",
      col.names = c("Variabel", "Jumlah NA", "% NA")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Jumlah dan Persentase Missing Value per Variabel
Variabel Jumlah NA % NA
prov 0 0
kabu 0 0
Y 0 0
X1 0 0
X2 0 0
X3 0 0
X4 0 0
X5 0 0
X6 0 0
X7 0 0
X8 0 0
X9 0 0

2.4 Distribusi Variabel Dependen (Y)

tbl_y <- dataku_final %>%
  count(Y) %>%
  mutate(Persen = round(n / sum(n) * 100, 2))

kable(tbl_y,
      caption = "Distribusi Partisipasi Sekolah Anak Disabilitas Usia 13–15 Tahun",
      col.names = c("Status Sekolah", "Frekuensi", "%")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE)
Distribusi Partisipasi Sekolah Anak Disabilitas Usia 13–15 Tahun
Status Sekolah Frekuensi %
Tidak bersekolah 67 28.39
Bersekolah 169 71.61
# ── Pie Chart Distribusi Y ──────────────────────────────────────
pd_y <- tbl_y %>%
  mutate(name = as.character(Y), y = Persen) %>%
  select(name, y)

highchart() %>%
  hc_chart(type = "pie", backgroundColor = "transparent") %>%
  hc_title(text  = "Distribusi Partisipasi Sekolah",
           style = list(fontSize = "15px", fontWeight = "700", color = "#333")) %>%
  hc_subtitle(text = "Anak Penyandang Disabilitas Usia 13\u201315 Tahun (SUSENAS 2023)",
              style = list(fontSize = "12px", color = "#666")) %>%
  hc_series(list(name = "Persentase", colorByPoint = TRUE,
                 data = list_parse(pd_y))) %>%
  hc_colors(c("#D73027","#4575B4")) %>%
  hc_plotOptions(pie = list(
    allowPointSelect = TRUE, cursor = "pointer",
    dataLabels = list(enabled = TRUE,
                      format  = "<b>{point.name}</b><br>n = {point.y:.0f}%",
                      style   = list(fontSize = "13px", textOutline = "none")),
    showInLegend = TRUE
  )) %>%
  hc_tooltip(pointFormat = "<b>{point.name}</b>: {point.y:.2f}%") %>%
  hc_exporting(enabled = TRUE, filename = "distribusi_y",
               buttons = list(contextButton = list(
                 menuItems = c("downloadPNG","downloadSVG","downloadPDF"),
                 text = "Unduh HD")))

2.5 Distribusi Variabel Independen

📈 X1: Jenis Kelamin Anak

📋 Tabulasi X1: Jenis Kelamin Anak

Distribusi X1: Jenis Kelamin Anak
Kategori Frekuensi Persen (%)
Perempuan 107 45.34
Laki-laki 129 54.66

🔗 Tabulasi X1: Jenis Kelamin Anak × Partisipasi Sekolah

Silang: X1: Jenis Kelamin Anak × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Perempuan Tidak bersekolah 33 30.84
Perempuan Bersekolah 74 69.16
Laki-laki Tidak bersekolah 34 26.36
Laki-laki Bersekolah 95 73.64

🥧 Pie Chart — X1: Jenis Kelamin Anak

📊 Stacked Bar — X1: Jenis Kelamin Anak × Partisipasi Sekolah

📈 X2: Jenis Disabilitas

📋 Tabulasi X2: Jenis Disabilitas

Distribusi X2: Jenis Disabilitas
Kategori Frekuensi Persen (%)
Disabilitas tunggal 129 54.66
Disabilitas ganda 107 45.34

🔗 Tabulasi X2: Jenis Disabilitas × Partisipasi Sekolah

Silang: X2: Jenis Disabilitas × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Disabilitas tunggal Tidak bersekolah 16 12.40
Disabilitas tunggal Bersekolah 113 87.60
Disabilitas ganda Tidak bersekolah 51 47.66
Disabilitas ganda Bersekolah 56 52.34

🥧 Pie Chart — X2: Jenis Disabilitas

📊 Stacked Bar — X2: Jenis Disabilitas × Partisipasi Sekolah

📈 X3: Tingkat Kesulitan

📋 Tabulasi X3: Tingkat Kesulitan

Distribusi X3: Tingkat Kesulitan
Kategori Frekuensi Persen (%)
Sangat kesulitan 66 27.97
Kesulitan 57 24.15
Sedikit kesulitan 113 47.88

🔗 Tabulasi X3: Tingkat Kesulitan × Partisipasi Sekolah

Silang: X3: Tingkat Kesulitan × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Sangat kesulitan Tidak bersekolah 37 56.06
Sangat kesulitan Bersekolah 29 43.94
Kesulitan Tidak bersekolah 16 28.07
Kesulitan Bersekolah 41 71.93
Sedikit kesulitan Tidak bersekolah 14 12.39
Sedikit kesulitan Bersekolah 99 87.61

🥧 Pie Chart — X3: Tingkat Kesulitan

📊 Stacked Bar — X3: Tingkat Kesulitan × Partisipasi Sekolah

📈 X4: Jenis Kelamin KRT

📋 Tabulasi X4: Jenis Kelamin KRT

Distribusi X4: Jenis Kelamin KRT
Kategori Frekuensi Persen (%)
Perempuan 32 13.56
Laki-laki 204 86.44

🔗 Tabulasi X4: Jenis Kelamin KRT × Partisipasi Sekolah

Silang: X4: Jenis Kelamin KRT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Perempuan Tidak bersekolah 5 15.62
Perempuan Bersekolah 27 84.38
Laki-laki Tidak bersekolah 62 30.39
Laki-laki Bersekolah 142 69.61

🥧 Pie Chart — X4: Jenis Kelamin KRT

📊 Stacked Bar — X4: Jenis Kelamin KRT × Partisipasi Sekolah

📈 X5: Pendidikan KRT

📋 Tabulasi X5: Pendidikan KRT

Distribusi X5: Pendidikan KRT
Kategori Frekuensi Persen (%)
≤ SMP 179 75.85
≥ SMA 57 24.15

🔗 Tabulasi X5: Pendidikan KRT × Partisipasi Sekolah

Silang: X5: Pendidikan KRT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
≤ SMP Tidak bersekolah 55 30.73
≤ SMP Bersekolah 124 69.27
≥ SMA Tidak bersekolah 12 21.05
≥ SMA Bersekolah 45 78.95

🥧 Pie Chart — X5: Pendidikan KRT

📊 Stacked Bar — X5: Pendidikan KRT × Partisipasi Sekolah

📈 X6: Pekerjaan KRT

📋 Tabulasi X6: Pekerjaan KRT

Distribusi X6: Pekerjaan KRT
Kategori Frekuensi Persen (%)
Tidak bekerja 20 8.47
Pertanian 127 53.81
Nonpertanian 89 37.71

🔗 Tabulasi X6: Pekerjaan KRT × Partisipasi Sekolah

Silang: X6: Pekerjaan KRT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Tidak bekerja Tidak bersekolah 10 50.0
Tidak bekerja Bersekolah 10 50.0
Pertanian Tidak bersekolah 40 31.5
Pertanian Bersekolah 87 68.5
Nonpertanian Tidak bersekolah 17 19.1
Nonpertanian Bersekolah 72 80.9

🥧 Pie Chart — X6: Pekerjaan KRT

📊 Stacked Bar — X6: Pekerjaan KRT × Partisipasi Sekolah

📈 X7: Daerah Tinggal

📋 Tabulasi X7: Daerah Tinggal

Distribusi X7: Daerah Tinggal
Kategori Frekuensi Persen (%)
Perdesaan 172 72.88
Perkotaan 64 27.12

🔗 Tabulasi X7: Daerah Tinggal × Partisipasi Sekolah

Silang: X7: Daerah Tinggal × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Perdesaan Tidak bersekolah 51 29.65
Perdesaan Bersekolah 121 70.35
Perkotaan Tidak bersekolah 16 25.00
Perkotaan Bersekolah 48 75.00

🥧 Pie Chart — X7: Daerah Tinggal

📊 Stacked Bar — X7: Daerah Tinggal × Partisipasi Sekolah

📈 X8: Kepemilikan KIP/PIP

📋 Tabulasi X8: Kepemilikan KIP/PIP

Distribusi X8: Kepemilikan KIP/PIP
Kategori Frekuensi Persen (%)
Tidak memiliki 200 84.75
Memiliki 36 15.25

🔗 Tabulasi X8: Kepemilikan KIP/PIP × Partisipasi Sekolah

Silang: X8: Kepemilikan KIP/PIP × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Tidak memiliki Tidak bersekolah 66 33.00
Tidak memiliki Bersekolah 134 67.00
Memiliki Tidak bersekolah 1 2.78
Memiliki Bersekolah 35 97.22

🥧 Pie Chart — X8: Kepemilikan KIP/PIP

📊 Stacked Bar — X8: Kepemilikan KIP/PIP × Partisipasi Sekolah

📈 X9: Status Kemiskinan RT

📋 Tabulasi X9: Status Kemiskinan RT

Distribusi X9: Status Kemiskinan RT
Kategori Frekuensi Persen (%)
Tidak miskin 188 79.66
Miskin 48 20.34

🔗 Tabulasi X9: Status Kemiskinan RT × Partisipasi Sekolah

Silang: X9: Status Kemiskinan RT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Tidak miskin Tidak bersekolah 51 27.13
Tidak miskin Bersekolah 137 72.87
Miskin Tidak bersekolah 16 33.33
Miskin Bersekolah 32 66.67

🥧 Pie Chart — X9: Status Kemiskinan RT

📊 Stacked Bar — X9: Status Kemiskinan RT × Partisipasi Sekolah


2.6 Distribusi Jumlah Jenis Disabilitas

tbl_dis <- dataku_filtered %>%
  count(JUMLAH_JENIS_DISABILITAS) %>%
  mutate(Persen = round(n / sum(n) * 100, 2))

kable(tbl_dis,
      caption  = "Distribusi Jumlah Jenis Disabilitas",
      col.names = c("Jumlah Jenis Disabilitas", "Frekuensi", "%")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE)
Distribusi Jumlah Jenis Disabilitas
Jumlah Jenis Disabilitas Frekuensi %
1 129 54.66
2 25 10.59
3 25 10.59
4 19 8.05
5 17 7.20
6 7 2.97
7 12 5.08
8 2 0.85
make_col(
  cats  = tbl_dis$JUMLAH_JENIS_DISABILITAS,
  vals  = tbl_dis$n,
  title = "Distribusi Jumlah Jenis Disabilitas",
  color = "#4575B4",
  xlab  = "Jumlah Jenis Disabilitas",
  ylab  = "Frekuensi"
)

2.7 Distribusi Observasi per Provinsi

tbl_prov <- dataku_final %>%
  count(prov) %>%
  mutate(Persen = round(n / sum(n) * 100, 2)) %>%
  arrange(desc(n))

kable(tbl_prov,
      caption   = "Distribusi Observasi per Provinsi",
      col.names = c("Kode Provinsi", "Frekuensi", "%")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE)
Distribusi Observasi per Provinsi
Kode Provinsi Frekuensi %
53 49 20.76
73 42 17.80
94 22 9.32
82 20 8.47
52 19 8.05
71 17 7.20
74 17 7.20
81 16 6.78
91 11 4.66
72 9 3.81
75 8 3.39
76 6 2.54
tbl_prov_sorted <- tbl_prov %>% arrange(n)

make_col(
  cats       = tbl_prov_sorted$prov,
  vals       = tbl_prov_sorted$n,
  title      = "Jumlah Observasi per Provinsi",
  color      = "#74ADD1",
  xlab       = "Kode Provinsi",
  ylab       = "Frekuensi",
  horizontal = TRUE
)

2.8 Tabulasi Silang: Partisipasi Sekolah (Y) vs Setiap Prediktor

Tabel berikut menyajikan distribusi status sekolah anak berdasarkan masing-masing variabel prediktor, beserta hasil uji Chi-Square.

vars_for_tab <- c("X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8", "X9")
label_vars   <- c(
  "Jenis Kelamin Anak (X1)",
  "Jenis Disabilitas (X2)",
  "Tingkat Kesulitan (X3)",
  "Jenis Kelamin KRT (X4)",
  "Pendidikan KRT (X5)",
  "Pekerjaan KRT (X6)",
  "Daerah Tinggal (X7)",
  "Kepemilikan KIP/PIP (X8)",
  "Status Kemiskinan RT (X9)"
)

for (i in seq_along(vars_for_tab)) {
  var <- vars_for_tab[i]
  cat("\n###", label_vars[i], "\n\n")

  df_tmp <- dataku_final %>%
    filter(!is.na(.data[[var]]), !is.na(Y))

  tab     <- table(df_tmp[[var]], df_tmp$Y)
  tab_pct <- prop.table(tab, margin = 1) * 100

  tbl_out <- as.data.frame.matrix(tab) %>%
    mutate(
      Total          = rowSums(.),
      `% Bersekolah` = round(tab_pct[, "Bersekolah"], 1)
    )

  chi_res <- tryCatch(chisq.test(tab), error = function(e) NULL)
  p_val   <- if (!is.null(chi_res)) round(chi_res$p.value, 4) else NA

  print(
    kable(tbl_out,
          caption = paste0(label_vars[i], " — Chi-Square p-value: ", p_val)) %>%
      kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                    full_width = FALSE)
  )
}

2.8.1 Jenis Kelamin Anak (X1)

Jenis Kelamin Anak (X1) — Chi-Square p-value: 0.5381
Tidak bersekolah Bersekolah Total % Bersekolah
Perempuan 33 74 107 69.2
Laki-laki 34 95 129 73.6

2.8.2 Jenis Disabilitas (X2)

Jenis Disabilitas (X2) — Chi-Square p-value: 0
Tidak bersekolah Bersekolah Total % Bersekolah
Disabilitas tunggal 16 113 129 87.6
Disabilitas ganda 51 56 107 52.3

2.8.3 Tingkat Kesulitan (X3)

Tingkat Kesulitan (X3) — Chi-Square p-value: 0
Tidak bersekolah Bersekolah Total % Bersekolah
Sangat kesulitan 37 29 66 43.9
Kesulitan 16 41 57 71.9
Sedikit kesulitan 14 99 113 87.6

2.8.4 Jenis Kelamin KRT (X4)

Jenis Kelamin KRT (X4) — Chi-Square p-value: 0.1306
Tidak bersekolah Bersekolah Total % Bersekolah
Perempuan 5 27 32 84.4
Laki-laki 62 142 204 69.6

2.8.5 Pendidikan KRT (X5)

Pendidikan KRT (X5) — Chi-Square p-value: 0.2142
Tidak bersekolah Bersekolah Total % Bersekolah
≤ SMP 55 124 179 69.3
≥ SMA 12 45 57 78.9

2.8.6 Pekerjaan KRT (X6)

Pekerjaan KRT (X6) — Chi-Square p-value: 0.0113
Tidak bersekolah Bersekolah Total % Bersekolah
Tidak bekerja 10 10 20 50.0
Pertanian 40 87 127 68.5
Nonpertanian 17 72 89 80.9

2.8.7 Daerah Tinggal (X7)

Daerah Tinggal (X7) — Chi-Square p-value: 0.5877
Tidak bersekolah Bersekolah Total % Bersekolah
Perdesaan 51 121 172 70.3
Perkotaan 16 48 64 75.0

2.8.8 Kepemilikan KIP/PIP (X8)

Kepemilikan KIP/PIP (X8) — Chi-Square p-value: 5e-04
Tidak bersekolah Bersekolah Total % Bersekolah
Tidak memiliki 66 134 200 67.0
Memiliki 1 35 36 97.2

2.8.9 Status Kemiskinan RT (X9)

Status Kemiskinan RT (X9) — Chi-Square p-value: 0.5018
Tidak bersekolah Bersekolah Total % Bersekolah
Tidak miskin 51 137 188 72.9
Miskin 16 32 48 66.7

2.9 Visualisasi: Partisipasi Sekolah per Prediktor (Stacked Bar)

📊 Jenis Kelamin Anak (X1)

📊 Jenis Disabilitas (X2)

📊 Tingkat Kesulitan (X3)

📊 Jenis Kelamin KRT (X4)

📊 Pendidikan KRT (X5)

📊 Pekerjaan KRT (X6)

📊 Daerah Tinggal (X7)

📊 Kepemilikan KIP/PIP (X8)

📊 Status Kemiskinan RT (X9)


2.10 Partisipasi Sekolah per Provinsi


2.11 Ringkasan Persentase Bersekolah per Variabel

Tabel ringkasan tingkat partisipasi sekolah (% bersekolah) untuk setiap kategori prediktor.

ringkasan <- lapply(seq_along(vars_for_tab), function(i) {
  var <- vars_for_tab[i]
  dataku_final %>%
    filter(!is.na(.data[[var]]), !is.na(Y)) %>%
    group_by(Variabel = label_vars[i], Kategori = .data[[var]]) %>%
    summarise(
      n              = n(),
      n_bersekolah   = sum(Y == "Bersekolah"),
      pct_bersekolah = round(n_bersekolah / n * 100, 1),
      .groups        = "drop"
    )
}) %>% bind_rows()

kable(ringkasan,
      caption   = "Ringkasan % Bersekolah per Kategori Prediktor",
      col.names = c("Variabel", "Kategori", "n", "n Bersekolah", "% Bersekolah")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE) %>%
  collapse_rows(columns = 1, valign = "top")
Ringkasan % Bersekolah per Kategori Prediktor
Variabel Kategori n n Bersekolah % Bersekolah
Jenis Kelamin Anak (X1) Perempuan 107 74 69.2
Laki-laki 129 95 73.6
Jenis Disabilitas (X2) Disabilitas tunggal 129 113 87.6
Disabilitas ganda 107 56 52.3
Tingkat Kesulitan (X3) Sangat kesulitan 66 29 43.9
Kesulitan 57 41 71.9
Sedikit kesulitan 113 99 87.6
Jenis Kelamin KRT (X4) Perempuan 32 27 84.4
Laki-laki 204 142 69.6
Pendidikan KRT (X5) ≤ SMP 179 124 69.3
≥ SMA 57 45 78.9
Pekerjaan KRT (X6) Tidak bekerja 20 10 50.0
Pertanian 127 87 68.5
Nonpertanian 89 72 80.9
Daerah Tinggal (X7) Perdesaan 172 121 70.3
Perkotaan 64 48 75.0
Kepemilikan KIP/PIP (X8) Tidak memiliki 200 134 67.0
Memiliki 36 35 97.2
Status Kemiskinan RT (X9) Tidak miskin 188 137 72.9
Miskin 48 32 66.7

3 Estimasi Langsung Indikator

Indikator: Persentase penyandang disabilitas usia 13–15 tahun yang sedang bersekolah, diestimasi di tingkat provinsi menggunakan metode estimasi langsung dengan desain survei SUSENAS 2023.

3.1 Pembentukan Data Indikator & Desain Survei

library(survey)

allowed_vals_ind <- c(1, 2, 3, 5, 6, 7)

# ── Bentuk data indikator dari data_susenas mentah ────────────────────────
indicator_disab <- data_susenas %>%
  mutate(
    across(R1002:R1009, ~ as.numeric(.x)),
    R101 = as.numeric(R101),
    R102 = as.numeric(R102),
    R105 = as.numeric(R105),
    R407 = as.numeric(R407),
    R610 = as.numeric(R610),
    FWT  = as.numeric(FWT),

    # Jumlah jenis disabilitas (nilai 1,2,3,5,6,7 = ada kesulitan)
    JUMLAH_DISAB = rowSums(
      across(R1002:R1009, ~ .x %in% allowed_vals_ind),
      na.rm = TRUE
    ),

    # Strata: kombinasi kode provinsi × daerah (perkotaan/perdesaan)
    STRATA_IND = paste0(sprintf("%02d", R101), R105),

    jml_pddk = 100,

    # Variabel respons: 100 = bersekolah (R610==2), 0 = tidak
    pa = ifelse(R610 == 2, 100, 0)
  ) %>%
  filter(
    R407 >= 13 & R407 <= 15,          # Usia 13–15 tahun
    JUMLAH_DISAB > 0,                 # Penyandang disabilitas
    R101 %in% c(52:53, 71:76, 81:82, 91:97)
    
  )

cat("Jumlah observasi domain indikator:", nrow(indicator_disab), "\n")
Jumlah observasi domain indikator: 236 
# ── Desain survei ─────────────────────────────────────────────────────────
options(survey.adjust.domain.lonely = TRUE)
options(survey.lonely.psu           = "adjust")

susenas_ind_design <- svydesign(
  id      = ~PSU + SSU,
  strata  = ~STRATA_IND,
  data    = indicator_disab,
  weights = ~FWT,
  nest    = TRUE
)

summary(susenas_ind_design$prob)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
0.000793 0.008747 0.017686 0.036946 0.036458 0.316958 

3.2 Estimasi Tingkat Provinsi

# ── Estimasi langsung per provinsi ────────────────────────────────────────
resultP_ind <- svyby(
  formula = ~pa,
  denom   = ~jml_pddk,
  by      = ~R101,
  design  = susenas_ind_design,
  deff    = TRUE,
  svyratio,
  vartype = c("se", "ci", "cv", "cvpct", "var")
)
resultP_ind[is.na(resultP_ind)] <- 0

# ── Hitung turunan statistik ──────────────────────────────────────────────
resultP_ind <- resultP_ind %>%
  mutate(
    theta    = round(`pa/jml_pddk` * 100, 2),
    SE       = round(`se.pa/jml_pddk` * 100, 2),
    VAR      = round(SE^2, 2),
    CI_LOWER = round(ci_l * 100, 2),
    CI_UPPER = round(ci_u * 100, 2),
    RSE      = round(`cv%`, 2),
    DEFF     = round(DEff, 2)
  )
resultP_ind$CI_LOWER[resultP_ind$CI_LOWER < 0] <- 0

# ── Label Nama Provinsi Lengkap (Kode BPS) ───────────────────────────────────
prov_label_ind <- data.frame(
  R101 = c(
    11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 
    31, 32, 33, 34, 35, 36, 
    51, 52, 53, 
    61, 62, 63, 64, 65, 
    71, 72, 73, 74, 75, 76, 
    81, 82, 
    91, 94
  ),
  Nama_Prov = c(
    "Aceh", "Sumatera Utara", "Sumatera Barat", "Riau", "Jambi", 
    "Sumatera Selatan", "Bengkulu", "Lampung", "Kepulauan Bangka Belitung", "Kepulauan Riau",
    "DKI Jakarta", "Jawa Barat", "Jawa Tengah", "DI Yogyakarta", "Jawa Timur", 
    "Banten",
    "Bali", "Nusa Tenggara Barat", "Nusa Tenggara Timur",
    "Kalimantan Barat", "Kalimantan Tengah", "Kalimantan Selatan", "Kalimantan Timur", "Kalimantan Utara",
    "Sulawesi Utara", "Sulawesi Tengah", "Sulawesi Selatan", "Sulawesi Tenggara", "Gorontalo", 
    "Sulawesi Barat",
    "Maluku", "Maluku Utara",
    "Papua Barat", "Papua"
  ),
  Wilayah = c(
    rep("Sumatera", 10),
    rep("Jawa", 6),
    "Bali", rep("Nusa Tenggara", 2),
    rep("Kalimantan", 5),
    rep("Sulawesi", 6),
    rep("Maluku", 2),
    rep("Papua", 2)
  )
)

# ── Tabel output bersih ───────────────────────────────────────────────────
outputP_ind <- resultP_ind %>%
  select(R101, theta, SE, VAR, CI_LOWER, CI_UPPER, RSE, DEFF) %>%
  rename(
    Prov      = R101,
    Estimasi  = theta,
    CI_LOWER  = CI_LOWER,
    CI_UPPER  = CI_UPPER
  ) %>%
  left_join(prov_label_ind, by = c("Prov" = "R101")) %>%
  arrange(Prov)

kable(
  outputP_ind[, c("Nama_Prov", "Wilayah", "Estimasi", "SE",
                  "CI_LOWER", "CI_UPPER", "RSE", "DEFF")],
  caption   = "Estimasi Langsung: % Penyandang Disabilitas 13–15 Tahun yang Bersekolah per Provinsi",
  col.names = c("Provinsi", "Wilayah", "Estimasi (%)", "SE",
                "CI Lower", "CI Upper", "RSE (%)", "DEFF"),
  digits    = 2
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = TRUE) %>%
  column_spec(3, bold = TRUE, color = "#1565c0") %>%
  column_spec(7, color = ifelse(outputP_ind$RSE > 25, "red", "darkgreen"),
              bold = ifelse(outputP_ind$RSE > 25, TRUE, FALSE)) %>%
  row_spec(which(outputP_ind$RSE > 25), background = "#fff3e0")
Estimasi Langsung: % Penyandang Disabilitas 13–15 Tahun yang Bersekolah per Provinsi
Provinsi Wilayah Estimasi (%) SE CI Lower CI Upper RSE (%) DEFF
Nusa Tenggara Barat Nusa Tenggara 83.39 11.17 61.49 105.29 13.40 1.63
Nusa Tenggara Timur Nusa Tenggara 76.48 8.26 60.30 92.66 10.79 1.84
Sulawesi Utara Sulawesi 82.46 10.52 61.84 103.09 12.76 1.24
Sulawesi Tengah Sulawesi 86.23 12.31 62.11 110.35 14.27 1.03
Sulawesi Selatan Sulawesi 67.28 10.36 46.97 87.59 15.40 2.01
Sulawesi Tenggara Sulawesi 29.57 12.54 4.99 54.16 42.42 1.23
Gorontalo Sulawesi 61.81 24.79 13.22 110.39 40.11 1.85
Sulawesi Barat Sulawesi 9.15 9.97 0.00 28.70 109.01 0.60
Maluku Maluku 89.16 8.66 72.20 106.13 9.71 1.17
Maluku Utara Maluku 84.10 8.34 67.75 100.45 9.92 1.01
Papua Barat Papua 84.91 10.55 64.23 105.59 12.42 0.90
Papua Papua 68.44 15.75 37.58 99.30 23.01 2.47

3.3 Visualisasi Estimasi & RSE per Provinsi

📈 Panel 1: Estimasi Angka Sekolah per Provinsi

⚠️ Panel 2: RSE Reliabilitas Data per Provinsi

📈 Estimasi Angka Sekolah per Wilayah KTI

⚠️ RSE Reliabilitas per Wilayah KTI


4 Analisis Regresi Logistik

Catatan: Semua 4 metode (Reglog Biasa, Firth, SMOTE, Class Weight) dijalankan terhadap satu dataset analisis (dataku_final). Gunakan panel filter di bawah untuk memilih metode yang ingin ditampilkan.

4.1 Fungsi Pembantu

xvars_global <- paste0("X", 1:9)

# ── Konversi kable ke string HTML ─────────────────────────────────
kbl_h <- function(df, caption = NULL) {
  knitr::kable(df, format = "html", caption = caption, escape = FALSE) %>%
    kable_styling(
      bootstrap_options = c("striped", "hover", "condensed"),
      full_width = FALSE, font_size = 13, position = "left"
    ) %>%
    as.character()
}

# ── Pra-pemrosesan data standar ───────────────────────────────────
# Y (factor "Tidak bersekolah"/"Bersekolah") dikonversi ke status_miskin 0/1
# agar semua fungsi helper berjalan langsung tanpa modifikasi tambahan
prep_data <- function(data) {
  df <- data %>%
    select(all_of(c("Y", xvars_global))) %>%
    mutate(
      status_miskin = as.integer(Y) - 1L,   # 0 = Tidak bersekolah, 1 = Bersekolah
      across(all_of(xvars_global), as.numeric)
    ) %>%
    select(-Y) %>%
    na.omit()

  konstan     <- xvars_global[sapply(xvars_global, function(v) length(unique(df[[v]])) < 2)]
  xvars_model <- setdiff(xvars_global, konstan)
  formula_mod <- as.formula(paste("status_miskin ~", paste(xvars_model, collapse = " + ")))
  list(df = df, xvars_model = xvars_model, formula_mod = formula_mod, konstan = konstan)
}

# ── Wrapper HTML untuk tiap sub-bagian ───────────────────────────
sec <- function(icon, judul, isi) {
  paste0(
    '<div style="margin-bottom:18px">',
    '<h5 style="color:#1565c0;border-bottom:2px solid #bbdefb;',
    'padding-bottom:5px;margin-top:18px;font-size:13.5px;">',
    icon, ' ', judul, '</h5>', isi, '</div>'
  )
}

# ── EDA: distribusi Y ────────────────────────────────────────────
eda_html <- function(df) {
  tbl <- table(df$status_miskin)
  pct <- round(prop.table(tbl) * 100, 2)
  kbl_h(data.frame(
    Kategori = c("Tidak Bersekolah (0)", "Bersekolah (1)"),
    N        = as.integer(c(tbl["0"], tbl["1"])),
    Persen   = paste0(c(pct["0"], pct["1"]), " %")
  ), "Distribusi Y — Partisipasi Sekolah")
}

# ── Uji Simultan (GLM-based) ─────────────────────────────────────
simultan_html <- function(model, model_null, xvars_model) {
  G2    <- model_null$deviance - model$deviance
  df_G2 <- length(xvars_model)
  chi_k <- qchisq(0.95, df_G2)
  pval  <- pchisq(G2, df_G2, lower.tail = FALSE)
  kbl_h(data.frame(
    Statistik = c("-2LL Null", "-2LL Model", "G² Hitung", "df",
                  "χ² Tabel (α=5%)", "p-value", "Keputusan"),
    Nilai = c(
      round(model_null$deviance, 3), round(model$deviance, 3),
      round(G2, 3), df_G2, round(chi_k, 3), round(pval, 4),
      ifelse(G2 > chi_k, "✅ Tolak H0 — Model Signifikan", "❌ Gagal Tolak H0")
    )
  ), "Uji Simultan (Likelihood Ratio Test / G²)")
}

# ── Uji Parsial Wald (GLM-based) ────────────────────────────────
wald_html <- function(model) {
  koef <- summary(model)$coefficients
  kbl_h(data.frame(
    Variabel   = rownames(koef),
    B          = round(koef[, 1], 4),
    SE         = round(koef[, 2], 4),
    Wald       = round((koef[, 1] / koef[, 2])^2, 4),
    `p-value`  = round(koef[, 4], 4),
    Kesimpulan = ifelse(koef[, 4] < 0.05, "✅ Signifikan", "❌ Tdk Signifikan"),
    check.names = FALSE
  ), "Uji Parsial (Wald Test)")
}

# ── Hosmer-Lemeshow ──────────────────────────────────────────────
hl_html <- function(model, y) {
  hl <- tryCatch(hoslem.test(y, fitted(model), g = 10), error = function(e) NULL)
  if (is.null(hl)) return("<p><em>⚠️ Hosmer-Lemeshow tidak dapat dihitung.</em></p>")
  kbl_h(data.frame(
    Statistik = c("χ² HL", "df", "p-value", "Keputusan"),
    Nilai = c(
      round(hl$statistic, 3), hl$parameter, round(hl$p.value, 4),
      ifelse(hl$p.value > 0.05,
             "✅ Gagal Tolak H0 — Model FIT",
             "❌ Tolak H0 — TIDAK FIT")
    )
  ), "Goodness of Fit (Hosmer-Lemeshow)")
}

# ── Odds Ratio (GLM-based) ───────────────────────────────────────
or_html <- function(model) {
  or <- exp(coef(model))
  ci <- exp(confint.default(model))
  kbl_h(data.frame(
    Variabel     = names(or),
    `exp(B)`     = round(or, 4),
    `CI 2.5%`    = round(ci[, 1], 4),
    `CI 97.5%`   = round(ci[, 2], 4),
    Interpretasi = ifelse(names(or) == "(Intercept)", "Baseline odds",
                   ifelse(or > 1,
                          "↑ Lebih berisiko tidak bersekolah",
                          "↓ Lebih kecil risiko tidak bersekolah")),
    check.names = FALSE
  ), "Interpretasi Parameter (Odds Ratio)")
}

# ── VIF (Multikolinearitas) ──────────────────────────────────────
vif_html <- function(model, df, xm, method_label = "GLM Biasa") {
  vif_val    <- tryCatch(car::vif(model), error = function(e) NULL)
  note_extra <- ""
  if (is.null(vif_val)) {
    fm_vif  <- as.formula(paste("status_miskin ~", paste(xm, collapse = " + ")))
    mdl_vif <- tryCatch(glm(fm_vif, data = df, family = binomial), error = function(e) NULL)
    vif_val <- if (!is.null(mdl_vif)) tryCatch(car::vif(mdl_vif), error = function(e) NULL) else NULL
    note_extra <- paste0(
      '<p><em><small>&#9432; VIF dihitung ulang via GLM biasa (binomial) karena model utama (',
      method_label, ') tidak kompatibel langsung dengan car::vif(). ',
      'VIF adalah properti matriks desain X — hasil tetap valid.</small></em></p>')
  }
  if (is.null(vif_val))
    return('<p><em>⚠️ VIF tidak dapat dihitung.</em></p>')

  if (is.matrix(vif_val)) {
    vif_vec  <- setNames(vif_val[, "GVIF^(1/(2*Df))"]^2, rownames(vif_val))
    col_note <- " (GVIF disesuaikan untuk faktor multi-level)"
  } else {
    vif_vec  <- vif_val
    col_note <- ""
  }

  vif_df <- data.frame(
    Variabel = names(vif_vec),
    VIF      = round(vif_vec, 4),
    Kategori = ifelse(vif_vec > 10, "❌ Parah (>10)",
               ifelse(vif_vec > 5,  "⚠️ Sedang (5–10)",
                                    "✅ Tidak Ada (<5)")),
    check.names = FALSE
  )

  status <- if (any(vif_vec > 10, na.rm = TRUE)) {
    '<p style="color:#c62828;font-weight:600;">⚠️ Terdeteksi multikolinearitas parah.</p>'
  } else if (any(vif_vec > 5, na.rm = TRUE)) {
    '<p style="color:#e65100;font-weight:600;">⚠️ Terdeteksi multikolinearitas sedang. Perlu diwaspadai.</p>'
  } else {
    '<p style="color:#2e7d32;font-weight:600;">✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).</p>'
  }

  paste0(status, note_extra, kbl_h(vif_df, paste0("Variance Inflation Factor (VIF)", col_note)))
}

# ── Kriteria Informasi: AIC / AICc / BIC ─────────────────────────
ic_html <- function(model, n, k, method = "glm") {
  if (method == "glm") {
    aic_val <- tryCatch(AIC(model),                error = function(e) NA)
    bic_val <- tryCatch(BIC(model),                error = function(e) NA)
    ll_val  <- tryCatch(as.numeric(logLik(model)), error = function(e) NA)
  } else if (method == "firth") {
    ll_val  <- max(model$loglik)
    aic_val <- -2 * ll_val + 2 * k
    bic_val <- -2 * ll_val + log(n) * k
  } else {
    aic_val <- NA; bic_val <- NA; ll_val <- NA
  }

  aicc_val <- if (!is.na(aic_val) && (n - k - 1) > 0) {
    aic_val + (2 * k^2 + 2 * k) / (n - k - 1)
  } else NA

  if (method == "quasi") {
    phi_est  <- tryCatch(summary(model)$dispersion, error = function(e) 1)
    dev_val  <- model$deviance
    df_r     <- model$df.residual
    qaic_val <- round(dev_val / phi_est + 2 * k, 3)
    rows <- paste0(
      '<tr><td><strong>QAIC</strong></td><td>', qaic_val,
      '</td><td>Quasi-AIC (φ̂ = ', round(phi_est, 3), ')</td></tr>',
      '<tr><td>Deviance Residual</td><td>', round(dev_val, 3),
      '</td><td>df = ', df_r, '</td></tr>',
      '<tr><td>Dispersi (φ̂)</td><td>', round(phi_est, 3),
      '</td><td>Estimasi dispersi quasibinomial</td></tr>',
      '<tr><td style="color:#888;font-style:italic">AIC / BIC</td>',
      '<td style="color:#888">N/A</td>',
      '<td style="color:#888;font-style:italic">Tidak terdefinisi untuk quasibinomial</td></tr>'
    )
  } else {
    rows <- paste0(
      '<tr><td><strong>AIC</strong></td><td>',  round(aic_val,  3),
      '</td><td>Semakin kecil semakin baik</td></tr>',
      '<tr><td><strong>AICc</strong></td><td>', round(aicc_val, 3),
      '</td><td>Koreksi utk sampel kecil</td></tr>',
      '<tr><td><strong>BIC</strong></td><td>',  round(bic_val,  3),
      '</td><td>Penalti lebih besar pada parameter</td></tr>',
      '<tr><td>Log-Likelihood</td><td>',         round(ll_val,  3),
      '</td><td></td></tr>'
    )
  }

  paste0(
    '<table class="table table-striped table-condensed" style="font-size:13px;width:auto;margin-bottom:0">',
    '<thead><tr>',
    '<th style="min-width:130px">Kriteria</th>',
    '<th style="min-width:90px">Nilai</th>',
    '<th>Keterangan</th></tr></thead><tbody>',
    rows,
    '<tr><td>k (parameter)</td><td>', k, '</td><td>Termasuk intercept</td></tr>',
    '<tr><td>n (observasi)</td><td>', n, '</td><td></td></tr>',
    '</tbody></table>'
  )
}

4.2 Fungsi Evaluasi Model

# ── A. Perfect Separation Check ──────────────────────────────────
separation_html <- function(coefs, ses, is_firth = FALSE) {
  if (is_firth) {
    return(paste0(
      '<div class="eval-sub">',
      '<h6>Perfect Separation Check</h6>',
      '<p class="sep-ok">&#10003; Firth (Penalized Likelihood) dirancang khusus ',
      'menangani complete/quasi-complete separation — hasil tetap valid tanpa bias.</p>',
      '</div>'
    ))
  }
  flag_b  <- abs(coefs) > 10
  flag_se <- !is.na(ses) & ses > 5
  flag    <- flag_b | flag_se
  status  <- if (any(flag, na.rm = TRUE)) {
    '<p class="sep-flag">&#9888; Terdeteksi potensi separation pada variabel berikut:</p>'
  } else {
    '<p class="sep-ok">&#10003; Tidak terdeteksi perfect/quasi-complete separation.</p>'
  }
  tbl <- kbl_h(data.frame(
    Variabel   = names(coefs),
    `|B|>10`   = ifelse(flag_b,  "&#9888; Ya", "&#10003; Tidak"),
    `SE>5`     = ifelse(flag_se, "&#9888; Ya", "&#10003; Tidak"),
    Status     = ifelse(flag,
                        '<span class="sep-flag">&#9888; Potensi Separation</span>',
                        '<span class="sep-ok">&#10003; Aman</span>'),
    check.names = FALSE
  ), "Perfect Separation Check")
  paste0('<div class="eval-sub"><h6>Perfect Separation Check</h6>', status, tbl, '</div>')
}

# ── B. Confusion Matrix + Metrics ────────────────────────────────
conf_metrics <- function(y_true, y_prob, cutoff) {
  y_pred <- as.integer(y_prob >= cutoff)
  tp <- sum(y_true == 1 & y_pred == 1)
  tn <- sum(y_true == 0 & y_pred == 0)
  fp <- sum(y_true == 0 & y_pred == 1)
  fn <- sum(y_true == 1 & y_pred == 0)
  acc  <- ifelse((tp + tn + fp + fn) > 0, (tp + tn) / (tp + tn + fp + fn), NA)
  sens <- ifelse((tp + fn) > 0, tp / (tp + fn), NA)
  spec <- ifelse((tn + fp) > 0, tn / (tn + fp), NA)
  prec <- ifelse((tp + fp) > 0, tp / (tp + fp), NA)
  f1   <- ifelse(!is.na(prec) & !is.na(sens) & (prec + sens) > 0,
                 2 * prec * sens / (prec + sens), NA)
  list(tp = tp, tn = tn, fp = fp, fn = fn,
       acc = acc, sens = sens, spec = spec, prec = prec, f1 = f1)
}

conf_block_html <- function(y_true, y_prob, cutoff, label_class, label_text) {
  m   <- conf_metrics(y_true, y_prob, cutoff)
  pct <- function(x) paste0(round(x * 100, 2), " %")
  cm_tbl <- kbl_h(data.frame(
    ` `          = c("Pred: Tidak Bersekolah (0)", "Pred: Bersekolah (1)"),
    `Aktual: 0`  = c(m$tn, m$fp),
    `Aktual: 1`  = c(m$fn, m$tp),
    check.names  = FALSE
  ), paste0("Confusion Matrix — ", label_text))
  chips <- paste0(
    '<div class="metric-row">',
    '<span class="metric-chip">Accuracy: ',    pct(m$acc),  '</span>',
    '<span class="metric-chip">Sensitivity: ', pct(m$sens), '</span>',
    '<span class="metric-chip">Specificity: ', pct(m$spec), '</span>',
    '<span class="metric-chip">Precision: ',   pct(m$prec), '</span>',
    '<span class="metric-chip">F1-Score: ',    pct(m$f1),   '</span>',
    '</div>'
  )
  paste0(
    '<div class="cm-box">',
    '<span class="cm-label ', label_class, '">', label_text, '</span>',
    cm_tbl, chips,
    '</div>'
  )
}

cm_html <- function(y_true, y_prob) {
  roc_obj <- tryCatch(pROC::roc(y_true, y_prob, quiet = TRUE), error = function(e) NULL)
  cut_you <- if (!is.null(roc_obj)) {
    co <- pROC::coords(roc_obj, "best", best.method = "youden", ret = "threshold")
    as.numeric(co[1, 1])
  } else 0.5

  b50 <- conf_block_html(y_true, y_prob, 0.5,     "cut50",  "Cutoff 0.5")
  byo <- conf_block_html(y_true, y_prob, cut_you, "cutyou",
                         paste0("Youden Optimal (", round(cut_you, 3), ")"))
  paste0('<div class="eval-sub"><h6>Confusion Matrix &amp; Metrik Klasifikasi</h6>',
         '<div class="cm-wrap">', b50, byo, '</div></div>')
}

# ── C. ROC-AUC + Toggle Plot ─────────────────────────────────────
roc_html <- function(y_true, y_prob, plot_id) {
  roc_obj <- tryCatch(pROC::roc(y_true, y_prob, quiet = TRUE), error = function(e) NULL)
  if (is.null(roc_obj))
    return('<div class="eval-sub"><h6>ROC-AUC</h6><p><em>ROC tidak dapat dihitung.</em></p></div>')

  auc_val <- as.numeric(pROC::auc(roc_obj))
  auc_cat <- if (auc_val >= 0.9) list(cls = "auc-great", txt = "Sangat Baik")
        else if (auc_val >= 0.8) list(cls = "auc-good",  txt = "Baik")
        else if (auc_val >= 0.7) list(cls = "auc-fair",  txt = "Cukup")
        else                     list(cls = "auc-poor",  txt = "Kurang")

  tmp <- tempfile(fileext = ".png")
  png(tmp, width = 400, height = 400, res = 90)
  plot(roc_obj, col = "#1565c0", lwd = 2.5,
       main = paste0("ROC Curve\nAUC = ", round(auc_val, 4)),
       cex.main = 0.9, cex.axis = 0.8, cex.lab = 0.85)
  abline(a = 0, b = 1, lty = 2, col = "gray60")
  dev.off()
  b64 <- knitr::image_uri(tmp)
  unlink(tmp)

  pid <- gsub("[^a-zA-Z0-9]", "_", plot_id)
  paste0(
    '<div class="eval-sub"><h6>ROC-AUC</h6>',
    '<span class="auc-badge ', auc_cat$cls, '">AUC = ', round(auc_val, 4), '</span>',
    '<strong>', auc_cat$txt, '</strong>',
    ' &nbsp;|&nbsp; Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | &lt;0.7 Kurang<br>',
    '<button class="btn-roc" onclick="toggleRoc(\'', pid, '\')">',
    '&#128200; Tampilkan / Sembunyikan Plot ROC</button>',
    '<div class="roc-plot-wrap" id="roc_', pid, '">',
    '<img src="', b64, '" alt="ROC Curve">',
    '</div></div>'
  )
}

# ── D. Full Evaluasi Section (GLM-based) ─────────────────────────
evaluasi_html <- function(model, y_true, y_prob, plot_id, is_firth = FALSE) {
  coefs <- coef(model)
  ses   <- tryCatch(sqrt(diag(vcov(model))), error = function(e) rep(NA, length(coefs)))
  paste0(
    '<div class="eval-wrap">',
    separation_html(coefs, ses, is_firth),
    cm_html(y_true, y_prob),
    roc_html(y_true, y_prob, plot_id),
    '</div>'
  )
}

# ── D2. Evaluasi SMOTE (2 data: asli + SMOTE) ────────────────────
evaluasi_smote_html <- function(model,
                                 y_true_ori, y_prob_ori,
                                 y_true_sm,  y_prob_sm,
                                 plot_id) {
  coefs <- coef(model)
  ses   <- tryCatch(sqrt(diag(vcov(model))), error = function(e) rep(NA, length(coefs)))
  pid   <- gsub("[^a-zA-Z0-9]", "_", plot_id)

  panel_ori <- paste0(
    '<div class="smote-panel active" id="panel_ori_', pid, '">',
    cm_html(y_true_ori, y_prob_ori),
    roc_html(y_true_ori, y_prob_ori, paste0(plot_id, "_ori")),
    '</div>'
  )
  panel_sm <- paste0(
    '<div class="smote-panel" id="panel_sm_', pid, '">',
    cm_html(y_true_sm, y_prob_sm),
    roc_html(y_true_sm, y_prob_sm, paste0(plot_id, "_smote")),
    '</div>'
  )
  tabs <- paste0(
    '<div class="smote-tabs">',
    '<button class="smote-tab-btn active" ',
    paste0('onclick="switchSmote(\'', pid, '\',\'ori\')">&#128202; Data Asli (Fair)</button>'),
    '<button class="smote-tab-btn" ',
    paste0('onclick="switchSmote(\'', pid, '\',\'sm\')">&#129512; Data SMOTE (In-Sample)</button>'),
    '</div>'
  )
  paste0(
    '<div class="eval-wrap">',
    separation_html(coefs, ses, FALSE),
    '<div class="eval-sub"><h6>Confusion Matrix, Metrik &amp; ROC — Pilih Data</h6>',
    tabs, panel_ori, panel_sm,
    '</div></div>'
  )
}

4.3 Fungsi Analisis — Reglog Biasa

run_biasa <- function(data, nama_dataset) {
  p   <- prep_data(data)
  df  <- p$df; xm <- p$xvars_model; fm <- p$formula_mod

  if (length(xm) == 0) return("<p>⚠️ Tidak ada variabel valid.</p>")

  mdl  <- tryCatch(glm(fm, data = df, family = binomial), error = function(e) NULL)
  mdl0 <- tryCatch(glm(status_miskin ~ 1, data = df, family = binomial), error = function(e) NULL)
  if (is.null(mdl)) return("<p>⚠️ Model gagal di-fit.</p>")

  info <- paste0('<p><strong>n valid:</strong> ', nrow(df), ' obs.',
    if (length(p$konstan) > 0) paste0(' | <em>Konstan (dikeluarkan): ',
                                       paste(p$konstan, collapse = ", "), '</em>') else '',
    '</p>')

  k_biasa <- length(coef(mdl))
  n_biasa <- nrow(df)
  pid     <- paste0("biasa-", gsub("[^a-zA-Z0-9]", "_", nama_dataset))

  paste0(info,
    sec("📊", "EDA — Distribusi Y",               eda_html(df)),
    sec("📐", "Uji Simultan (G²)",                simultan_html(mdl, mdl0, xm)),
    sec("📋", "Uji Parsial (Wald Test)",           wald_html(mdl)),
    sec("🔗", "Multikolinearitas (VIF)",           vif_html(mdl, df, xm, "Reglog Biasa")),
    sec("✅", "Goodness of Fit (H-L Test)",        hl_html(mdl, df$status_miskin)),
    sec("📊", "Kriteria Informasi (AIC/AICc/BIC)", ic_html(mdl, n_biasa, k_biasa, "glm")),
    sec("📈", "Interpretasi Parameter (OR)",       or_html(mdl)),
    sec("🔍", "Evaluasi Model",
        evaluasi_html(mdl, df$status_miskin, fitted(mdl), pid))
  )
}

4.4 Fungsi Analisis — Firth (Penalized Likelihood)

run_firth <- function(data, nama_dataset) {
  p  <- prep_data(data)
  df <- p$df; xm <- p$xvars_model; fm <- p$formula_mod

  if (length(xm) == 0) return("<p>⚠️ Tidak ada variabel valid.</p>")

  mdl <- tryCatch(logistf(fm, data = df), error = function(e) NULL)
  if (is.null(mdl)) return("<p>⚠️ Model Firth gagal di-fit.</p>")

  # Uji Simultan — penalized LRT
  G2     <- 2 * abs(diff(mdl$loglik))
  df_G2  <- length(xm)
  chi_k  <- qchisq(0.95, df_G2)
  pval   <- pchisq(G2, df_G2, lower.tail = FALSE)
  sim_tbl <- kbl_h(data.frame(
    Statistik = c("LogLik Null", "LogLik Model", "G² Hitung", "df",
                  "χ² Tabel (α=5%)", "p-value", "Keputusan"),
    Nilai = c(
      round(min(mdl$loglik), 3), round(max(mdl$loglik), 3),
      round(G2, 3), df_G2, round(chi_k, 3), round(pval, 4),
      ifelse(G2 > chi_k, "✅ Tolak H0 — Signifikan", "❌ Gagal Tolak H0")
    )
  ), "Uji Simultan (Penalized LRT)")

  # Koefisien & uji parsial
  coefs   <- mdl$coefficients
  se_raw  <- sqrt(diag(mdl$var))
  se      <- setNames(se_raw[seq_along(coefs)], names(coefs))
  pvals   <- mdl$prob[names(coefs)]
  chi_sq  <- (coefs / se)^2

  wald_tbl <- kbl_h(data.frame(
    Variabel   = names(coefs),
    B          = round(coefs, 4),
    SE         = round(se, 4),
    `Chi-sq`   = round(chi_sq, 4),
    `p-value`  = round(pvals, 4),
    Kesimpulan = ifelse(pvals < 0.05, "✅ Signifikan", "❌ Tdk Signifikan"),
    check.names = FALSE
  ), "Uji Per Variabel (Penalized LRT)")

  # Odds Ratio dengan Firth CI
  or_tbl <- kbl_h(data.frame(
    Variabel     = names(coefs),
    `exp(B)`     = round(exp(coefs), 4),
    `CI 2.5%`    = round(exp(mdl$ci.lower), 4),
    `CI 97.5%`   = round(exp(mdl$ci.upper), 4),
    Interpretasi = ifelse(names(coefs) == "(Intercept)", "Baseline odds",
                   ifelse(exp(coefs) > 1,
                          "↑ Lebih berisiko tidak bersekolah",
                          "↓ Lebih kecil risiko tidak bersekolah")),
    check.names = FALSE
  ), "Interpretasi Parameter (Odds Ratio — Firth CI)")

  info <- paste0('<p><strong>n valid:</strong> ', nrow(df),
    ' obs. | <em>Firth menangani quasi-complete separation & sampel kecil</em></p>')

  # GoF Hosmer-Lemeshow
  hl_firth <- tryCatch(
    hoslem.test(df$status_miskin, mdl$predict, g = 10),
    error = function(e) NULL
  )
  hl_firth_tbl <- if (is.null(hl_firth)) {
    "<p><em>⚠️ Hosmer-Lemeshow tidak dapat dihitung.</em></p>"
  } else {
    kbl_h(data.frame(
      Statistik = c("χ² HL", "df", "p-value", "Keputusan"),
      Nilai = c(
        round(hl_firth$statistic, 3),
        hl_firth$parameter,
        round(hl_firth$p.value, 4),
        ifelse(hl_firth$p.value > 0.05,
               "✅ Gagal Tolak H0 — Model FIT",
               "❌ Tolak H0 — TIDAK FIT")
      )
    ), "Goodness of Fit (Hosmer-Lemeshow)")
  }

  k_firth <- length(coefs)
  n_firth <- nrow(df)
  pid     <- paste0("firth-", gsub("[^a-zA-Z0-9]", "_", nama_dataset))

  paste0(info,
    sec("📊", "EDA — Distribusi Y",                     eda_html(df)),
    sec("📐", "Uji Simultan (Penalized LRT)",            sim_tbl),
    sec("📋", "Uji Per Variabel",                        wald_tbl),
    sec("🔗", "Multikolinearitas (VIF)",                 vif_html(mdl, df, xm, "Firth")),
    sec("✅", "Goodness of Fit (H-L Test)",              hl_firth_tbl),
    sec("📊", "Kriteria Informasi (AIC/AICc/BIC)",       ic_html(mdl, n_firth, k_firth, "firth")),
    sec("📈", "Interpretasi Parameter (OR — Firth CI)",  or_tbl),
    sec("🔍", "Evaluasi Model",
        evaluasi_html(mdl, df$status_miskin, mdl$predict, pid, is_firth = TRUE))
  )
}

4.5 Fungsi Analisis — SMOTE

run_smote <- function(data, nama_dataset) {
  p  <- prep_data(data)
  df <- p$df; xm <- p$xvars_model; fm <- p$formula_mod

  if (length(xm) == 0) return("<p>⚠️ Tidak ada variabel valid.</p>")

  df_sm <- tryCatch({
    res <- smotefamily::SMOTE(
      X        = df[, xm, drop = FALSE],
      target   = as.character(df$status_miskin),
      K        = 5,
      dup_size = 0
    )$data
    res$status_miskin <- as.numeric(as.character(res$class))
    res$class         <- NULL
    res[, c("status_miskin", xm)]
  }, error = function(e) NULL)

  if (is.null(df_sm))
    return("<p>⚠️ SMOTE gagal — kemungkinan n terlalu kecil atau kelas sangat tidak seimbang.</p>")

  mdl  <- tryCatch(glm(fm, data = df_sm, family = binomial), error = function(e) NULL)
  mdl0 <- tryCatch(glm(status_miskin ~ 1, data = df_sm, family = binomial), error = function(e) NULL)
  if (is.null(mdl)) return("<p>⚠️ Model GLM setelah SMOTE gagal.</p>")

  # EDA before & after
  t_ori   <- table(df$status_miskin)
  t_sm    <- table(df_sm$status_miskin)
  eda_tbl <- kbl_h(data.frame(
    Kategori  = c("Tidak Bersekolah (0)", "Bersekolah (1)"),
    `N Asal`  = as.integer(c(t_ori["0"], t_ori["1"])),
    `N SMOTE` = as.integer(c(t_sm["0"], t_sm["1"])),
    check.names = FALSE
  ), "Distribusi Y Sebelum & Sesudah SMOTE")

  info <- paste0('<p><strong>n asli:</strong> ', nrow(df),
    ' | <strong>n setelah SMOTE:</strong> ', nrow(df_sm), '</p>')

  k_smote <- length(coef(mdl))
  n_smote <- nrow(df_sm)
  pid     <- paste0("smote-", gsub("[^a-zA-Z0-9]", "_", nama_dataset))

  paste0(info,
    sec("📊", "EDA — Distribusi Y (Sebelum & Sesudah SMOTE)", eda_tbl),
    sec("📐", "Uji Simultan (G²)",       simultan_html(mdl, mdl0, xm)),
    sec("📋", "Uji Parsial (Wald Test)",  wald_html(mdl)),
    sec("🔗", "Multikolinearitas (VIF)",
        paste0(vif_html(mdl, df_sm, xm, "SMOTE"),
               '<p><em><small>&#9432; VIF dihitung dari data SMOTE (in-sample). ',
               'VIF pada data asli mungkin sedikit berbeda.</small></em></p>')),
    sec("✅", "Goodness of Fit (H-L Test)", hl_html(mdl, df_sm$status_miskin)),
    sec("📊", "Kriteria Informasi (AIC/AICc/BIC)",
        paste0(ic_html(mdl, n_smote, k_smote, "glm"),
               '<p><em><small>&#9432; AIC/AICc/BIC dihitung dari data SMOTE (n = ',
               n_smote, '). Gunakan sebagai perbandingan; evaluasi fair tetap di data asli.</small></em></p>')),
    sec("📈", "Interpretasi Parameter (OR)", or_html(mdl)),
    sec("🔍", "Evaluasi Model",
        evaluasi_smote_html(
          mdl,
          df$status_miskin,    predict(mdl, newdata = df,    type = "response"),
          df_sm$status_miskin, predict(mdl, newdata = df_sm, type = "response"),
          pid
        ))
  )
}

5 Hasil Analisis Interaktif

🔧 Filter Hasil Analisis

Metode Analisis

Menghitung...
⚠ Tidak ada hasil yang sesuai pilihan.
Centang minimal satu Metode.

Disabilitas 13–15 Thn | SUSENAS 2023

Reglog Biasa

Reglog Biasa

n valid: 236 obs.

📊 EDA — Distribusi Y
Distribusi Y — Partisipasi Sekolah
Kategori N Persen
Tidak Bersekolah (0) 67 28.39 %
Bersekolah (1) 169 71.61 %
📐 Uji Simultan (G²)
Uji Simultan (Likelihood Ratio Test / G²)
Statistik Nilai
-2LL Null 281.594
-2LL Model 204.405
G² Hitung 77.189
df 9
χ² Tabel (α=5%) 16.919
p-value 0
Keputusan ✅ Tolak H0 — Model Signifikan
📋 Uji Parsial (Wald Test)
Uji Parsial (Wald Test)
Variabel B SE Wald p-value Kesimpulan
(Intercept) (Intercept) -1.9156 2.0977 0.8339 0.3611 ❌ Tdk Signifikan
X1 X1 0.6439 0.3643 3.1229 0.0772 ❌ Tdk Signifikan
X2 X2 -1.1148 0.4089 7.4315 0.0064 ✅ Signifikan
X3 X3 0.7509 0.2356 10.1546 0.0014 ✅ Signifikan
X4 X4 -1.7244 0.6272 7.5580 0.0060 ✅ Signifikan
X5 X5 0.4960 0.4503 1.2136 0.2706 ❌ Tdk Signifikan
X6 X6 0.8563 0.3125 7.5056 0.0062 ✅ Signifikan
X7 X7 -0.0336 0.4307 0.0061 0.9378 ❌ Tdk Signifikan
X8 X8 2.4671 1.0554 5.4641 0.0194 ✅ Signifikan
X9 X9 0.1283 0.4368 0.0863 0.7689 ❌ Tdk Signifikan
🔗 Multikolinearitas (VIF)

✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).

Variance Inflation Factor (VIF)
Variabel VIF Kategori
X1 X1 1.0884 ✅ Tidak Ada (<5)
X2 X2 1.2992 ✅ Tidak Ada (<5)
X3 X3 1.3200 ✅ Tidak Ada (<5)
X4 X4 1.2163 ✅ Tidak Ada (<5)
X5 X5 1.1190 ✅ Tidak Ada (<5)
X6 X6 1.2421 ✅ Tidak Ada (<5)
X7 X7 1.1447 ✅ Tidak Ada (<5)
X8 X8 1.0149 ✅ Tidak Ada (<5)
X9 X9 1.0786 ✅ Tidak Ada (<5)
✅ Goodness of Fit (H-L Test)
Goodness of Fit (Hosmer-Lemeshow)
Statistik Nilai
χ² HL 5.188
df 8
p-value 0.7373
Keputusan ✅ Gagal Tolak H0 — Model FIT
📊 Kriteria Informasi (AIC/AICc/BIC)
KriteriaNilaiKeterangan
AIC224.405Semakin kecil semakin baik
AICc225.383Koreksi utk sampel kecil
BIC259.043Penalti lebih besar pada parameter
Log-Likelihood-102.202
k (parameter)10Termasuk intercept
n (observasi)236
📈 Interpretasi Parameter (OR)
Interpretasi Parameter (Odds Ratio)
Variabel exp(B) CI 2.5% CI 97.5% Interpretasi
(Intercept) (Intercept) 0.1473 0.0024 8.9878 Baseline odds
X1 X1 1.9038 0.9322 3.8882 ↑ Lebih berisiko tidak bersekolah
X2 X2 0.3280 0.1471 0.7310 ↓ Lebih kecil risiko tidak bersekolah
X3 X3 2.1189 1.3352 3.3628 ↑ Lebih berisiko tidak bersekolah
X4 X4 0.1783 0.0521 0.6096 ↓ Lebih kecil risiko tidak bersekolah
X5 X5 1.6422 0.6795 3.9690 ↑ Lebih berisiko tidak bersekolah
X6 X6 2.3543 1.2759 4.3442 ↑ Lebih berisiko tidak bersekolah
X7 X7 0.9670 0.4157 2.2490 ↓ Lebih kecil risiko tidak bersekolah
X8 X8 11.7884 1.4896 93.2922 ↑ Lebih berisiko tidak bersekolah
X9 X9 1.1369 0.4830 2.6763 ↑ Lebih berisiko tidak bersekolah
🔍 Evaluasi Model
Perfect Separation Check

✓ Tidak terdeteksi perfect/quasi-complete separation.

Perfect Separation Check
Variabel |B|>10 SE>5 Status
(Intercept) (Intercept) ✓ Tidak ✓ Tidak ✓ Aman
X1 X1 ✓ Tidak ✓ Tidak ✓ Aman
X2 X2 ✓ Tidak ✓ Tidak ✓ Aman
X3 X3 ✓ Tidak ✓ Tidak ✓ Aman
X4 X4 ✓ Tidak ✓ Tidak ✓ Aman
X5 X5 ✓ Tidak ✓ Tidak ✓ Aman
X6 X6 ✓ Tidak ✓ Tidak ✓ Aman
X7 X7 ✓ Tidak ✓ Tidak ✓ Aman
X8 X8 ✓ Tidak ✓ Tidak ✓ Aman
X9 X9 ✓ Tidak ✓ Tidak ✓ Aman
Confusion Matrix & Metrik Klasifikasi
Cutoff 0.5
Confusion Matrix — Cutoff 0.5
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 36 14
Pred: Bersekolah (1) 31 155
Accuracy: 80.93 %Sensitivity: 91.72 %Specificity: 53.73 %Precision: 83.33 %F1-Score: 87.32 %
Youden Optimal (0.688)
Confusion Matrix — Youden Optimal (0.688)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 51 36
Pred: Bersekolah (1) 16 133
Accuracy: 77.97 %Sensitivity: 78.7 %Specificity: 76.12 %Precision: 89.26 %F1-Score: 83.65 %
ROC-AUC
AUC = 0.8382Baik  |  Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | <0.7 Kurang
ROC Curve

Disabilitas 13–15 Thn | SUSENAS 2023

Firth

Firth

n valid: 236 obs. | Firth menangani quasi-complete separation & sampel kecil

📊 EDA — Distribusi Y
Distribusi Y — Partisipasi Sekolah
Kategori N Persen
Tidak Bersekolah (0) 67 28.39 %
Bersekolah (1) 169 71.61 %
📐 Uji Simultan (Penalized LRT)
Uji Simultan (Penalized LRT)
Statistik Nilai
LogLik Null -128.542
LogLik Model -92.506
G² Hitung 72.071
df 9
χ² Tabel (α=5%) 16.919
p-value 0
Keputusan ✅ Tolak H0 — Signifikan
📋 Uji Per Variabel
Uji Per Variabel (Penalized LRT)
Variabel B SE Chi-sq p-value Kesimpulan
(Intercept) (Intercept) -1.5967 1.9068 0.7012 0.4110 ❌ Tdk Signifikan
X1 X1 0.6125 0.3472 3.1131 0.0799 ❌ Tdk Signifikan
X2 X2 -1.0584 0.3873 7.4686 0.0070 ✅ Signifikan
X3 X3 0.7111 0.2236 10.1092 0.0016 ✅ Signifikan
X4 X4 -1.5803 0.5837 7.3309 0.0042 ✅ Signifikan
X5 X5 0.4627 0.4276 1.1710 0.2820 ❌ Tdk Signifikan
X6 X6 0.8043 0.2965 7.3601 0.0063 ✅ Signifikan
X7 X7 -0.0312 0.4091 0.0058 0.9402 ❌ Tdk Signifikan
X8 X8 2.0455 0.8474 5.8265 0.0030 ✅ Signifikan
X9 X9 0.1145 0.4159 0.0758 0.7861 ❌ Tdk Signifikan
🔗 Multikolinearitas (VIF)

✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).

ⓘ VIF dihitung ulang via GLM biasa (binomial) karena model utama (Firth) tidak kompatibel langsung dengan car::vif(). VIF adalah properti matriks desain X — hasil tetap valid.

Variance Inflation Factor (VIF)
Variabel VIF Kategori
X1 X1 1.0884 ✅ Tidak Ada (<5)
X2 X2 1.2992 ✅ Tidak Ada (<5)
X3 X3 1.3200 ✅ Tidak Ada (<5)
X4 X4 1.2163 ✅ Tidak Ada (<5)
X5 X5 1.1190 ✅ Tidak Ada (<5)
X6 X6 1.2421 ✅ Tidak Ada (<5)
X7 X7 1.1447 ✅ Tidak Ada (<5)
X8 X8 1.0149 ✅ Tidak Ada (<5)
X9 X9 1.0786 ✅ Tidak Ada (<5)
✅ Goodness of Fit (H-L Test)
Goodness of Fit (Hosmer-Lemeshow)
Statistik Nilai
χ² HL 5.076
df 8
p-value 0.7494
Keputusan ✅ Gagal Tolak H0 — Model FIT
📊 Kriteria Informasi (AIC/AICc/BIC)
KriteriaNilaiKeterangan
AIC205.012Semakin kecil semakin baik
AICc205.99Koreksi utk sampel kecil
BIC239.65Penalti lebih besar pada parameter
Log-Likelihood-92.506
k (parameter)10Termasuk intercept
n (observasi)236
📈 Interpretasi Parameter (OR — Firth CI)
Interpretasi Parameter (Odds Ratio — Firth CI)
Variabel exp(B) CI 2.5% CI 97.5% Interpretasi
(Intercept) (Intercept) 0.2026 0.0040 9.1739 Baseline odds
X1 X1 1.8451 0.9300 3.7417 ↑ Lebih berisiko tidak bersekolah
X2 X2 0.3470 0.1577 0.7484 ↓ Lebih kecil risiko tidak bersekolah
X3 X3 2.0362 1.3089 3.2073 ↑ Lebih berisiko tidak bersekolah
X4 X4 0.2059 0.0582 0.6205 ↓ Lebih kecil risiko tidak bersekolah
X5 X5 1.5884 0.6877 3.8349 ↑ Lebih berisiko tidak bersekolah
X6 X6 2.2351 1.2517 4.1185 ↑ Lebih berisiko tidak bersekolah
X7 X7 0.9692 0.4288 2.2191 ↓ Lebih kecil risiko tidak bersekolah
X8 X8 7.7332 1.8283 72.0571 ↑ Lebih berisiko tidak bersekolah
X9 X9 1.1213 0.4939 2.6134 ↑ Lebih berisiko tidak bersekolah
🔍 Evaluasi Model
Perfect Separation Check

✓ Firth (Penalized Likelihood) dirancang khusus menangani complete/quasi-complete separation — hasil tetap valid tanpa bias.

Confusion Matrix & Metrik Klasifikasi
Cutoff 0.5
Confusion Matrix — Cutoff 0.5
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 36 14
Pred: Bersekolah (1) 31 155
Accuracy: 80.93 %Sensitivity: 91.72 %Specificity: 53.73 %Precision: 83.33 %F1-Score: 87.32 %
Youden Optimal (0.687)
Confusion Matrix — Youden Optimal (0.687)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 52 39
Pred: Bersekolah (1) 15 130
Accuracy: 77.12 %Sensitivity: 76.92 %Specificity: 77.61 %Precision: 89.66 %F1-Score: 82.8 %
ROC-AUC
AUC = 0.8389Baik  |  Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | <0.7 Kurang
ROC Curve

Disabilitas 13–15 Thn | SUSENAS 2023

SMOTE

SMOTE

n asli: 236 | n setelah SMOTE: 303

📊 EDA — Distribusi Y (Sebelum & Sesudah SMOTE)
Distribusi Y Sebelum & Sesudah SMOTE
Kategori N Asal N SMOTE
Tidak Bersekolah (0) 67 134
Bersekolah (1) 169 169
📐 Uji Simultan (G²)
Uji Simultan (Likelihood Ratio Test / G²)
Statistik Nilai
-2LL Null 415.995
-2LL Model 288.258
G² Hitung 127.737
df 9
χ² Tabel (α=5%) 16.919
p-value 0
Keputusan ✅ Tolak H0 — Model Signifikan
📋 Uji Parsial (Wald Test)
Uji Parsial (Wald Test)
Variabel B SE Wald p-value Kesimpulan
(Intercept) (Intercept) -2.7304 1.7468 2.4433 0.1180 ❌ Tdk Signifikan
X1 X1 0.7081 0.3164 5.0095 0.0252 ✅ Signifikan
X2 X2 -1.2143 0.3513 11.9501 0.0005 ✅ Signifikan
X3 X3 0.6975 0.2067 11.3820 0.0007 ✅ Signifikan
X4 X4 -2.1299 0.5440 15.3265 0.0001 ✅ Signifikan
X5 X5 0.8289 0.3990 4.3146 0.0378 ✅ Signifikan
X6 X6 0.9109 0.2748 10.9893 0.0009 ✅ Signifikan
X7 X7 0.0034 0.3737 0.0001 0.9928 ❌ Tdk Signifikan
X8 X8 2.8762 0.8742 10.8254 0.0010 ✅ Signifikan
X9 X9 0.1963 0.3762 0.2721 0.6019 ❌ Tdk Signifikan
🔗 Multikolinearitas (VIF)

✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).

Variance Inflation Factor (VIF)
Variabel VIF Kategori
X1 X1 1.1499 ✅ Tidak Ada (<5)
X2 X2 1.4118 ✅ Tidak Ada (<5)
X3 X3 1.4342 ✅ Tidak Ada (<5)
X4 X4 1.2555 ✅ Tidak Ada (<5)
X5 X5 1.1609 ✅ Tidak Ada (<5)
X6 X6 1.2999 ✅ Tidak Ada (<5)
X7 X7 1.1720 ✅ Tidak Ada (<5)
X8 X8 1.0181 ✅ Tidak Ada (<5)
X9 X9 1.0728 ✅ Tidak Ada (<5)

ⓘ VIF dihitung dari data SMOTE (in-sample). VIF pada data asli mungkin sedikit berbeda.

✅ Goodness of Fit (H-L Test)
Goodness of Fit (Hosmer-Lemeshow)
Statistik Nilai
χ² HL 6.425
df 8
p-value 0.5997
Keputusan ✅ Gagal Tolak H0 — Model FIT
📊 Kriteria Informasi (AIC/AICc/BIC)
KriteriaNilaiKeterangan
AIC308.258Semakin kecil semakin baik
AICc309.012Koreksi utk sampel kecil
BIC345.396Penalti lebih besar pada parameter
Log-Likelihood-144.129
k (parameter)10Termasuk intercept
n (observasi)303

ⓘ AIC/AICc/BIC dihitung dari data SMOTE (n = 303). Gunakan sebagai perbandingan; evaluasi fair tetap di data asli.

📈 Interpretasi Parameter (OR)
Interpretasi Parameter (Odds Ratio)
Variabel exp(B) CI 2.5% CI 97.5% Interpretasi
(Intercept) (Intercept) 0.0652 0.0021 2.0001 Baseline odds
X1 X1 2.0302 1.0920 3.7745 ↑ Lebih berisiko tidak bersekolah
X2 X2 0.2969 0.1492 0.5911 ↓ Lebih kecil risiko tidak bersekolah
X3 X3 2.0087 1.3395 3.0122 ↑ Lebih berisiko tidak bersekolah
X4 X4 0.1189 0.0409 0.3452 ↓ Lebih kecil risiko tidak bersekolah
X5 X5 2.2907 1.0479 5.0077 ↑ Lebih berisiko tidak bersekolah
X6 X6 2.4865 1.4511 4.2608 ↑ Lebih berisiko tidak bersekolah
X7 X7 1.0034 0.4824 2.0870 ↑ Lebih berisiko tidak bersekolah
X8 X8 17.7468 3.1990 98.4507 ↑ Lebih berisiko tidak bersekolah
X9 X9 1.2168 0.5821 2.5438 ↑ Lebih berisiko tidak bersekolah
🔍 Evaluasi Model
Perfect Separation Check

✓ Tidak terdeteksi perfect/quasi-complete separation.

Perfect Separation Check
Variabel |B|>10 SE>5 Status
(Intercept) (Intercept) ✓ Tidak ✓ Tidak ✓ Aman
X1 X1 ✓ Tidak ✓ Tidak ✓ Aman
X2 X2 ✓ Tidak ✓ Tidak ✓ Aman
X3 X3 ✓ Tidak ✓ Tidak ✓ Aman
X4 X4 ✓ Tidak ✓ Tidak ✓ Aman
X5 X5 ✓ Tidak ✓ Tidak ✓ Aman
X6 X6 ✓ Tidak ✓ Tidak ✓ Aman
X7 X7 ✓ Tidak ✓ Tidak ✓ Aman
X8 X8 ✓ Tidak ✓ Tidak ✓ Aman
X9 X9 ✓ Tidak ✓ Tidak ✓ Aman
Confusion Matrix, Metrik & ROC — Pilih Data
Confusion Matrix & Metrik Klasifikasi
Cutoff 0.5
Confusion Matrix — Cutoff 0.5
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 45 34
Pred: Bersekolah (1) 22 135
Accuracy: 76.27 %Sensitivity: 79.88 %Specificity: 67.16 %Precision: 85.99 %F1-Score: 82.82 %
Youden Optimal (0.58)
Confusion Matrix — Youden Optimal (0.58)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 54 47
Pred: Bersekolah (1) 13 122
Accuracy: 74.58 %Sensitivity: 72.19 %Specificity: 80.6 %Precision: 90.37 %F1-Score: 80.26 %
ROC-AUC
AUC = 0.8357Baik  |  Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | <0.7 Kurang
ROC Curve
Confusion Matrix & Metrik Klasifikasi
Cutoff 0.5
Confusion Matrix — Cutoff 0.5
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 94 34
Pred: Bersekolah (1) 40 135
Accuracy: 75.58 %Sensitivity: 79.88 %Specificity: 70.15 %Precision: 77.14 %F1-Score: 78.49 %
Youden Optimal (0.7)
Confusion Matrix — Youden Optimal (0.7)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 121 59
Pred: Bersekolah (1) 13 110
Accuracy: 76.24 %Sensitivity: 65.09 %Specificity: 90.3 %Precision: 89.43 %F1-Score: 75.34 %
ROC-AUC
AUC = 0.8465Baik  |  Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | <0.7 Kurang
ROC Curve