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("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 di 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) >= 10 & as.numeric(R101) < 100
  )

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

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 2 = Non-pertanian; 1 = Pertanian; 0 = Tidak bekerja
id_rt <- c("URUT")

data_krt <- data_susenas %>%
  filter(as.numeric(R403) == 1) %>%
  mutate(
    R405 = as.numeric(R405),
    R614 = as.numeric(R614),
    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
    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(
  "46654/Susenas KP Maret 2023/bv2_2026_02_22_08_55_02_ssn202303kp_blok43_51_94.dbf",
  "46654/Susenas KP Maret 2023/bv2_2026_02_22_08_49_01_ssn202303kp_blok43_11_31.dbf",
  "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(
  "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 2 = Non-pertanian; 1 = Pertanian; 0 = Tidak bekerja
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 = Non-pertanian; 2 = Pertanian",
    "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 = Non-pertanian; 2 = Pertanian
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 : 761 
cat("Jumlah variabel  :", ncol(dataku_final), "\n\n")
Jumlah variabel  : 12 
glimpse(dataku_final)
Rows: 761
Columns: 12
$ prov <dbl> 33, 74, 31, 31, 65, 31, 53, 74, 71, 31, 62, 64, 17, 15, 64, 33, 5…
$ kabu <dbl> 3375, 7411, 3172, 3172, 6571, 3171, 5302, 7414, 7106, 3171, 6201,…
$ Y    <fct> Bersekolah, Bersekolah, Bersekolah, Tidak bersekolah, Bersekolah,…
$ X1   <fct> Laki-laki, Perempuan, Perempuan, Perempuan, Laki-laki, Laki-laki,…
$ X2   <fct> Disabilitas ganda, Disabilitas ganda, Disabilitas tunggal, Disabi…
$ X3   <fct> Sangat kesulitan, Sedikit kesulitan, Sedikit kesulitan, Sedikit k…
$ X4   <fct> Laki-laki, Perempuan, Perempuan, Laki-laki, Laki-laki, Laki-laki,…
$ X5   <fct> ≥ SMA, ≤ SMP, ≤ SMP, ≥ SMA, ≤ SMP, ≥ SMA, ≤ SMP, ≤ SMP, ≥ SMA, ≥ …
$ X6   <fct> Nonpertanian, Pertanian, Tidak bekerja, Nonpertanian, Pertanian, …
$ X7   <fct> Perkotaan, Perdesaan, Perkotaan, Perkotaan, Perkotaan, Perkotaan,…
$ X8   <fct> Memiliki, Tidak memiliki, Tidak memiliki, Tidak memiliki, Tidak m…
$ X9   <fct> Tidak miskin, Tidak miskin, Tidak miskin, Tidak miskin, Tidak mis…

2.2 Statistik Deskriptif Ringkas

summary(dataku_final)
      prov            kabu                     Y               X1     
 Min.   :11.00   Min.   :1102   Tidak bersekolah:164   Perempuan:340  
 1st Qu.:16.00   1st Qu.:1674   Bersekolah      :597   Laki-laki:421  
 Median :35.00   Median :3501                                         
 Mean   :41.69   Mean   :4192                                         
 3rd Qu.:63.00   3rd Qu.:6310                                         
 Max.   :94.00   Max.   :9436                                         
                   X2                      X3              X4          X5     
 Disabilitas tunggal:400   Sangat kesulitan :209   Perempuan:101   ≤ SMP:493  
 Disabilitas ganda  :361   Kesulitan        :168   Laki-laki:660   ≥ SMA:268  
                           Sedikit kesulitan:384                              
                                                                              
                                                                              
                                                                              
             X6              X7                   X8                 X9     
 Tidak bekerja: 58   Perdesaan:418   Tidak memiliki:622   Tidak miskin:654  
 Pertanian    :320   Perkotaan:343   Memiliki      :139   Miskin      :107  
 Nonpertanian :383                                                          
                                                                            
                                                                            
                                                                            

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 164 21.55
Bersekolah 597 78.45
# ── 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 340 44.68
Laki-laki 421 55.32

🔗 Tabulasi X1: Jenis Kelamin Anak × Partisipasi Sekolah

Silang: X1: Jenis Kelamin Anak × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Perempuan Tidak bersekolah 71 20.88
Perempuan Bersekolah 269 79.12
Laki-laki Tidak bersekolah 93 22.09
Laki-laki Bersekolah 328 77.91

🥧 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 400 52.56
Disabilitas ganda 361 47.44

🔗 Tabulasi X2: Jenis Disabilitas × Partisipasi Sekolah

Silang: X2: Jenis Disabilitas × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Disabilitas tunggal Tidak bersekolah 38 9.5
Disabilitas tunggal Bersekolah 362 90.5
Disabilitas ganda Tidak bersekolah 126 34.9
Disabilitas ganda Bersekolah 235 65.1

🥧 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 209 27.46
Kesulitan 168 22.08
Sedikit kesulitan 384 50.46

🔗 Tabulasi X3: Tingkat Kesulitan × Partisipasi Sekolah

Silang: X3: Tingkat Kesulitan × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Sangat kesulitan Tidak bersekolah 81 38.76
Sangat kesulitan Bersekolah 128 61.24
Kesulitan Tidak bersekolah 36 21.43
Kesulitan Bersekolah 132 78.57
Sedikit kesulitan Tidak bersekolah 47 12.24
Sedikit kesulitan Bersekolah 337 87.76

🥧 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 101 13.27
Laki-laki 660 86.73

🔗 Tabulasi X4: Jenis Kelamin KRT × Partisipasi Sekolah

Silang: X4: Jenis Kelamin KRT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Perempuan Tidak bersekolah 17 16.83
Perempuan Bersekolah 84 83.17
Laki-laki Tidak bersekolah 147 22.27
Laki-laki Bersekolah 513 77.73

🥧 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 493 64.78
≥ SMA 268 35.22

🔗 Tabulasi X5: Pendidikan KRT × Partisipasi Sekolah

Silang: X5: Pendidikan KRT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
≤ SMP Tidak bersekolah 122 24.75
≤ SMP Bersekolah 371 75.25
≥ SMA Tidak bersekolah 42 15.67
≥ SMA Bersekolah 226 84.33

🥧 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 58 7.62
Pertanian 320 42.05
Nonpertanian 383 50.33

🔗 Tabulasi X6: Pekerjaan KRT × Partisipasi Sekolah

Silang: X6: Pekerjaan KRT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Tidak bekerja Tidak bersekolah 17 29.31
Tidak bekerja Bersekolah 41 70.69
Pertanian Tidak bersekolah 78 24.38
Pertanian Bersekolah 242 75.62
Nonpertanian Tidak bersekolah 69 18.02
Nonpertanian Bersekolah 314 81.98

🥧 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 418 54.93
Perkotaan 343 45.07

🔗 Tabulasi X7: Daerah Tinggal × Partisipasi Sekolah

Silang: X7: Daerah Tinggal × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Perdesaan Tidak bersekolah 108 25.84
Perdesaan Bersekolah 310 74.16
Perkotaan Tidak bersekolah 56 16.33
Perkotaan Bersekolah 287 83.67

🥧 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 622 81.73
Memiliki 139 18.27

🔗 Tabulasi X8: Kepemilikan KIP/PIP × Partisipasi Sekolah

Silang: X8: Kepemilikan KIP/PIP × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Tidak memiliki Tidak bersekolah 159 25.56
Tidak memiliki Bersekolah 463 74.44
Memiliki Tidak bersekolah 5 3.60
Memiliki Bersekolah 134 96.40

🥧 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 654 85.94
Miskin 107 14.06

🔗 Tabulasi X9: Status Kemiskinan RT × Partisipasi Sekolah

Silang: X9: Status Kemiskinan RT × Partisipasi Sekolah
Kategori Partisipasi Sekolah Frekuensi Persen (%)
Tidak miskin Tidak bersekolah 140 21.41
Tidak miskin Bersekolah 514 78.59
Miskin Tidak bersekolah 24 22.43
Miskin Bersekolah 83 77.57

🥧 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 400 52.56
2 88 11.56
3 88 11.56
4 82 10.78
5 40 5.26
6 31 4.07
7 24 3.15
8 8 1.05
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 %
12 61 8.02
32 50 6.57
33 50 6.57
53 49 6.44
35 48 6.31
11 45 5.91
73 42 5.52
13 36 4.73
16 24 3.15
18 22 2.89
31 22 2.89
94 22 2.89
63 21 2.76
82 20 2.63
52 19 2.50
14 18 2.37
17 17 2.23
62 17 2.23
71 17 2.23
74 17 2.23
81 16 2.10
61 15 1.97
51 14 1.84
36 13 1.71
21 11 1.45
64 11 1.45
91 11 1.45
15 10 1.31
19 10 1.31
72 9 1.18
75 8 1.05
65 6 0.79
76 6 0.79
34 4 0.53
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.7533
Tidak bersekolah Bersekolah Total % Bersekolah
Perempuan 71 269 340 79.1
Laki-laki 93 328 421 77.9

2.8.2 Jenis Disabilitas (X2)

Jenis Disabilitas (X2) — Chi-Square p-value: 0
Tidak bersekolah Bersekolah Total % Bersekolah
Disabilitas tunggal 38 362 400 90.5
Disabilitas ganda 126 235 361 65.1

2.8.3 Tingkat Kesulitan (X3)

Tingkat Kesulitan (X3) — Chi-Square p-value: 0
Tidak bersekolah Bersekolah Total % Bersekolah
Sangat kesulitan 81 128 209 61.2
Kesulitan 36 132 168 78.6
Sedikit kesulitan 47 337 384 87.8

2.8.4 Jenis Kelamin KRT (X4)

Jenis Kelamin KRT (X4) — Chi-Square p-value: 0.2676
Tidak bersekolah Bersekolah Total % Bersekolah
Perempuan 17 84 101 83.2
Laki-laki 147 513 660 77.7

2.8.5 Pendidikan KRT (X5)

Pendidikan KRT (X5) — Chi-Square p-value: 0.0049
Tidak bersekolah Bersekolah Total % Bersekolah
≤ SMP 122 371 493 75.3
≥ SMA 42 226 268 84.3

2.8.6 Pekerjaan KRT (X6)

Pekerjaan KRT (X6) — Chi-Square p-value: 0.0406
Tidak bersekolah Bersekolah Total % Bersekolah
Tidak bekerja 17 41 58 70.7
Pertanian 78 242 320 75.6
Nonpertanian 69 314 383 82.0

2.8.7 Daerah Tinggal (X7)

Daerah Tinggal (X7) — Chi-Square p-value: 0.002
Tidak bersekolah Bersekolah Total % Bersekolah
Perdesaan 108 310 418 74.2
Perkotaan 56 287 343 83.7

2.8.8 Kepemilikan KIP/PIP (X8)

Kepemilikan KIP/PIP (X8) — Chi-Square p-value: 0
Tidak bersekolah Bersekolah Total % Bersekolah
Tidak memiliki 159 463 622 74.4
Memiliki 5 134 139 96.4

2.8.9 Status Kemiskinan RT (X9)

Status Kemiskinan RT (X9) — Chi-Square p-value: 0.911
Tidak bersekolah Bersekolah Total % Bersekolah
Tidak miskin 140 514 654 78.6
Miskin 24 83 107 77.6

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 340 269 79.1
Laki-laki 421 328 77.9
Jenis Disabilitas (X2) Disabilitas tunggal 400 362 90.5
Disabilitas ganda 361 235 65.1
Tingkat Kesulitan (X3) Sangat kesulitan 209 128 61.2
Kesulitan 168 132 78.6
Sedikit kesulitan 384 337 87.8
Jenis Kelamin KRT (X4) Perempuan 101 84 83.2
Laki-laki 660 513 77.7
Pendidikan KRT (X5) ≤ SMP 493 371 75.3
≥ SMA 268 226 84.3
Pekerjaan KRT (X6) Tidak bekerja 58 41 70.7
Pertanian 320 242 75.6
Nonpertanian 383 314 82.0
Daerah Tinggal (X7) Perdesaan 418 310 74.2
Perkotaan 343 287 83.7
Kepemilikan KIP/PIP (X8) Tidak memiliki 622 463 74.4
Memiliki 139 134 96.4
Status Kemiskinan RT (X9) Tidak miskin 654 514 78.6
Miskin 107 83 77.6

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
    
  )

cat("Jumlah observasi domain indikator:", nrow(indicator_disab), "\n")
Jumlah observasi domain indikator: 761 
# ── 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.0004872 0.0039392 0.0095784 0.0265952 0.0230631 0.3169584 

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),
    rep("Nusa Tenggara & Bali", 3),
    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
