# ── Manipulasi Data ───────────────────────────────────────────────
library(foreign) # read.dbf
library(dplyr)
library(readxl) # read_excel
library(tidyr)
# ── Visualisasi ───────────────────────────────────────────────────
library(ggplot2)
library(scales)
library(gridExtra)
# ── 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)
library(htmltools)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")Populasi studi adalah anak penyandang disabilitas usia 13–15 tahun yang berdomisili di wilayah Sulawesi dan Kalimantan (kode provinsi 60–79).
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
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 | 1 = Non-pertanian; 0 = Pertanian |
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 ~ 0L,
R706 > 6 | R706 == 0 ~ 1L,
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(
"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)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 | 1 = Non-pertanian; 0 = Pertanian |
| 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), labels = c("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",
"1 = Non-pertanian; 0 = 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")| 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 | 1 = Non-pertanian; 0 = 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 |
Jumlah observasi : 761
Jumlah variabel : 12
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, Nonpertanian, Nonpertanian, Pertanian, N…
$ 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…
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
Pertanian :320 Perdesaan:418 Tidak memiliki:622 Tidak miskin:654
Nonpertanian:441 Perkotaan:343 Memiliki :139 Miskin :107
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 | 164 | 21.55 |
| Bersekolah | 597 | 78.45 |
ggplot(tbl_y, aes(x = Y, y = n, fill = Y)) +
geom_bar(stat = "identity", width = 0.5, show.legend = FALSE) +
geom_text(aes(label = paste0(n, "\n(", Persen, "%)")),
vjust = -0.4, size = 4) +
scale_fill_manual(values = c("#D73027", "#4575B4")) +
labs(title = "Distribusi Partisipasi Sekolah",
subtitle = "Anak Penyandang Disabilitas Usia 13–15 Tahun\nSulawesi & Kalimantan (SUSENAS 2023)",
x = NULL, y = "Frekuensi") +
theme_minimal(base_size = 13)vars_cat <- c("X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8", "X9")
labels_cat <- c(
"X1: Jenis Kelamin Anak",
"X2: Jenis Disabilitas",
"X3: Tingkat Kesulitan",
"X4: Jenis Kelamin KRT",
"X5: Pendidikan KRT",
"X6: Pekerjaan KRT",
"X7: Daerah Tinggal",
"X8: Kepemilikan KIP/PIP",
"X9: Status Kemiskinan RT"
)
plot_list <- lapply(seq_along(vars_cat), function(i) {
var <- vars_cat[i]
df_tmp <- dataku_final %>%
count(.data[[var]]) %>%
filter(!is.na(.data[[var]])) %>%
mutate(pct = round(n / sum(n) * 100, 1))
ggplot(df_tmp, aes(x = .data[[var]], y = n, fill = .data[[var]])) +
geom_bar(stat = "identity", show.legend = FALSE) +
geom_text(aes(label = paste0(pct, "%")), vjust = -0.4, size = 3) +
labs(title = labels_cat[i], x = NULL, y = "n") +
theme_minimal(base_size = 10) +
theme(plot.title = element_text(size = 10, face = "bold"))
})
do.call(grid.arrange, c(plot_list, ncol = 3))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 | 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 |
ggplot(tbl_dis, aes(x = factor(JUMLAH_JENIS_DISABILITAS), y = n)) +
geom_bar(stat = "identity", fill = "#4575B4", width = 0.6) +
geom_text(aes(label = paste0(n, "\n(", Persen, "%)")),
vjust = -0.3, size = 3.5) +
labs(title = "Distribusi Jumlah Jenis Disabilitas",
x = "Jumlah Jenis Disabilitas", y = "Frekuensi") +
theme_minimal(base_size = 12)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 | % |
|---|---|---|
| 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 |
ggplot(tbl_prov, aes(x = reorder(factor(prov), n), y = n)) +
geom_bar(stat = "identity", fill = "#74ADD1") +
geom_text(aes(label = n), hjust = -0.2, size = 3.5) +
coord_flip() +
labs(title = "Jumlah Observasi per Provinsi",
x = "Kode Provinsi", y = "Frekuensi") +
theme_minimal(base_size = 12)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 | 71 | 269 | 340 | 79.1 |
| Laki-laki | 93 | 328 | 421 | 77.9 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Disabilitas tunggal | 38 | 362 | 400 | 90.5 |
| Disabilitas ganda | 126 | 235 | 361 | 65.1 |
| 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 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Perempuan | 17 | 84 | 101 | 83.2 |
| Laki-laki | 147 | 513 | 660 | 77.7 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| ≤ SMP | 122 | 371 | 493 | 75.3 |
| ≥ SMA | 42 | 226 | 268 | 84.3 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Pertanian | 78 | 242 | 320 | 75.6 |
| Nonpertanian | 86 | 355 | 441 | 80.5 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Perdesaan | 108 | 310 | 418 | 74.2 |
| Perkotaan | 56 | 287 | 343 | 83.7 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Tidak memiliki | 159 | 463 | 622 | 74.4 |
| Memiliki | 5 | 134 | 139 | 96.4 |
| Tidak bersekolah | Bersekolah | Total | % Bersekolah | |
|---|---|---|---|---|
| Tidak miskin | 140 | 514 | 654 | 78.6 |
| Miskin | 24 | 83 | 107 | 77.6 |
plot_stacked <- function(var, label) {
df_tmp <- dataku_final %>%
filter(!is.na(.data[[var]]), !is.na(Y)) %>%
count(.data[[var]], Y) %>%
group_by(.data[[var]]) %>%
mutate(pct = round(n / sum(n) * 100, 1)) %>%
ungroup()
ggplot(df_tmp, aes(x = .data[[var]], y = pct, fill = Y)) +
geom_bar(stat = "identity", position = "stack") +
geom_text(aes(label = paste0(pct, "%")),
position = position_stack(vjust = 0.5),
size = 3, color = "white", fontface = "bold") +
scale_fill_manual(values = c("Tidak bersekolah" = "#D73027",
"Bersekolah" = "#4575B4")) +
labs(title = label, x = NULL, y = "%", fill = NULL) +
theme_minimal(base_size = 10) +
theme(legend.position = "bottom",
plot.title = element_text(size = 10, face = "bold"))
}
p_list <- lapply(seq_along(vars_for_tab), function(i) {
plot_stacked(vars_for_tab[i], label_vars[i])
})
do.call(grid.arrange, c(p_list, ncol = 3))df_prov_y <- dataku_final %>%
filter(!is.na(Y)) %>%
count(prov, Y) %>%
group_by(prov) %>%
mutate(pct = round(n / sum(n) * 100, 1)) %>%
ungroup()
ggplot(df_prov_y, aes(x = factor(prov), y = pct, fill = Y)) +
geom_bar(stat = "identity", position = "stack") +
geom_text(aes(label = paste0(pct, "%")),
position = position_stack(vjust = 0.5),
size = 2.8, color = "white", fontface = "bold") +
scale_fill_manual(values = c("Tidak bersekolah" = "#D73027",
"Bersekolah" = "#4575B4")) +
labs(title = "Partisipasi Sekolah Anak Disabilitas Usia 13–15 Tahun\nper Provinsi (Sulawesi & Kalimantan)",
x = "Kode Provinsi", y = "%", fill = NULL) +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")df_pulau_y <- dataku_final %>%
filter(!is.na(Y)) %>%
mutate(
pulau = case_when(
floor(prov / 10) == 6 ~ "Kalimantan",
floor(prov / 10) == 7 ~ "Sulawesi",
TRUE ~ NA_character_
),
pulau = factor(pulau, levels = c("Kalimantan", "Sulawesi"))
) %>%
filter(!is.na(pulau)) %>%
count(pulau, Y) %>%
group_by(pulau) %>%
mutate(
total = sum(n),
pct = round(n / total * 100, 1)
) %>%
ungroup()
ggplot(df_pulau_y, aes(x = pulau, y = pct, fill = Y)) +
geom_bar(stat = "identity", position = "stack", width = 0.65) +
geom_text(aes(label = paste0(pct, "%")),
position = position_stack(vjust = 0.5),
size = 3.5, color = "white", fontface = "bold") +
geom_text(
data = df_pulau_y %>% distinct(pulau, total),
aes(x = pulau, y = 102, label = paste0("n=", total)),
inherit.aes = FALSE, size = 3.2, color = "grey40"
) +
scale_fill_manual(values = c("Tidak bersekolah" = "#D73027",
"Bersekolah" = "#4575B4")) +
scale_y_continuous(limits = c(0, 110), labels = function(x) paste0(x, "%")) +
labs(
title = "Partisipasi Sekolah Anak Penyandang Disabilitas Usia 13–15 Tahun",
subtitle = "per Pulau — SUSENAS 2023 (Sulawesi & Kalimantan)",
x = NULL,
y = "Persentase (%)",
fill = NULL
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "bottom",
plot.title = element_text(face = "bold"),
axis.text.x = element_text(size = 11)
)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 | 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) | Pertanian | 320 | 242 | 75.6 |
| Nonpertanian | 441 | 355 | 80.5 | |
| 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 |
Indikator: Persentase penyandang disabilitas usia 13–15 tahun yang sedang bersekolah, diestimasi di tingkat provinsi menggunakan metode estimasi langsung dengan desain survei SUSENAS 2023 (Sulawesi & Kalimantan).
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
# ── 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")| 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 |
library(dplyr)
library(ggplot2)
library(tidyr)
# ── 1. Restrukturisasi Data untuk Faceting Vertikal ───────────────────────
plot_data_ind <- outputP_ind %>%
select(Nama_Prov, Wilayah, Estimasi, RSE, CI_LOWER, CI_UPPER) %>%
mutate(
# Urutkan provinsi berdasarkan nilai Estimasi dari terbesar ke terkecil
Nama_Prov = factor(Nama_Prov, levels = Nama_Prov[order(-Estimasi)]),
RSE_flag = factor(
ifelse(RSE > 25, "RSE > 25% (Tidak Reliabel)", "RSE ≤ 25% (Reliabel)"),
levels = c("RSE ≤ 25% (Reliabel)", "RSE > 25% (Tidak Reliabel)")
)
) %>%
# Mengubah data menjadi format long agar bisa di-facet secara vertikal
pivot_longer(cols = c(Estimasi, RSE), names_to = "Indikator", values_to = "Nilai")
# ── 2. Pembuatan Single Plot dengan Facet ─────────────────────────────────
ggplot(plot_data_ind, aes(x = Nama_Prov, y = Nilai, fill = Wilayah)) +
# Batang utama grafik
geom_bar(stat = "identity", width = 0.65, alpha = 0.88, color = "white", linewidth = 0.2) +
# Error Bar Khusus untuk panel Estimasi saja
geom_errorbar(
data = filter(plot_data_ind, Indikator == "Estimasi"),
aes(ymin = CI_LOWER, ymax = CI_UPPER),
width = 0.2, color = "gray20", linewidth = 0.6
) +
# Garis Batas RSE 25% Khusus untuk panel RSE saja
geom_hline(
data = filter(plot_data_ind, Indikator == "RSE"),
aes(yintercept = 25), linetype = "dashed", color = "#C62828", linewidth = 0.7
) +
# Label angka di atas bar (fleksibel mengikuti posisi CI_UPPER atau Nilai RSE)
geom_text(
aes(
label = paste0(round(Nilai, 1), "%"),
y = ifelse(Indikator == "Estimasi", CI_UPPER, Nilai)
),
vjust = -0.5, size = 3.3, fontface = "bold", color = "gray10"
) +
# Pewarnaan khusus untuk wilayah Kalimantan & Sulawesi
scale_fill_manual(values = c("Kalimantan" = "#1976D2", "Sulawesi" = "#E64A19")) +
# Pengaturan skala Y otomatis melonggar di atas agar text tidak terpotong
scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
# Pembagian Panel Vertikal (Atas: Estimasi, Bawah: RSE) dengan skala Y bebas
facet_wrap(~ Indikator, ncol = 1, scales = "free_y",
labeller = as_labeller(c("Estimasi" = "Estimasi Angka Sekolah (95% CI)",
"RSE" = "Relative Standard Error / RSE (Batas 25%)"))) +
# Labeling Grafis
labs(
title = "Analisis Indikator Disabilitas Usia 13–15 Tahun yang Bersekolah",
subtitle = "Sulawesi & Kalimantan — SUSENAS 2023 | Estimasi Langsung",
x = NULL,
y = "Persentase (%)",
fill = "Wilayah:",
caption = "Garis putus-putus merah menyatakan batas maksimal reliabilitas RSE BPS (25%)"
) +
# Tema visual minimalis & profesional
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14, color = "gray10"),
plot.subtitle = element_text(color = "gray40", size = 10, margin = margin(b = 12)),
# Merapikan text nama provinsi di sumbu X agar vertikal/miring dan tidak bertumpuk
axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1, face = "bold", color = "gray20"),
axis.title.y = element_text(size = 10, color = "gray30"),
# Desain strip panel facet yang clean
strip.background = element_rect(fill = "gray95", color = NA),
strip.text = element_text(face = "bold", size = 11, color = "gray20", margin = margin(t = 6, b = 6)),
# Menghapus grid line yang tidak perlu
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "bottom",
legend.margin = margin(t = -5)
)## 1. AGREGASI DATA PER PULAU (Menggabungkan Nilai Varians & SE)
# Menggabungkan data provinsi ke tingkat pulau dengan pendekatan survey
pulau_data <- aggregate(cbind(Estimasi, VAR) ~ Wilayah, data = outputP_ind, FUN = mean)
# Menghitung ulang SE, CI, dan RSE tingkat Pulau agar akurat secara statistik
pulau_data$SE <- sqrt(pulau_data$VAR)
pulau_data$CI_LOWER <- pulau_data$Estimasi - (1.96 * pulau_data$SE)
pulau_data$CI_UPPER <- pulau_data$Estimasi + (1.96 * pulau_data$SE)
pulau_data$RSE <- (pulau_data$SE / pulau_data$Estimasi) * 100
# Urutkan pulau berdasarkan nilai Estimasi terbesar ke terkecil agar plot rapi
pulau_data <- pulau_data[order(-pulau_data$Estimasi), ]
## 2. SETTING LAYOUT GRAFIK (Base R)
# Membagi layar menjadi 2 kolom (Kiri: Estimasi, Kanan: RSE)
old_par <- par(mfrow = c(1, 2), mar = c(6, 5, 4, 2))
# Palette warna estetik modern untuk masing-masing pulau
warna_pulau <- c("#1976D2", "#E64A19", "#388E3C", "#0097A7", "#7B1FA2", "#FBC02D", "#E91E63")[1:nrow(pulau_data)]
## ── PLOT 1: ESTIMASI LENGKAP DENGAN CI PER PULAU ──────────────────────────
y_max_est <- max(pulau_data$CI_UPPER, na.rm = TRUE) * 1.15
bp1 <- barplot(pulau_data$Estimasi,
names.arg = pulau_data$Wilayah,
las = 2, # Nama pulau tegak lurus/vertikal
col = warna_pulau,
border = NA,
ylim = c(0, y_max_est),
ylab = "Estimasi (%)",
main = "Estimasi Angka Sekolah Per Pulau",
font.main = 2, cex.main = 1.1, cex.names = 0.9)
# Tambahkan Grid & Gambar Ulang Bar agar bersih
grid(nx = NA, ny = NULL, col = "gray93", lty = "solid")
barplot(pulau_data$Estimasi, col = warna_pulau, border = NA, add = TRUE, las = 2, names.arg = NULL)
# Tambahkan Error Bar (Confidence Interval)
arrows(x0 = bp1, y0 = pulau_data$CI_LOWER,
x1 = bp1, y1 = pulau_data$CI_UPPER,
code = 3, angle = 90, length = 0.05, col = "gray20", lwd = 1.5)
# Tampilkan angka persentase di atas bar
text(x = bp1, y = pulau_data$Estimasi + (y_max_est * 0.02),
labels = paste0(round(pulau_data$Estimasi, 1), "%"),
cex = 0.85, font = 2)
## ── PLOT 2: RELATIVE STANDARD ERROR (RSE) PER PULAU ──────────────────────
y_max_rse <- max(c(pulau_data$RSE, 30), na.rm = TRUE) * 1.15
# Warna RSE otomatis: Hijau jika <= 25% (Reliabel), Merah jika > 25%
warna_rse <- ifelse(pulau_data$RSE > 25, "#EF5350", "#66BB6A")
bp2 <- barplot(pulau_data$RSE,
names.arg = pulau_data$Wilayah,
las = 2,
col = warna_rse,
border = NA,
ylim = c(0, y_max_rse),
ylab = "RSE (%)",
main = "Nilai RSE (Reliabilitas Data) Per Pulau",
font.main = 2, cex.main = 1.1, cex.names = 0.9)
# Tambahkan Grid & Gambar Ulang Bar RSE
grid(nx = NA, ny = NULL, col = "gray93", lty = "solid")
barplot(pulau_data$RSE, col = warna_rse, border = NA, add = TRUE, las = 2, names.arg = NULL)
# Tambahkan Garis Batas RSE 25% (Standard BPS untuk data reliabel)
abline(h = 25, col = "#C62828", linetype = "dashed", lwd = 1.5)
text(x = max(bp2)*0.3, y = 26.5, labels = "Batas RSE 25%", col = "#C62828", cex = 0.8, font = 3)
# Tampilkan nilai RSE di atas bar
text(x = bp2, y = pulau_data$RSE + (y_max_rse * 0.02),
labels = paste0(round(pulau_data$RSE, 1), "%"),
cex = 0.85, font = 2)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
))
)
}run_weight <- 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>")
n <- nrow(df)
n1 <- sum(df$status_miskin == 1)
n0 <- sum(df$status_miskin == 0)
# Simpan bobot sebagai kolom di df agar glm selalu menemukannya
df$.w <- ifelse(df$status_miskin == 1, n / (2 * n1), n / (2 * n0))
ctrl <- glm.control(maxit = 200, epsilon = 1e-8)
mdl <- suppressWarnings(tryCatch(
glm(fm, data = df, family = quasibinomial(link = "logit"),
weights = .w, control = ctrl),
error = function(e) NULL
))
mdl0 <- suppressWarnings(tryCatch(
glm(status_miskin ~ 1, data = df,
family = quasibinomial(link = "logit"),
weights = .w, control = ctrl),
error = function(e) NULL
))
if (is.null(mdl)) return("<p>⚠️ Model Class Weight gagal di-fit.</p>")
# EDA + weight info
t <- table(df$status_miskin)
pct <- round(prop.table(t) * 100, 2)
eda_tbl <- kbl_h(data.frame(
Kategori = c("Tidak Bersekolah (0)", "Bersekolah (1)"),
N = as.integer(c(t["0"], t["1"])),
Persen = paste0(c(pct["0"], pct["1"]), " %"),
`Class Weight` = c(round(n / (2 * n0), 4), round(n / (2 * n1), 4)),
check.names = FALSE
), "Distribusi Y + Class Weight")
info <- paste0(
'<p><strong>n valid:</strong> ', nrow(df), ' obs.',
' | <strong>w bersekolah:</strong> ', round(n / (2 * n1), 3),
' | <strong>w tidak bersekolah:</strong> ', round(n / (2 * n0), 3),
' | <em>family: quasibinomial</em></p>'
)
k_weight <- length(coef(mdl))
n_weight <- nrow(df)
pid <- paste0("weight-", gsub("[^a-zA-Z0-9]", "_", nama_dataset))
paste0(info,
sec("📊", "EDA — Distribusi Y + Class Weight", eda_tbl),
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, "Class Weight")),
sec("✅", "Goodness of Fit (H-L Test)", hl_html(mdl, df$status_miskin)),
sec("📊", "Kriteria Informasi (QAIC/Deviance)", ic_html(mdl, n_weight, k_weight, "quasi")),
sec("📈", "Interpretasi Parameter (OR)", or_html(mdl)),
sec("🔍", "Evaluasi Model",
evaluasi_html(mdl, df$status_miskin, fitted(mdl), pid))
)
}Reglog Biasa
n valid: 761 obs.
| Kategori | N | Persen |
|---|---|---|
| Tidak Bersekolah (0) | 164 | 21.55 % |
| Bersekolah (1) | 597 | 78.45 % |
| Statistik | Nilai |
|---|---|
| -2LL Null | 793.207 |
| -2LL Model | 666.638 |
| G² Hitung | 126.569 |
| df | 9 |
| χ² Tabel (α=5%) | 16.919 |
| p-value | 0 |
| Keputusan | ✅ Tolak H0 — Model Signifikan |
| Variabel | B | SE | Wald | p-value | Kesimpulan | |
|---|---|---|---|---|---|---|
| (Intercept) | (Intercept) | -0.3493 | 1.1236 | 0.0966 | 0.7559 | ❌ Tdk Signifikan |
| X1 | X1 | 0.0400 | 0.1945 | 0.0422 | 0.8372 | ❌ Tdk Signifikan |
| X2 | X2 | -1.1185 | 0.2313 | 23.3817 | 0.0000 | ✅ Signifikan |
| X3 | X3 | 0.4020 | 0.1250 | 10.3486 | 0.0013 | ✅ Signifikan |
| X4 | X4 | -0.5098 | 0.3041 | 2.8108 | 0.0936 | ❌ Tdk Signifikan |
| X5 | X5 | 0.5269 | 0.2264 | 5.4158 | 0.0200 | ✅ Signifikan |
| X6 | X6 | 0.0194 | 0.2137 | 0.0083 | 0.9275 | ❌ Tdk Signifikan |
| X7 | X7 | 0.4479 | 0.2199 | 4.1501 | 0.0416 | ✅ Signifikan |
| X8 | X8 | 1.7780 | 0.4765 | 13.9202 | 0.0002 | ✅ Signifikan |
| X9 | X9 | 0.1522 | 0.2801 | 0.2952 | 0.5869 | ❌ Tdk Signifikan |
✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).
| Variabel | VIF | Kategori | |
|---|---|---|---|
| X1 | X1 | 1.0057 | ✅ Tidak Ada (<5) |
| X2 | X2 | 1.2219 | ✅ Tidak Ada (<5) |
| X3 | X3 | 1.2166 | ✅ Tidak Ada (<5) |
| X4 | X4 | 1.0194 | ✅ Tidak Ada (<5) |
| X5 | X5 | 1.1483 | ✅ Tidak Ada (<5) |
| X6 | X6 | 1.2236 | ✅ Tidak Ada (<5) |
| X7 | X7 | 1.2310 | ✅ Tidak Ada (<5) |
| X8 | X8 | 1.0213 | ✅ Tidak Ada (<5) |
| X9 | X9 | 1.0399 | ✅ Tidak Ada (<5) |
| Statistik | Nilai |
|---|---|
| χ² HL | 3.414 |
| df | 8 |
| p-value | 0.9058 |
| Keputusan | ✅ Gagal Tolak H0 — Model FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| AIC | 686.638 | Semakin kecil semakin baik |
| AICc | 686.931 | Koreksi utk sampel kecil |
| BIC | 732.984 | Penalti lebih besar pada parameter |
| Log-Likelihood | -333.319 | |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 761 |
| Variabel | exp(B) | CI 2.5% | CI 97.5% | Interpretasi | |
|---|---|---|---|---|---|
| (Intercept) | (Intercept) | 0.7052 | 0.0780 | 6.3789 | Baseline odds |
| X1 | X1 | 1.0408 | 0.7108 | 1.5238 | ↑ Lebih berisiko tidak bersekolah |
| X2 | X2 | 0.3268 | 0.2077 | 0.5142 | ↓ Lebih kecil risiko tidak bersekolah |
| X3 | X3 | 1.4949 | 1.1701 | 1.9098 | ↑ Lebih berisiko tidak bersekolah |
| X4 | X4 | 0.6006 | 0.3310 | 1.0900 | ↓ Lebih kecil risiko tidak bersekolah |
| X5 | X5 | 1.6937 | 1.0867 | 2.6396 | ↑ Lebih berisiko tidak bersekolah |
| X6 | X6 | 1.0196 | 0.6707 | 1.5501 | ↑ Lebih berisiko tidak bersekolah |
| X7 | X7 | 1.5651 | 1.0171 | 2.4082 | ↑ Lebih berisiko tidak bersekolah |
| X8 | X8 | 5.9180 | 2.3256 | 15.0594 | ↑ Lebih berisiko tidak bersekolah |
| X9 | X9 | 1.1643 | 0.6725 | 2.0159 | ↑ 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) | 38 | 27 |
| Pred: Bersekolah (1) | 126 | 570 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 122 | 191 |
| Pred: Bersekolah (1) | 42 | 406 |
Firth
n valid: 761 obs. | Firth menangani quasi-complete separation & sampel kecil
| Kategori | N | Persen |
|---|---|---|
| Tidak Bersekolah (0) | 164 | 21.55 % |
| Bersekolah (1) | 597 | 78.45 % |
| Statistik | Nilai |
|---|---|
| LogLik Null | -379.436 |
| LogLik Model | -317.742 |
| G² Hitung | 123.387 |
| df | 9 |
| χ² Tabel (α=5%) | 16.919 |
| p-value | 0 |
| Keputusan | ✅ Tolak H0 — Signifikan |
| Variabel | B | SE | Chi-sq | p-value | Kesimpulan | |
|---|---|---|---|---|---|---|
| (Intercept) | (Intercept) | -0.3003 | 1.0949 | 0.0752 | 0.7857 | ❌ Tdk Signifikan |
| X1 | X1 | 0.0397 | 0.1916 | 0.0430 | 0.8368 | ❌ Tdk Signifikan |
| X2 | X2 | -1.0982 | 0.2270 | 23.3960 | 0.0000 | ✅ Signifikan |
| X3 | X3 | 0.3962 | 0.1229 | 10.3822 | 0.0014 | ✅ Signifikan |
| X4 | X4 | -0.4856 | 0.2974 | 2.6662 | 0.0949 | ❌ Tdk Signifikan |
| X5 | X5 | 0.5162 | 0.2228 | 5.3668 | 0.0197 | ✅ Signifikan |
| X6 | X6 | 0.0204 | 0.2104 | 0.0094 | 0.9231 | ❌ Tdk Signifikan |
| X7 | X7 | 0.4397 | 0.2164 | 4.1264 | 0.0425 | ✅ Signifikan |
| X8 | X8 | 1.6802 | 0.4499 | 13.9453 | 0.0000 | ✅ Signifikan |
| X9 | X9 | 0.1408 | 0.2748 | 0.2623 | 0.6087 | ❌ 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.0057 | ✅ Tidak Ada (<5) |
| X2 | X2 | 1.2219 | ✅ Tidak Ada (<5) |
| X3 | X3 | 1.2166 | ✅ Tidak Ada (<5) |
| X4 | X4 | 1.0194 | ✅ Tidak Ada (<5) |
| X5 | X5 | 1.1483 | ✅ Tidak Ada (<5) |
| X6 | X6 | 1.2236 | ✅ Tidak Ada (<5) |
| X7 | X7 | 1.2310 | ✅ Tidak Ada (<5) |
| X8 | X8 | 1.0213 | ✅ Tidak Ada (<5) |
| X9 | X9 | 1.0399 | ✅ Tidak Ada (<5) |
| Statistik | Nilai |
|---|---|
| χ² HL | 3.222 |
| df | 8 |
| p-value | 0.9197 |
| Keputusan | ✅ Gagal Tolak H0 — Model FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| AIC | 655.485 | Semakin kecil semakin baik |
| AICc | 655.778 | Koreksi utk sampel kecil |
| BIC | 701.831 | Penalti lebih besar pada parameter |
| Log-Likelihood | -317.742 | |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 761 |
| Variabel | exp(B) | CI 2.5% | CI 97.5% | Interpretasi | |
|---|---|---|---|---|---|
| (Intercept) | (Intercept) | 0.7406 | 0.0846 | 6.5021 | Baseline odds |
| X1 | X1 | 1.0405 | 0.7123 | 1.5179 | ↑ Lebih berisiko tidak bersekolah |
| X2 | X2 | 0.3335 | 0.2112 | 0.5192 | ↓ Lebih kecil risiko tidak bersekolah |
| X3 | X3 | 1.4861 | 1.1662 | 1.8960 | ↑ Lebih berisiko tidak bersekolah |
| X4 | X4 | 0.6153 | 0.3336 | 1.0852 | ↓ Lebih kecil risiko tidak bersekolah |
| X5 | X5 | 1.6757 | 1.0850 | 2.6184 | ↑ Lebih berisiko tidak bersekolah |
| X6 | X6 | 1.0206 | 0.6729 | 1.5451 | ↑ Lebih berisiko tidak bersekolah |
| X7 | X7 | 1.5522 | 1.0150 | 2.3875 | ↑ Lebih berisiko tidak bersekolah |
| X8 | X8 | 5.3666 | 2.3929 | 14.7890 | ↑ Lebih berisiko tidak bersekolah |
| X9 | X9 | 1.1511 | 0.6770 | 2.0090 | ↑ 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) | 38 | 27 |
| Pred: Bersekolah (1) | 126 | 570 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 122 | 191 |
| Pred: Bersekolah (1) | 42 | 406 |
SMOTE
n asli: 761 | n setelah SMOTE: 1089
| Kategori | N Asal | N SMOTE |
|---|---|---|
| Tidak Bersekolah (0) | 164 | 492 |
| Bersekolah (1) | 597 | 597 |
| Statistik | Nilai |
|---|---|
| -2LL Null | 1499.535 |
| -2LL Model | 1209.695 |
| G² Hitung | 289.84 |
| df | 9 |
| χ² Tabel (α=5%) | 16.919 |
| p-value | 0 |
| Keputusan | ✅ Tolak H0 — Model Signifikan |
| Variabel | B | SE | Wald | p-value | Kesimpulan | |
|---|---|---|---|---|---|---|
| (Intercept) | (Intercept) | -1.9550 | 0.8197 | 5.6882 | 0.0171 | ✅ Signifikan |
| X1 | X1 | 0.1072 | 0.1418 | 0.5719 | 0.4495 | ❌ Tdk Signifikan |
| X2 | X2 | -1.1470 | 0.1651 | 48.2734 | 0.0000 | ✅ Signifikan |
| X3 | X3 | 0.3663 | 0.0915 | 16.0427 | 0.0001 | ✅ Signifikan |
| X4 | X4 | -0.5543 | 0.2245 | 6.0984 | 0.0135 | ✅ Signifikan |
| X5 | X5 | 0.6976 | 0.1665 | 17.5430 | 0.0000 | ✅ Signifikan |
| X6 | X6 | -0.0955 | 0.1622 | 0.3465 | 0.5561 | ❌ Tdk Signifikan |
| X7 | X7 | 0.4813 | 0.1670 | 8.3035 | 0.0040 | ✅ Signifikan |
| X8 | X8 | 2.1746 | 0.3391 | 41.1343 | 0.0000 | ✅ Signifikan |
| X9 | X9 | 0.2508 | 0.2165 | 1.3420 | 0.2467 | ❌ Tdk Signifikan |
✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).
| Variabel | VIF | Kategori | |
|---|---|---|---|
| X1 | X1 | 1.0156 | ✅ Tidak Ada (<5) |
| X2 | X2 | 1.2902 | ✅ Tidak Ada (<5) |
| X3 | X3 | 1.2878 | ✅ Tidak Ada (<5) |
| X4 | X4 | 1.0260 | ✅ Tidak Ada (<5) |
| X5 | X5 | 1.2132 | ✅ Tidak Ada (<5) |
| X6 | X6 | 1.3036 | ✅ Tidak Ada (<5) |
| X7 | X7 | 1.2994 | ✅ Tidak Ada (<5) |
| X8 | X8 | 1.0166 | ✅ Tidak Ada (<5) |
| X9 | X9 | 1.0551 | ✅ Tidak Ada (<5) |
ⓘ VIF dihitung dari data SMOTE (in-sample). VIF pada data asli mungkin sedikit berbeda.
| Statistik | Nilai |
|---|---|
| χ² HL | 4.915 |
| df | 8 |
| p-value | 0.7666 |
| Keputusan | ✅ Gagal Tolak H0 — Model FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| AIC | 1229.695 | Semakin kecil semakin baik |
| AICc | 1229.899 | Koreksi utk sampel kecil |
| BIC | 1279.625 | Penalti lebih besar pada parameter |
| Log-Likelihood | -604.848 | |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 1089 |
ⓘ AIC/AICc/BIC dihitung dari data SMOTE (n = 1089). Gunakan sebagai perbandingan; evaluasi fair tetap di data asli.
| Variabel | exp(B) | CI 2.5% | CI 97.5% | Interpretasi | |
|---|---|---|---|---|---|
| (Intercept) | (Intercept) | 0.1416 | 0.0284 | 0.7058 | Baseline odds |
| X1 | X1 | 1.1132 | 0.8431 | 1.4698 | ↑ Lebih berisiko tidak bersekolah |
| X2 | X2 | 0.3176 | 0.2298 | 0.4389 | ↓ Lebih kecil risiko tidak bersekolah |
| X3 | X3 | 1.4424 | 1.2057 | 1.7256 | ↑ Lebih berisiko tidak bersekolah |
| X4 | X4 | 0.5745 | 0.3700 | 0.8919 | ↓ Lebih kecil risiko tidak bersekolah |
| X5 | X5 | 2.0089 | 1.4494 | 2.7843 | ↑ Lebih berisiko tidak bersekolah |
| X6 | X6 | 0.9090 | 0.6615 | 1.2490 | ↓ Lebih kecil risiko tidak bersekolah |
| X7 | X7 | 1.6182 | 1.1664 | 2.2449 | ↑ Lebih berisiko tidak bersekolah |
| X8 | X8 | 8.7986 | 4.5270 | 17.1010 | ↑ Lebih berisiko tidak bersekolah |
| X9 | X9 | 1.2851 | 0.8407 | 1.9645 | ↑ 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) | 111 | 164 |
| Pred: Bersekolah (1) | 53 | 433 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 124 | 202 |
| Pred: Bersekolah (1) | 40 | 395 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 338 | 164 |
| Pred: Bersekolah (1) | 154 | 433 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 326 | 143 |
| Pred: Bersekolah (1) | 166 | 454 |
Class Weight
n valid: 761 obs. | w bersekolah: 0.637 | w tidak bersekolah: 2.32 | family: quasibinomial
| Kategori | N | Persen | Class Weight |
|---|---|---|---|
| Tidak Bersekolah (0) | 164 | 21.55 % | 2.3201 |
| Bersekolah (1) | 597 | 78.45 % | 0.6374 |
| Statistik | Nilai |
|---|---|
| -2LL Null | 1054.97 |
| -2LL Model | 866.093 |
| G² Hitung | 188.877 |
| df | 9 |
| χ² Tabel (α=5%) | 16.919 |
| p-value | 0 |
| Keputusan | ✅ Tolak H0 — Model Signifikan |
| Variabel | B | SE | Wald | p-value | Kesimpulan | |
|---|---|---|---|---|---|---|
| (Intercept) | (Intercept) | -1.6916 | 0.9259 | 3.3379 | 0.0681 | ❌ Tdk Signifikan |
| X1 | X1 | 0.0877 | 0.1667 | 0.2767 | 0.5990 | ❌ Tdk Signifikan |
| X2 | X2 | -1.1282 | 0.1898 | 35.3213 | 0.0000 | ✅ Signifikan |
| X3 | X3 | 0.3783 | 0.1066 | 12.5894 | 0.0004 | ✅ Signifikan |
| X4 | X4 | -0.4049 | 0.2539 | 2.5429 | 0.1112 | ❌ Tdk Signifikan |
| X5 | X5 | 0.5898 | 0.1915 | 9.4826 | 0.0021 | ✅ Signifikan |
| X6 | X6 | -0.0321 | 0.1873 | 0.0293 | 0.8641 | ❌ Tdk Signifikan |
| X7 | X7 | 0.3792 | 0.1868 | 4.1179 | 0.0428 | ✅ Signifikan |
| X8 | X8 | 1.8221 | 0.3366 | 29.3036 | 0.0000 | ✅ Signifikan |
| X9 | X9 | 0.0543 | 0.2462 | 0.0486 | 0.8256 | ❌ Tdk Signifikan |
✅ Tidak terdeteksi masalah multikolinearitas (semua VIF < 5).
| Variabel | VIF | Kategori | |
|---|---|---|---|
| X1 | X1 | 1.0159 | ✅ Tidak Ada (<5) |
| X2 | X2 | 1.2711 | ✅ Tidak Ada (<5) |
| X3 | X3 | 1.2664 | ✅ Tidak Ada (<5) |
| X4 | X4 | 1.0287 | ✅ Tidak Ada (<5) |
| X5 | X5 | 1.1908 | ✅ Tidak Ada (<5) |
| X6 | X6 | 1.2787 | ✅ Tidak Ada (<5) |
| X7 | X7 | 1.2520 | ✅ Tidak Ada (<5) |
| X8 | X8 | 1.0219 | ✅ Tidak Ada (<5) |
| X9 | X9 | 1.0528 | ✅ Tidak Ada (<5) |
| Statistik | Nilai |
|---|---|
| χ² HL | 207.302 |
| df | 8 |
| p-value | 0 |
| Keputusan | ❌ Tolak H0 — TIDAK FIT |
| Kriteria | Nilai | Keterangan |
|---|---|---|
| QAIC | 892.783 | Quasi-AIC (φ̂ = 0.992) |
| Deviance Residual | 866.093 | df = 751 |
| Dispersi (φ̂) | 0.992 | Estimasi dispersi quasibinomial |
| AIC / BIC | N/A | Tidak terdefinisi untuk quasibinomial |
| k (parameter) | 10 | Termasuk intercept |
| n (observasi) | 761 |
| Variabel | exp(B) | CI 2.5% | CI 97.5% | Interpretasi | |
|---|---|---|---|---|---|
| (Intercept) | (Intercept) | 0.1842 | 0.0300 | 1.1310 | Baseline odds |
| X1 | X1 | 1.0917 | 0.7874 | 1.5135 | ↑ Lebih berisiko tidak bersekolah |
| X2 | X2 | 0.3236 | 0.2231 | 0.4695 | ↓ Lebih kecil risiko tidak bersekolah |
| X3 | X3 | 1.4598 | 1.1845 | 1.7991 | ↑ Lebih berisiko tidak bersekolah |
| X4 | X4 | 0.6671 | 0.4055 | 1.0972 | ↓ Lebih kecil risiko tidak bersekolah |
| X5 | X5 | 1.8037 | 1.2391 | 2.6254 | ↑ Lebih berisiko tidak bersekolah |
| X6 | X6 | 0.9685 | 0.6709 | 1.3979 | ↓ Lebih kecil risiko tidak bersekolah |
| X7 | X7 | 1.4611 | 1.0130 | 2.1072 | ↑ Lebih berisiko tidak bersekolah |
| X8 | X8 | 6.1847 | 3.1975 | 11.9628 | ↑ Lebih berisiko tidak bersekolah |
| X9 | X9 | 1.0558 | 0.6517 | 1.7105 | ↑ 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) | 123 | 201 |
| Pred: Bersekolah (1) | 41 | 396 |
| Aktual: 0 | Aktual: 1 | |
|---|---|---|
| Pred: Tidak Bersekolah (0) | 122 | 193 |
| Pred: Bersekolah (1) | 42 | 404 |