# ── 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)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")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
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)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)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)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"))
)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")| 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 |
Jumlah observasi : 236
Jumlah variabel : 12
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…
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
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)| 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 |
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)| 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")))| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Perempuan | 107 | 45.34 |
| Laki-laki | 129 | 54.66 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Disabilitas tunggal | 129 | 54.66 |
| Disabilitas ganda | 107 | 45.34 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Sangat kesulitan | 66 | 27.97 |
| Kesulitan | 57 | 24.15 |
| Sedikit kesulitan | 113 | 47.88 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Perempuan | 32 | 13.56 |
| Laki-laki | 204 | 86.44 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| ≤ SMP | 179 | 75.85 |
| ≥ SMA | 57 | 24.15 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Tidak bekerja | 20 | 8.47 |
| Pertanian | 127 | 53.81 |
| Nonpertanian | 89 | 37.71 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Perdesaan | 172 | 72.88 |
| Perkotaan | 64 | 27.12 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Tidak memiliki | 200 | 84.75 |
| Memiliki | 36 | 15.25 |
| 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 |
| Kategori | Frekuensi | Persen (%) |
|---|---|---|
| Tidak miskin | 188 | 79.66 |
| Miskin | 48 | 20.34 |
| 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 |
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)| 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"
)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)| 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
)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)
)
}| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Perempuan | 33 | 74 | 107 | 69.2 |
| Laki-laki | 34 | 95 | 129 | 73.6 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Disabilitas tunggal | 16 | 113 | 129 | 87.6 |
| Disabilitas ganda | 51 | 56 | 107 | 52.3 |
| 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 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Perempuan | 5 | 27 | 32 | 84.4 |
| Laki-laki | 62 | 142 | 204 | 69.6 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| ≤ SMP | 55 | 124 | 179 | 69.3 |
| ≥ SMA | 12 | 45 | 57 | 78.9 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Tidak bekerja | 10 | 10 | 20 | 50.0 |
| Pertanian | 40 | 87 | 127 | 68.5 |
| Nonpertanian | 17 | 72 | 89 | 80.9 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Perdesaan | 51 | 121 | 172 | 70.3 |
| Perkotaan | 16 | 48 | 64 | 75.0 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Tidak memiliki | 66 | 134 | 200 | 67.0 |
| Memiliki | 1 | 35 | 36 | 97.2 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Tidak miskin | 51 | 137 | 188 | 72.9 |
| Miskin | 16 | 32 | 48 | 66.7 |
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")| 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 |
Indikator: Persentase penyandang disabilitas usia 13–15 tahun yang sedang bersekolah, diestimasi di tingkat provinsi menggunakan metode estimasi langsung dengan desain survei SUSENAS 2023.
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
# ── 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")| 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 |
📈 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
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.
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>ⓘ 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>'
)
}# ── 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">✓ 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">⚠ Terdeteksi potensi separation pada variabel berikut:</p>'
} else {
'<p class="sep-ok">✓ Tidak terdeteksi perfect/quasi-complete separation.</p>'
}
tbl <- kbl_h(data.frame(
Variabel = names(coefs),
`|B|>10` = ifelse(flag_b, "⚠ Ya", "✓ Tidak"),
`SE>5` = ifelse(flag_se, "⚠ Ya", "✓ Tidak"),
Status = ifelse(flag,
'<span class="sep-flag">⚠ Potensi Separation</span>',
'<span class="sep-ok">✓ 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 & 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>',
' | Skala: 0.9–1.0 Sangat Baik | 0.8–0.9 Baik | 0.7–0.8 Cukup | <0.7 Kurang<br>',
'<button class="btn-roc" onclick="toggleRoc(\'', pid, '\')">',
'📈 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\')">📊 Data Asli (Fair)</button>'),
'<button class="smote-tab-btn" ',
paste0('onclick="switchSmote(\'', pid, '\',\'sm\')">🧨 Data SMOTE (In-Sample)</button>'),
'</div>'
)
paste0(
'<div class="eval-wrap">',
separation_html(coefs, ses, FALSE),
'<div class="eval-sub"><h6>Confusion Matrix, Metrik & ROC — Pilih Data</h6>',
tabs, panel_ori, panel_sm,
'</div></div>'
)
}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))
)
}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))
)
}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>ⓘ 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>ⓘ 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
))
)
}Reglog Biasa
n valid: 236 obs.
| Kategori | N | Persen |
|---|---|---|
| Tidak Bersekolah (0) | 67 | 28.39 % |
| Bersekolah (1) | 169 | 71.61 % |
| 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 |
| 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 |
✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).
| 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) |
| Statistik | Nilai |
|---|---|
| χ² HL | 5.188 |
| df | 8 |
| p-value | 0.7373 |
| Keputusan | ✅ Gagal Tolak H0 — Model FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| AIC | 224.405 | Semakin kecil semakin baik |
| AICc | 225.383 | Koreksi utk sampel kecil |
| BIC | 259.043 | Penalti lebih besar pada parameter |
| Log-Likelihood | -102.202 | |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 236 |
| 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 |
✓ Tidak terdeteksi perfect/quasi-complete separation.
| 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 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 36 | 14 |
| Pred: Bersekolah (1) | 31 | 155 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 51 | 36 |
| Pred: Bersekolah (1) | 16 | 133 |
Firth
n valid: 236 obs. | Firth menangani quasi-complete separation & sampel kecil
| Kategori | N | Persen |
|---|---|---|
| Tidak Bersekolah (0) | 67 | 28.39 % |
| Bersekolah (1) | 169 | 71.61 % |
| 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 |
| 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 |
✅ 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.
| 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) |
| Statistik | Nilai |
|---|---|
| χ² HL | 5.076 |
| df | 8 |
| p-value | 0.7494 |
| Keputusan | ✅ Gagal Tolak H0 — Model FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| AIC | 205.012 | Semakin kecil semakin baik |
| AICc | 205.99 | Koreksi utk sampel kecil |
| BIC | 239.65 | Penalti lebih besar pada parameter |
| Log-Likelihood | -92.506 | |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 236 |
| 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 |
✓ Firth (Penalized Likelihood) dirancang khusus menangani complete/quasi-complete separation — hasil tetap valid tanpa bias.
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 36 | 14 |
| Pred: Bersekolah (1) | 31 | 155 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 52 | 39 |
| Pred: Bersekolah (1) | 15 | 130 |
SMOTE
n asli: 236 | n setelah SMOTE: 303
| Kategori | N Asal | N SMOTE |
|---|---|---|
| Tidak Bersekolah (0) | 67 | 134 |
| Bersekolah (1) | 169 | 169 |
| 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 |
| 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 |
✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).
| 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.
| Statistik | Nilai |
|---|---|
| χ² HL | 6.425 |
| df | 8 |
| p-value | 0.5997 |
| Keputusan | ✅ Gagal Tolak H0 — Model FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| AIC | 308.258 | Semakin kecil semakin baik |
| AICc | 309.012 | Koreksi utk sampel kecil |
| BIC | 345.396 | Penalti lebih besar pada parameter |
| Log-Likelihood | -144.129 | |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 303 |
ⓘ AIC/AICc/BIC dihitung dari data SMOTE (n = 303). Gunakan sebagai perbandingan; evaluasi fair tetap di data asli.
| 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 |
✓ Tidak terdeteksi perfect/quasi-complete separation.
| 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 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 45 | 34 |
| Pred: Bersekolah (1) | 22 | 135 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 54 | 47 |
| Pred: Bersekolah (1) | 13 | 122 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 94 | 34 |
| Pred: Bersekolah (1) | 40 | 135 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 121 | 59 |
| Pred: Bersekolah (1) | 13 | 110 |