Aceh Sumatera 93.03 5.39 82.47 103.58 5.79 1.99
Sumatera Utara Sumatera 70.71 11.22 48.72 92.71 15.87 3.67
Sumatera Barat Sumatera 89.86 5.59 78.90 100.82 6.22 1.21
Riau Sumatera 71.42 14.18 43.62 99.22 19.86 1.68
Jambi Sumatera 82.29 14.30 54.26 110.31 17.38 1.27
Sumatera Selatan Sumatera 60.03 15.86 28.95 91.12 26.42 2.43
Bengkulu Sumatera 96.01 4.17 87.84 104.18 4.34 0.73
Lampung Sumatera 63.22 12.73 38.27 88.18 20.14 1.47
Kepulauan Bangka Belitung Sumatera 52.73 19.23 15.04 90.42 36.47 1.35
Kepulauan Riau Sumatera 99.62 0.31 99.01 100.23 0.31 0.03
DKI Jakarta Jawa 87.99 11.51 65.43 110.54 13.08 2.64
Jawa Barat Jawa 79.23 8.76 62.07 96.40 11.05 2.29
Jawa Tengah Jawa 78.26 9.28 60.07 96.45 11.86 2.49
DI Yogyakarta Jawa 100.00 0.00 100.00 100.00 0.00 0.00
Jawa Timur Jawa 82.50 8.37 66.09 98.91 10.15 2.29
Banten Jawa 84.36 11.76 61.30 107.41 13.95 1.26
Bali Nusa Tenggara & Bali 78.10 15.59 47.55 108.65 19.96 1.86
Nusa Tenggara Barat Nusa Tenggara & Bali 83.39 11.17 61.49 105.29 13.40 1.63
Nusa Tenggara Timur Nusa Tenggara & Bali 76.48 8.26 60.30 92.66 10.79 1.84
Kalimantan Barat Kalimantan 68.91 12.11 45.17 92.66 17.58 0.97
Kalimantan Tengah Kalimantan 88.74 6.02 76.94 100.54 6.79 0.59
Kalimantan Selatan Kalimantan 49.54 14.75 20.62 78.45 29.78 1.75
Kalimantan Timur Kalimantan 93.18 5.85 81.71 104.65 6.28 0.54
Kalimantan Utara Kalimantan 99.18 0.93 97.36 101.01 0.94 0.05
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 Pulau

⚠️ RSE Reliabilitas per Pulau


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: 761 obs.

📊 EDA — Distribusi Y
Distribusi Y — Partisipasi Sekolah
Kategori N Persen
Tidak Bersekolah (0) 164 21.55 %
Bersekolah (1) 597 78.45 %
📐 Uji Simultan (G²)
Uji Simultan (Likelihood Ratio Test / G²)
Statistik Nilai
-2LL Null 793.207
-2LL Model 663.733
G² Hitung 129.474
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) -0.6295 1.1164 0.3179 0.5729 ❌ Tdk Signifikan
X1 X1 0.0203 0.1953 0.0108 0.9172 ❌ Tdk Signifikan
X2 X2 -1.1183 0.2319 23.2517 0.0000 ✅ Signifikan
X3 X3 0.4015 0.1253 10.2712 0.0014 ✅ Signifikan
X4 X4 -0.6258 0.3129 3.9992 0.0455 ✅ Signifikan
X5 X5 0.4846 0.2248 4.6455 0.0311 ✅ Signifikan
X6 X6 0.2822 0.1648 2.9334 0.0868 ❌ Tdk Signifikan
X7 X7 0.3708 0.2133 3.0206 0.0822 ❌ Tdk Signifikan
X8 X8 1.7770 0.4769 13.8842 0.0002 ✅ Signifikan
X9 X9 0.1974 0.2813 0.4922 0.4830 ❌ Tdk Signifikan
🔗 Multikolinearitas (VIF)

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

Variance Inflation Factor (VIF)
Variabel VIF Kategori
X1 X1 1.0096 ✅ Tidak Ada (<5)
X2 X2 1.2244 ✅ Tidak Ada (<5)
X3 X3 1.2190 ✅ Tidak Ada (<5)
X4 X4 1.0682 ✅ Tidak Ada (<5)
X5 X5 1.1254 ✅ Tidak Ada (<5)
X6 X6 1.1726 ✅ Tidak Ada (<5)
X7 X7 1.1520 ✅ Tidak Ada (<5)
X8 X8 1.0218 ✅ Tidak Ada (<5)
X9 X9 1.0449 ✅ Tidak Ada (<5)
✅ Goodness of Fit (H-L Test)
Goodness of Fit (Hosmer-Lemeshow)
Statistik Nilai
χ² HL 2.162
df 8
p-value 0.9756
Keputusan ✅ Gagal Tolak H0 — Model FIT
📊 Kriteria Informasi (AIC/AICc/BIC)
KriteriaNilaiKeterangan
AIC683.733Semakin kecil semakin baik
AICc684.026Koreksi utk sampel kecil
BIC730.079Penalti lebih besar pada parameter
Log-Likelihood-331.866
k (parameter)10Termasuk intercept
n (observasi)761
📈 Interpretasi Parameter (OR)
Interpretasi Parameter (Odds Ratio)
Variabel exp(B) CI 2.5% CI 97.5% Interpretasi
(Intercept) (Intercept) 0.5329 0.0598 4.7520 Baseline odds
X1 X1 1.0205 0.6959 1.4966 ↑ Lebih berisiko tidak bersekolah
X2 X2 0.3269 0.2075 0.5149 ↓ Lebih kecil risiko tidak bersekolah
X3 X3 1.4940 1.1688 1.9098 ↑ Lebih berisiko tidak bersekolah
X4 X4 0.5349 0.2897 0.9876 ↓ Lebih kecil risiko tidak bersekolah
X5 X5 1.6236 1.0449 2.5226 ↑ Lebih berisiko tidak bersekolah
X6 X6 1.3260 0.9601 1.8314 ↑ Lebih berisiko tidak bersekolah
X7 X7 1.4489 0.9538 2.2010 ↑ Lebih berisiko tidak bersekolah
X8 X8 5.9121 2.3217 15.0550 ↑ Lebih berisiko tidak bersekolah
X9 X9 1.2182 0.7018 2.1145 ↑ 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) 38 18
Pred: Bersekolah (1) 126 579
Accuracy: 81.08 %Sensitivity: 96.98 %Specificity: 23.17 %Precision: 82.13 %F1-Score: 88.94 %
Youden Optimal (0.786)
Confusion Matrix — Youden Optimal (0.786)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 122 197
Pred: Bersekolah (1) 42 400
Accuracy: 68.59 %Sensitivity: 67 %Specificity: 74.39 %Precision: 90.5 %F1-Score: 77 %
ROC-AUC
AUC = 0.7745Cukup  |  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: 761 obs. | Firth menangani quasi-complete separation & sampel kecil

📊 EDA — Distribusi Y
Distribusi Y — Partisipasi Sekolah
Kategori N Persen
Tidak Bersekolah (0) 164 21.55 %
Bersekolah (1) 597 78.45 %
📐 Uji Simultan (Penalized LRT)
Uji Simultan (Penalized LRT)
Statistik Nilai
LogLik Null -379.169
LogLik Model -316.051
G² Hitung 126.236
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) -0.5777 1.0877 0.2821 0.5984 ❌ Tdk Signifikan
X1 X1 0.0207 0.1924 0.0116 0.9149 ❌ Tdk Signifikan
X2 X2 -1.0980 0.2276 23.2683 0.0000 ✅ Signifikan
X3 X3 0.3955 0.1232 10.3048 0.0014 ✅ Signifikan
X4 X4 -0.5983 0.3058 3.8271 0.0444 ✅ Signifikan
X5 X5 0.4742 0.2213 4.5937 0.0311 ✅ Signifikan
X6 X6 0.2786 0.1619 2.9613 0.0886 ❌ Tdk Signifikan
X7 X7 0.3631 0.2099 2.9918 0.0843 ❌ Tdk Signifikan
X8 X8 1.6795 0.4504 13.9026 0.0000 ✅ Signifikan
X9 X9 0.1849 0.2761 0.4485 0.5025 ❌ 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.0096 ✅ Tidak Ada (<5)
X2 X2 1.2244 ✅ Tidak Ada (<5)
X3 X3 1.2190 ✅ Tidak Ada (<5)
X4 X4 1.0682 ✅ Tidak Ada (<5)
X5 X5 1.1254 ✅ Tidak Ada (<5)
X6 X6 1.1726 ✅ Tidak Ada (<5)
X7 X7 1.1520 ✅ Tidak Ada (<5)
X8 X8 1.0218 ✅ Tidak Ada (<5)
X9 X9 1.0449 ✅ Tidak Ada (<5)
✅ Goodness of Fit (H-L Test)
Goodness of Fit (Hosmer-Lemeshow)
Statistik Nilai
χ² HL 3.256
df 8
p-value 0.9173
Keputusan ✅ Gagal Tolak H0 — Model FIT
📊 Kriteria Informasi (AIC/AICc/BIC)
KriteriaNilaiKeterangan
AIC652.102Semakin kecil semakin baik
AICc652.395Koreksi utk sampel kecil
BIC698.448Penalti lebih besar pada parameter
Log-Likelihood-316.051
k (parameter)10Termasuk intercept
n (observasi)761
📈 Interpretasi Parameter (OR — Firth CI)
Interpretasi Parameter (Odds Ratio — Firth CI)
Variabel exp(B) CI 2.5% CI 97.5% Interpretasi
(Intercept) (Intercept) 0.5612 0.0647 4.8329 Baseline odds
X1 X1 1.0209 0.6977 1.4915 ↑ Lebih berisiko tidak bersekolah
X2 X2 0.3335 0.2110 0.5198 ↓ Lebih kecil risiko tidak bersekolah
X3 X3 1.4852 1.1648 1.8959 ↑ Lebih berisiko tidak bersekolah
X4 X4 0.5498 0.2929 0.9855 ↓ Lebih kecil risiko tidak bersekolah
X5 X5 1.6068 1.0435 2.5026 ↑ Lebih berisiko tidak bersekolah
X6 X6 1.3212 0.9586 1.8185 ↑ Lebih berisiko tidak bersekolah
X7 X7 1.4378 0.9522 2.1832 ↑ Lebih berisiko tidak bersekolah
X8 X8 5.3628 2.3889 14.7876 ↑ Lebih berisiko tidak bersekolah
X9 X9 1.2031 0.7059 2.1052 ↑ 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) 38 18
Pred: Bersekolah (1) 126 579
Accuracy: 81.08 %Sensitivity: 96.98 %Specificity: 23.17 %Precision: 82.13 %F1-Score: 88.94 %
Youden Optimal (0.781)
Confusion Matrix — Youden Optimal (0.781)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 122 197
Pred: Bersekolah (1) 42 400
Accuracy: 68.59 %Sensitivity: 67 %Specificity: 74.39 %Precision: 90.5 %F1-Score: 77 %
ROC-AUC
AUC = 0.7743Cukup  |  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: 761 | n setelah SMOTE: 1089

📊 EDA — Distribusi Y (Sebelum & Sesudah SMOTE)
Distribusi Y Sebelum & Sesudah SMOTE
Kategori N Asal N SMOTE
Tidak Bersekolah (0) 164 492
Bersekolah (1) 597 597
📐 Uji Simultan (G²)
Uji Simultan (Likelihood Ratio Test / G²)
Statistik Nilai
-2LL Null 1499.535
-2LL Model 1188.42
G² Hitung 311.115
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.0183 0.8341 5.8556 0.0155 ✅ Signifikan
X1 X1 0.0605 0.1451 0.1738 0.6767 ❌ Tdk Signifikan
X2 X2 -1.2594 0.1683 56.0165 0.0000 ✅ Signifikan
X3 X3 0.3324 0.0926 12.8946 0.0003 ✅ Signifikan
X4 X4 -0.7323 0.2399 9.3207 0.0023 ✅ Signifikan
X5 X5 0.6949 0.1671 17.2955 0.0000 ✅ Signifikan
X6 X6 0.1169 0.1252 0.8727 0.3502 ❌ Tdk Signifikan
X7 X7 0.4624 0.1614 8.2056 0.0042 ✅ Signifikan
X8 X8 2.2894 0.3634 39.6825 0.0000 ✅ Signifikan
X9 X9 0.4521 0.2254 4.0223 0.0449 ✅ Signifikan
🔗 Multikolinearitas (VIF)

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

Variance Inflation Factor (VIF)
Variabel VIF Kategori
X1 X1 1.0310 ✅ Tidak Ada (<5)
X2 X2 1.2895 ✅ Tidak Ada (<5)
X3 X3 1.2959 ✅ Tidak Ada (<5)
X4 X4 1.0748 ✅ Tidak Ada (<5)
X5 X5 1.1698 ✅ Tidak Ada (<5)
X6 X6 1.2214 ✅ Tidak Ada (<5)
X7 X7 1.1934 ✅ Tidak Ada (<5)
X8 X8 1.0147 ✅ Tidak Ada (<5)
X9 X9 1.0599 ✅ 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 3.007
df 8
p-value 0.9339
Keputusan ✅ Gagal Tolak H0 — Model FIT
📊 Kriteria Informasi (AIC/AICc/BIC)
KriteriaNilaiKeterangan
AIC1208.42Semakin kecil semakin baik
AICc1208.624Koreksi utk sampel kecil
BIC1258.35Penalti lebih besar pada parameter
Log-Likelihood-594.21
k (parameter)10Termasuk intercept
n (observasi)1089

ⓘ AIC/AICc/BIC dihitung dari data SMOTE (n = 1089). 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.1329 0.0259 0.6814 Baseline odds
X1 X1 1.0624 0.7994 1.4118 ↑ Lebih berisiko tidak bersekolah
X2 X2 0.2838 0.2041 0.3947 ↓ Lebih kecil risiko tidak bersekolah
X3 X3 1.3943 1.1630 1.6717 ↑ Lebih berisiko tidak bersekolah
X4 X4 0.4808 0.3005 0.7694 ↓ Lebih kecil risiko tidak bersekolah
X5 X5 2.0036 1.4440 2.7799 ↑ Lebih berisiko tidak bersekolah
X6 X6 1.1240 0.8795 1.4366 ↑ Lebih berisiko tidak bersekolah
X7 X7 1.5878 1.1572 2.1787 ↑ Lebih berisiko tidak bersekolah
X8 X8 9.8694 4.8410 20.1210 ↑ Lebih berisiko tidak bersekolah
X9 X9 1.5717 1.0103 2.4449 ↑ 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) 109 161
Pred: Bersekolah (1) 55 436
Accuracy: 71.62 %Sensitivity: 73.03 %Specificity: 66.46 %Precision: 88.8 %F1-Score: 80.15 %
Youden Optimal (0.616)
Confusion Matrix — Youden Optimal (0.616)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 132 234
Pred: Bersekolah (1) 32 363
Accuracy: 65.05 %Sensitivity: 60.8 %Specificity: 80.49 %Precision: 91.9 %F1-Score: 73.19 %
ROC-AUC
AUC = 0.7723Cukup  |  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) 345 161
Pred: Bersekolah (1) 147 436
Accuracy: 71.72 %Sensitivity: 73.03 %Specificity: 70.12 %Precision: 74.79 %F1-Score: 73.9 %
Youden Optimal (0.543)
Confusion Matrix — Youden Optimal (0.543)
Aktual: 0 Aktual: 1
Pred: Tidak Bersekolah (0) 371 179
Pred: Bersekolah (1) 121 418
Accuracy: 72.45 %Sensitivity: 70.02 %Specificity: 75.41 %Precision: 77.55 %F1-Score: 73.59 %
ROC-AUC
AUC = 0.7928Cukup  |  Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | <0.7 Kurang
ROC Curve