1 Pendahuluan

1.1 Gambaran Umum Analisis

Laporan ini menerapkan empat model regresi berdasarkan skala variabel respons: regresi logistik biner untuk respons dua kategori, regresi logistik multinomial untuk respons nominal lebih dari dua kategori, regresi logistik ordinal untuk respons berurutan, dan regresi Poisson untuk respons hitungan. Alur analisis mengikuti materi regresi kategorik dan Poisson: pemahaman data, estimasi model, uji simultan \(G^2\), uji Wald, pemilihan model terbaik, interpretasi variabel yang mendukung respons, dan prediksi menggunakan model terbaik.

1.2 Library yang Digunakan

Library berikut digunakan untuk membaca data, mengolah data, membangun model regresi, membuat visualisasi, dan menyajikan tabel pada dokumen HTML.

library(readxl)
library(dplyr)
library(tidyr)
library(ggplot2)
library(nnet)
library(MASS)
library(broom)
library(knitr)
library(kableExtra)
library(scales)
library(tibble)
library(purrr)
library(stringr)
library(pROC)

1.3 Catatan Khusus

Catatan 1 — Kriteria pemilihan model terbaik.
Kriteria pemilihan model terbaik dalam laporan ini mengutamakan jumlah variabel X yang signifikan terlebih dahulu, kemudian mempertimbangkan AIC terkecil. Artinya, apabila satu model memiliki lebih banyak variabel signifikan meskipun AIC-nya sedikit lebih besar, model tersebut tetap lebih diprioritaskan daripada model lain yang AIC-nya lebih kecil tetapi kehilangan banyak variabel signifikan.

Catatan 2 — Interpretasi hasil model.
Interpretasi koefisien, odds ratio, relative risk ratio, dan incidence rate ratio dalam laporan ini bersifat asosiasi statistik, bukan klaim sebab-akibat. Dengan demikian, variabel X yang signifikan dipahami sebagai variabel yang mendukung atau berkaitan dengan variasi variabel Y dalam model, bukan sebagai penyebab langsung.

2 Sumber Data

data_sources <- tibble(
  bagian=c("Regresi logistik biner","Regresi logistik multinomial","Regresi logistik ordinal","Regresi Poisson tahunan"),
  topik=c("Food Crisis","Environment Priority","Government Confidence","Violence Case Count"),
  sumber=c("World Bank - World Development Indicators","World Values Survey Wave 7","World Values Survey Wave 7","UCDP Georeferenced Event Dataset Global"),
  tautan=c("https://databank.worldbank.org/source/world-development-indicators","https://www.worldvaluessurvey.org/WVSDocumentationWV7.jsp","https://www.worldvaluessurvey.org/WVSDocumentationWV7.jsp","https://ucdp.uu.se/downloads/#ged_global"),
  file_model_ready=c("wdi_binary_logit_model_ready.xlsx","multinomial_model_ready_sample.xlsx","ordinal_model_ready_sample.xlsx","poisson_model_ready.xlsx")
)
safe_table(data_sources, "Sumber data dan file model-ready yang digunakan", full_width=TRUE)
Tabel 1. Sumber data dan file model-ready yang digunakan
bagian topik sumber tautan file_model_ready
Regresi logistik biner Food Crisis World Bank - World Development Indicators https://databank.worldbank.org/source/world-development-indicators wdi_binary_logit_model_ready.xlsx
Regresi logistik multinomial Environment Priority World Values Survey Wave 7 https://www.worldvaluessurvey.org/WVSDocumentationWV7.jsp multinomial_model_ready_sample.xlsx
Regresi logistik ordinal Government Confidence World Values Survey Wave 7 https://www.worldvaluessurvey.org/WVSDocumentationWV7.jsp ordinal_model_ready_sample.xlsx
Regresi Poisson tahunan Violence Case Count UCDP Georeferenced Event Dataset Global https://ucdp.uu.se/downloads/#ged_global poisson_model_ready.xlsx

3 Bagian I — Regresi Logistik Biner

3.1 Konsep model

Regresi logistik biner digunakan ketika respons hanya memiliki dua kemungkinan hasil. Model logit menghubungkan prediktor dengan log odds kejadian:

\[ \log\left(\frac{p_i}{1-p_i}\right)=\beta_0+\beta_1X_{1i}+\cdots+\beta_kX_{ki} \]

Uji simultan model dilakukan dengan statistik likelihood ratio:

\[ G^2=-2(\ell_0-\ell_1)=2(\ell_1-\ell_0) \]

Jika p-value dari \(G^2\) kecil, model penuh lebih baik daripada model kosong.

3.2 Import, struktur data, dan distribusi respons

binary_data <- readxl::read_excel(resolve_path("wdi_binary_logit_model_ready.xlsx")) |>
  clean_model_data() |>
  mutate(food_crisis=as.integer(food_crisis), food_crisis_label=factor(food_crisis, levels=c(0,1), labels=c("non_food_crisis","food_crisis")))

binary_dict <- make_dictionary(binary_data)
binary_summary <- binary_data |> count(food_crisis, food_crisis_label, name="n") |> mutate(proporsi=n/sum(n))

safe_table(binary_dict, "Kamus data biner setelah seleksi variabel analisis")
Tabel 2. Kamus data biner setelah seleksi variabel analisis
variabel tipe missing unique_value
food_crisis integer 0 2
year numeric 0 14
log_gdp_per_capita numeric 0 1988
inflation numeric 0 1988
unemployment numeric 0 1844
urban_population numeric 0 1974
population_growth numeric 0 1988
health_expenditure numeric 0 1988
food_crisis_label factor 0 2
safe_table(binary_summary, "Distribusi respons biner food_crisis", digits=3)
Tabel 3. Distribusi respons biner food_crisis
food_crisis food_crisis_label n proporsi
0 non_food_crisis 1336 0.672
1 food_crisis 652 0.328
binary_data |> count(food_crisis_label) |>
  ggplot(aes(x=food_crisis_label, y=n, fill=food_crisis_label))+
  geom_col(width=.68)+geom_text(aes(label=n), vjust=-.45, fontface="bold")+
  scale_fill_manual(values=binary_palette)+
  labs(title="Distribusi Respons Food Crisis", subtitle="Respons biner: 0 = non-food crisis dan 1 = food crisis.", x="Kategori respons", y="Jumlah observasi", fill="Kategori")+
  theme_report()
Gambar 1. Distribusi kategori respons food_crisis.

Gambar 1. Distribusi kategori respons food_crisis.

Variabel food_crisis sudah sesuai untuk regresi logistik biner karena hanya memiliki dua kategori. Distribusi kelas dibaca sebelum pemodelan karena ketidakseimbangan kelas dapat membuat accuracy terlihat baik meskipun model kurang sensitif dalam mengenali kelas kejadian.

3.3 Statistik Deskriptif dan Eksplorasi Data

binary_descriptive <- numeric_descriptive(binary_data, response="food_crisis")
binary_group_summary <- binary_data |>
  select(food_crisis_label, where(is.numeric)) |>
  select(-food_crisis) |>
  group_by(food_crisis_label) |>
  summarise(across(everything(), ~mean(.x, na.rm=TRUE)), .groups="drop") |>
  pivot_longer(-food_crisis_label, names_to="variabel", values_to="mean") |>
  pivot_wider(names_from=food_crisis_label, values_from=mean)

safe_table(binary_descriptive, "Statistik deskriptif prediktor numerik pada data logistik biner", digits=3, full_width=TRUE)
Tabel 4. Statistik deskriptif prediktor numerik pada data logistik biner
variabel n missing mean sd min q1 median q3 max
year 1988 0 2016.500 4.032 2010.000 2013.000 2016.500 2020.000 2023.000
log_gdp_per_capita 1988 0 8.705 1.410 5.801 7.622 8.671 9.793 11.813
inflation 1988 0 5.215 9.431 -12.563 1.500 3.337 6.409 221.342
unemployment 1988 0 7.698 5.822 0.119 3.686 5.651 10.183 34.007
urban_population 1988 0 59.327 21.275 13.000 43.269 61.541 76.150 100.000
population_growth 1988 0 1.323 1.421 -3.218 0.358 1.282 2.275 14.225
health_expenditure 1988 0 6.409 2.675 1.752 4.343 6.108 8.224 23.088
safe_table(binary_group_summary, "Rata-rata prediktor menurut status food_crisis", digits=3, full_width=TRUE)
Tabel 5. Rata-rata prediktor menurut status food_crisis
variabel non_food_crisis food_crisis
year 2016.527 2016.445
log_gdp_per_capita 9.353 7.376
inflation 4.490 6.702
unemployment 7.799 7.490
urban_population 66.479 44.673
population_growth 0.877 2.237
health_expenditure 6.949 5.303
binary_plot_vars <- binary_x <- model_predictors(binary_data, response="food_crisis")
binary_plot_vars <- binary_plot_vars[seq_len(min(4, length(binary_plot_vars)))]
binary_data |>
  select(food_crisis_label, all_of(binary_plot_vars)) |>
  pivot_longer(-food_crisis_label, names_to="variabel", values_to="nilai") |>
  ggplot(aes(x=food_crisis_label, y=nilai, fill=food_crisis_label))+
  geom_boxplot(alpha=.75, outlier.alpha=.25)+
  facet_wrap(~variabel, scales="free_y")+
  scale_fill_manual(values=binary_palette)+
  labs(title="Eksplorasi Prediktor terhadap Status Food Crisis", x="Status food crisis", y="Nilai prediktor", fill="Kategori")+
  theme_report()
Gambar 2. Eksplorasi prediktor numerik utama menurut status food_crisis.

Gambar 2. Eksplorasi prediktor numerik utama menurut status food_crisis.

3.4 Pembagian data training dan testing

binary_split <- split_train_test(binary_data, strata="food_crisis", prop=.80)
binary_train <- binary_split$train; binary_test <- binary_split$test
binary_split_table <- bind_rows(binary_train |> count(food_crisis_label, name="n") |> mutate(subset="Training"), binary_test |> count(food_crisis_label, name="n") |> mutate(subset="Testing")) |> group_by(subset) |> mutate(proporsi=n/sum(n)) |> ungroup() |> select(subset, food_crisis_label, n, proporsi)
safe_table(binary_split_table, "Komposisi kelas food_crisis pada data training dan testing", digits=3)
Tabel 6. Komposisi kelas food_crisis pada data training dan testing
subset food_crisis_label n proporsi
Training non_food_crisis 1068 0.672
Training food_crisis 521 0.328
Testing non_food_crisis 268 0.672
Testing food_crisis 131 0.328

3.5 Kandidat model dan pemilihan model terbaik

binary_x <- model_predictors(binary_train, response="food_crisis")
binary_selection <- select_best_glm(binary_train, response="food_crisis", predictors=binary_x, family=binomial(link="logit"), effect_name="odds_ratio")
binary_best_fit <- binary_selection$best$fit
binary_best_null <- glm(food_crisis ~ 1, data=binary_train, family=binomial(link="logit"))
binary_best_wald <- binary_selection$best$wald
binary_best_g2 <- binary_selection$best$g2
binary_selected_vars <- binary_best_wald |> filter(p_value < 0.05) |> pull(variabel)

binary_model_selection_table <- binary_selection$summary |>
  mutate(p_value_G2 = p_value_G2_tampil) |>
  select(model_ke, formula, jumlah_prediktor, jumlah_variabel_signifikan, AIC, G2, p_value_G2)

binary_fit_table <- tibble(keterangan=c("Jumlah observasi training","Jumlah prediktor model terbaik","AIC model terbaik","Log-likelihood model terbaik","Log-likelihood model kosong","McFadden pseudo R-square"), nilai=c(nrow(binary_train), length(binary_selection$best$vars), AIC(binary_best_fit), as.numeric(logLik(binary_best_fit)), as.numeric(logLik(binary_best_null)), mcfadden_r2(binary_best_fit,binary_best_null)))

binary_best_wald_show <- binary_best_wald |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
binary_best_g2_show <- binary_best_g2 |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)

safe_table(binary_model_selection_table, "Perbandingan kandidat model logistik biner berdasarkan jumlah variabel signifikan dan AIC", digits=4, full_width=TRUE)
Tabel 7. Perbandingan kandidat model logistik biner berdasarkan jumlah variabel signifikan dan AIC
model_ke formula jumlah_prediktor jumlah_variabel_signifikan AIC G2 p_value_G2
4 food_crisis ~ year + log_gdp_per_capita + unemployment + population_growth 4 4 997.2477 1023.366 < 0.001
3 food_crisis ~ year + log_gdp_per_capita + unemployment + urban_population + population_growth 5 4 997.8557 1024.758 < 0.001
2 food_crisis ~ year + log_gdp_per_capita + unemployment + urban_population + population_growth + health_expenditure 6 4 999.1942 1025.420 < 0.001
1 food_crisis ~ year + log_gdp_per_capita + inflation + unemployment + urban_population + population_growth + health_expenditure 7 4 1000.9461 1025.668 < 0.001
safe_table(binary_fit_table, "Ringkasan kecocokan model terbaik logistik biner", digits=4)
Tabel 8. Ringkasan kecocokan model terbaik logistik biner
keterangan nilai
Jumlah observasi training 1589.0000
Jumlah prediktor model terbaik 4.0000
AIC model terbaik 997.2477
Log-likelihood model terbaik -493.6239
Log-likelihood model kosong -1005.3071
McFadden pseudo R-square 0.5090
safe_table(binary_best_g2_show, "Uji simultan G² model terbaik logistik biner", digits=4)
Tabel 9. Uji simultan G² model terbaik logistik biner
uji G2 df p_value keputusan penjelasan_hasil kesimpulan
Uji likelihood ratio 1023.366 4 < 0.001 Tolak H0 Model dengan prediktor memberikan peningkatan kecocokan dibanding model kosong. Model layak digunakan karena prediktor secara simultan lebih baik daripada model kosong.
safe_table(binary_best_wald_show, "Uji Wald parsial dan odds ratio model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 10. Uji Wald parsial dan odds ratio model terbaik logistik biner
variabel estimate std_error z_value p_value odds_ratio ci_95
log_gdp_per_capita -1.8753 0.1107 -16.9340 < 0.001 0.1533 0.123 - 0.19
population_growth 0.4655 0.0629 7.4014 < 0.001 1.5928 1.408 - 1.802
unemployment 0.0902 0.0129 7.0088 < 0.001 1.0944 1.067 - 1.122
year 0.0532 0.0202 2.6369 0.008 1.0547 1.014 - 1.097
binary_effect_interpretation <- make_effect_interpretation(
  binary_best_wald,
  effect_col="odds_ratio",
  effect_label="OR",
  response_name="food_crisis"
)
safe_table(binary_effect_interpretation, "Interpretasi model terbaik logistik biner berdasarkan estimate dan odds ratio", digits=3, full_width=TRUE)
Tabel 11. Interpretasi model terbaik logistik biner berdasarkan estimate dan odds ratio
variabel estimate p_value OR ci_95 interpretasi_hasil
log_gdp_per_capita -1.875 < 0.001 0.153 0.123 - 0.19 Dengan variabel lain konstan, setiap kenaikan satu unit pada log_gdp_per_capita membuat odds terjadinya food_crisis menurun sekitar 84.67% (estimate = -1.8753; OR = 0.153; CI 95% = 0.123 - 0.19; p-value = < 0.001). Artinya, log_gdp_per_capita merupakan variabel X yang mendukung peluang kejadian pada model logistik.
population_growth 0.466 < 0.001 1.593 1.408 - 1.802 Dengan variabel lain konstan, setiap kenaikan satu unit pada population_growth membuat odds terjadinya food_crisis meningkat sekitar 59.28% (estimate = 0.4655; OR = 1.593; CI 95% = 1.408 - 1.802; p-value = < 0.001). Artinya, population_growth merupakan variabel X yang mendukung peluang kejadian pada model logistik.
unemployment 0.090 < 0.001 1.094 1.067 - 1.122 Dengan variabel lain konstan, setiap kenaikan satu unit pada unemployment membuat odds terjadinya food_crisis meningkat sekitar 9.44% (estimate = 0.0902; OR = 1.094; CI 95% = 1.067 - 1.122; p-value = < 0.001). Artinya, unemployment merupakan variabel X yang mendukung peluang kejadian pada model logistik.
year 0.053 0.008 1.055 1.014 - 1.097 Dengan variabel lain konstan, setiap kenaikan satu unit pada year membuat odds terjadinya food_crisis meningkat sekitar 5.47% (estimate = 0.0532; OR = 1.055; CI 95% = 1.014 - 1.097; p-value = 0.008). Artinya, year merupakan variabel X yang mendukung peluang kejadian pada model logistik.

Model terbaik logistik biner adalah kandidat ke-4 karena memiliki 4 variabel signifikan dan AIC sebesar 997.248. Uji simultan \(G^2\) menghasilkan p-value < 0.001, sehingga model terbaik memberikan peningkatan kecocokan dibanding model kosong. Berdasarkan uji Wald, variabel X yang signifikan dan mendukung food_crisis adalah: log_gdp_per_capita, population_growth, unemployment, year. Dengan demikian, prediksi selanjutnya dilakukan menggunakan model terbaik tersebut, bukan model penuh awal.

3.6 Uji Asumsi Model Terbaik

3.6.1 Multikolinearitas — VIF

Hipotesis/Kriteria:
\(H_0\): Tidak terdapat multikolinearitas berat antarprediktor.
\(H_1\): Terdapat multikolinearitas berat antarprediktor.
Kriteria praktis: VIF < 5 menunjukkan tidak ada indikasi multikolinearitas berat; VIF 5–10 perlu perhatian; VIF > 10 mengindikasikan multikolinearitas berat.

binary_vif_table <- vif_table(binary_train, binary_selection$best$vars)
safe_table(binary_vif_table, "Uji multikolinearitas model terbaik logistik biner menggunakan VIF", digits=3, full_width=TRUE)
Tabel 12. Uji multikolinearitas model terbaik logistik biner menggunakan VIF
variabel VIF keterangan penjelasan_hasil kesimpulan
population_growth 1.214 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
log_gdp_per_capita 1.154 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
unemployment 1.059 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
year 1.016 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.

3.6.2 Likelihood Ratio Test (LRT)

Hipotesis:
\(H_0\): Semua koefisien prediktor sama dengan nol, sehingga model dengan prediktor tidak lebih baik daripada model kosong.
\(H_1\): Minimal ada satu koefisien prediktor tidak sama dengan nol, sehingga model dengan prediktor lebih baik daripada model kosong.

binary_lrt_show <- binary_best_g2 |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
safe_table(binary_lrt_show, "Likelihood Ratio Test atau uji G² model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 13. Likelihood Ratio Test atau uji G² model terbaik logistik biner
uji G2 df p_value keputusan penjelasan_hasil kesimpulan
Uji likelihood ratio 1023.366 4 < 0.001 Tolak H0 Model dengan prediktor memberikan peningkatan kecocokan dibanding model kosong. Model layak digunakan karena prediktor secara simultan lebih baik daripada model kosong.

3.6.3 Hosmer-Lemeshow Goodness-of-Fit

Hipotesis:
\(H_0\): Model memiliki kecocokan yang baik; tidak ada perbedaan bermakna antara frekuensi aktual dan frekuensi harapan.
\(H_1\): Model tidak memiliki kecocokan yang baik; terdapat perbedaan bermakna antara frekuensi aktual dan frekuensi harapan.

binary_train_prob <- predict(binary_best_fit, newdata=binary_train, type="response")
binary_hl <- hosmer_lemeshow_table(actual=binary_train$food_crisis, predicted=binary_train_prob, g=10)
binary_hl_test <- binary_hl$test |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
safe_table(binary_hl$detail, "Detail pengelompokan Hosmer-Lemeshow model terbaik logistik biner", digits=3, full_width=TRUE)
Tabel 14. Detail pengelompokan Hosmer-Lemeshow model terbaik logistik biner
group observed expected n non_observed non_expected
1 0 0.261 159 159 158.739
2 1 0.864 159 158 158.136
3 2 2.427 159 157 156.573
4 1 6.699 159 158 152.301
5 10 17.580 159 149 141.420
6 48 36.536 159 111 122.464
7 80 66.758 159 79 92.242
8 111 106.143 159 48 52.857
9 116 134.646 159 43 24.354
10 152 149.086 158 6 8.914
safe_table(binary_hl_test, "Uji Hosmer-Lemeshow Goodness-of-Fit model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 15. Uji Hosmer-Lemeshow Goodness-of-Fit model terbaik logistik biner
uji statistik df p_value keputusan penjelasan_hasil kesimpulan
Hosmer-Lemeshow Goodness-of-Fit 36.8279 8 < 0.001 Ada indikasi lack of fit H0 ditolak; terdapat perbedaan bermakna antara frekuensi aktual dan harapan. Model digunakan dengan catatan terdapat indikasi lack of fit berdasarkan Hosmer-Lemeshow.

3.6.4 Linearitas Log-Odds (Box-Tidwell)

Hipotesis:
\(H_0\): Hubungan prediktor numerik dengan log-odds bersifat linear.
\(H_1\): Hubungan prediktor numerik dengan log-odds tidak linear.

binary_box_tidwell <- box_tidwell_table(binary_train, response="food_crisis", predictors=binary_selection$best$vars, family=binomial(link="logit")) |>
  mutate(p_value=p_value_tampil) |>
  select(-p_value_tampil)
safe_table(binary_box_tidwell, "Uji linearitas log-odds Box-Tidwell model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 16. Uji linearitas log-odds Box-Tidwell model terbaik logistik biner
variabel estimate p_value keterangan penjelasan_hasil kesimpulan
year 53.3512 0.022 Ada indikasi non-linearitas logit H0 ditolak; hubungan prediktor dengan logit tidak linear. Model masih dapat digunakan, tetapi asumsi linearitas logit perlu diberi catatan.
log_gdp_per_capita -0.2924 0.829 Tidak ada bukti kuat non-linearitas logit H0 gagal ditolak; hubungan prediktor dengan logit relatif linear. Model layak digunakan karena linearitas logit relatif terpenuhi.
unemployment 0.0666 0.060 Tidak ada bukti kuat non-linearitas logit H0 gagal ditolak; hubungan prediktor dengan logit relatif linear. Model layak digunakan karena linearitas logit relatif terpenuhi.
population_growth -2.7188 < 0.001 Ada indikasi non-linearitas logit H0 ditolak; hubungan prediktor dengan logit tidak linear. Model masih dapat digunakan, tetapi asumsi linearitas logit perlu diberi catatan.

3.6.5 Nagelkerke \(R^2\)

Nagelkerke \(R^2\) bukan uji hipotesis, sehingga tidak memiliki \(H_0\) dan \(H_1\). Ukuran ini digunakan sebagai indeks kekuatan penjelasan model. Nilai yang lebih besar menunjukkan model memberikan peningkatan kecocokan yang lebih baik dibanding model kosong.

binary_nagelkerke_table <- tibble(
  ukuran="Nagelkerke R-square",
  nilai=nagelkerke_r2(binary_best_fit, binary_best_null),
  keterangan="Semakin besar nilainya, semakin besar peningkatan kecocokan model dibanding model kosong."
)
safe_table(binary_nagelkerke_table, "Nagelkerke R-square model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 17. Nagelkerke R-square model terbaik logistik biner
ukuran nilai keterangan
Nagelkerke R-square 0.6615 Semakin besar nilainya, semakin besar peningkatan kecocokan model dibanding model kosong.

3.6.6 Diagnostik Pengaruh Observasi

Diagnostik pengaruh observasi bukan uji hipotesis formal. Pemeriksaan ini digunakan untuk melihat apakah ada observasi yang terlalu memengaruhi estimasi model. Kriteria praktis: Cook’s distance > 1 perlu diperiksa, dan leverage tinggi dilihat dengan batas \(2p/n\).

binary_influence_table <- influence_check_table(binary_best_fit)
safe_table(binary_influence_table, "Diagnostik pengaruh observasi model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 18. Diagnostik pengaruh observasi model terbaik logistik biner
diagnostik nilai keterangan penjelasan_hasil kesimpulan
Maximum Cook’s distance 0.1079 Nilai Cook’s distance yang besar menunjukkan observasi yang berpengaruh kuat. Semakin kecil nilai maksimum Cook’s distance, semakin kecil pengaruh observasi ekstrem terhadap model. Model dapat digunakan; nilai maksimum Cook’s distance menjadi catatan stabilitas model.
Jumlah Cook’s distance > 1 0.0000 Jumlah observasi dengan Cook’s distance > 1 perlu diperiksa lebih lanjut. Tidak ada observasi dengan Cook’s distance > 1. Model layak digunakan karena tidak ada Cook’s distance > 1.
Maximum leverage 0.1408 Leverage tinggi menunjukkan observasi dengan kombinasi prediktor yang ekstrem. Nilai leverage maksimum menunjukkan observasi dengan kombinasi prediktor paling ekstrem. Model dapat digunakan; leverage maksimum menjadi catatan untuk observasi dengan kombinasi prediktor ekstrem.
Jumlah leverage > 2p/n 150.0000 Aturan praktis leverage tinggi menggunakan batas 2p/n.  Ada observasi dengan leverage tinggi. Model digunakan dengan catatan terdapat observasi berleverage tinggi.

3.6.7 Ringkasan Uji Asumsi

binary_assumption_summary <- tibble(
  uji=c("VIF", "Likelihood Ratio Test", "Hosmer-Lemeshow", "Box-Tidwell", "Nagelkerke R-square", "Cook's distance"),
  angka_hasil=c(
    ifelse(all(is.na(binary_vif_table$VIF)), "VIF tidak dihitung", paste0("VIF maksimum = ", round(max(binary_vif_table$VIF, na.rm=TRUE), 3))),
    paste0("G² = ", round(binary_best_g2$G2, 3), "; df = ", binary_best_g2$df, "; p-value = ", binary_best_g2$p_value_tampil),
    paste0("HL = ", round(binary_hl$test$statistik, 3), "; df = ", binary_hl$test$df, "; p-value = ", binary_hl$test$p_value_tampil),
    paste0("Jumlah indikasi non-linearitas = ", sum(binary_box_tidwell$keterangan == "Ada indikasi non-linearitas logit", na.rm=TRUE)),
    paste0("Nagelkerke R² = ", round(binary_nagelkerke_table$nilai, 4)),
    paste0("Maksimum Cook's distance = ", round(binary_influence_table$nilai[1], 4), "; jumlah Cook > 1 = ", binary_influence_table$nilai[2])
  ),
  interpretasi_hasil=c(
    ifelse(all(is.na(binary_vif_table$VIF)) || max(binary_vif_table$VIF, na.rm=TRUE) < 5, "Tidak ada indikasi multikolinearitas berat pada model terbaik.", "Terdapat indikasi multikolinearitas yang perlu diperhatikan."),
    ifelse(binary_best_g2$p_value < .05, "Model terbaik signifikan secara simultan dan lebih baik daripada model kosong.", "Model terbaik belum terbukti lebih baik daripada model kosong secara simultan."),
    ifelse(binary_hl$test$p_value < .05, "Terdapat indikasi lack of fit berdasarkan Hosmer-Lemeshow.", "Tidak ada bukti kuat lack of fit berdasarkan Hosmer-Lemeshow."),
    ifelse(sum(binary_box_tidwell$keterangan == "Ada indikasi non-linearitas logit", na.rm=TRUE) > 0, "Ada prediktor yang berpotensi tidak linear pada skala logit.", "Tidak ada bukti kuat pelanggaran linearitas log-odds."),
    "Nilai Nagelkerke R² menunjukkan proporsi peningkatan kecocokan model dibanding model kosong.",
    ifelse(binary_influence_table$nilai[2] > 0, "Ada observasi yang berpengaruh kuat dan perlu dicek.", "Tidak ada observasi dengan Cook's distance > 1.")
  ),
  kesimpulan=c(
    ifelse(all(is.na(binary_vif_table$VIF)) || max(binary_vif_table$VIF, na.rm=TRUE) < 5, "Model layak digunakan karena tidak ada indikasi multikolinearitas berat.", "Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena ada indikasi multikolinearitas."),
    ifelse(binary_best_g2$p_value < .05, "Model layak digunakan karena prediktor secara simultan mendukung food_crisis.", "Model dapat dilaporkan dengan catatan dukungan simultan belum kuat."),
    ifelse(binary_hl$test$p_value < .05, "Model digunakan dengan catatan terdapat indikasi lack of fit.", "Model layak digunakan karena tidak ada bukti kuat lack of fit."),
    ifelse(sum(binary_box_tidwell$keterangan == "Ada indikasi non-linearitas logit", na.rm=TRUE) > 0, "Model masih dapat digunakan, tetapi asumsi linearitas logit perlu diberi catatan.", "Model layak digunakan karena linearitas log-odds relatif terpenuhi."),
    "Model memiliki kemampuan penjelasan yang dapat dibaca melalui Nagelkerke R².",
    ifelse(binary_influence_table$nilai[2] > 0, "Model dapat digunakan dengan catatan ada observasi berpengaruh.", "Model layak digunakan karena tidak ada Cook's distance > 1.")
  )
)
safe_table(binary_assumption_summary, "Ringkasan uji asumsi model terbaik logistik biner", digits=4, full_width=TRUE)
Tabel 19. Ringkasan uji asumsi model terbaik logistik biner
uji angka_hasil interpretasi_hasil kesimpulan
VIF VIF maksimum = 1.214 Tidak ada indikasi multikolinearitas berat pada model terbaik. Model layak digunakan karena tidak ada indikasi multikolinearitas berat.
Likelihood Ratio Test G² = 1023.366; df = 4; p-value = < 0.001 Model terbaik signifikan secara simultan dan lebih baik daripada model kosong. Model layak digunakan karena prediktor secara simultan mendukung food_crisis.
Hosmer-Lemeshow HL = 36.828; df = 8; p-value = < 0.001 Terdapat indikasi lack of fit berdasarkan Hosmer-Lemeshow. Model digunakan dengan catatan terdapat indikasi lack of fit.
Box-Tidwell Jumlah indikasi non-linearitas = 2 Ada prediktor yang berpotensi tidak linear pada skala logit. Model masih dapat digunakan, tetapi asumsi linearitas logit perlu diberi catatan.
Nagelkerke R-square Nagelkerke R² = 0.6615 Nilai Nagelkerke R² menunjukkan proporsi peningkatan kecocokan model dibanding model kosong. Model memiliki kemampuan penjelasan yang dapat dibaca melalui Nagelkerke R².
Cook’s distance Maksimum Cook’s distance = 0.1079; jumlah Cook > 1 = 0 Tidak ada observasi dengan Cook’s distance > 1. Model layak digunakan karena tidak ada Cook’s distance > 1.

3.7 Prediksi dengan model terbaik

binary_prob <- predict(binary_best_fit, newdata=binary_test, type="response")
binary_pred <- ifelse(binary_prob >= .50, 1, 0)
binary_cm <- table(Aktual=binary_test$food_crisis, Prediksi=binary_pred)
binary_metric_table <- binary_metrics(binary_test$food_crisis, binary_pred)
binary_cm_table <- cm_labeled(binary_cm)
names(binary_cm_table) <- c("Aktual \\ Prediksi", "Prediksi: non_food_crisis (0)", "Prediksi: food_crisis (1)")
binary_cm_table[["Aktual \\ Prediksi"]] <- c("Aktual: non_food_crisis (0)", "Aktual: food_crisis (1)")
safe_table(binary_cm_table, "Confusion matrix logistik biner model terbaik dengan keterangan aktual dan prediksi")
Tabel 20. Confusion matrix logistik biner model terbaik dengan keterangan aktual dan prediksi
Aktual  Prediksi Prediksi: non_food_crisis (0) Prediksi: food_crisis (1)
Aktual: non_food_crisis (0) 241 27
Aktual: food_crisis (1) 34 97
safe_table(binary_metric_table, "Metrik evaluasi testing model terbaik logistik biner pada threshold 0,50", digits=3)
Tabel 21. Metrik evaluasi testing model terbaik logistik biner pada threshold 0,50
threshold accuracy error_rate sensitivity specificity precision f1_score balanced_accuracy
0.5 0.847 0.153 0.74 0.899 0.782 0.761 0.82
binary_roc <- pROC::roc(response=binary_test$food_crisis, predictor=binary_prob, quiet=TRUE)
binary_auc <- as.numeric(pROC::auc(binary_roc))
roc_df <- tibble(specificity=binary_roc$specificities, sensitivity=binary_roc$sensitivities) |> mutate(false_positive_rate=1-specificity)
roc_df |> ggplot(aes(x=false_positive_rate,y=sensitivity))+geom_line(linewidth=1.15,color="#6d28d9")+geom_abline(slope=1,intercept=0,linetype="dashed",color="#e11d48")+coord_equal()+labs(title="Kurva ROC Model Terbaik Logistik Biner", subtitle=paste0("AUC = ",round(binary_auc,3)), x="False positive rate", y="True positive rate / sensitivity")+theme_report()
Gambar 2. Kurva ROC model terbaik logistik biner.

Gambar 2. Kurva ROC model terbaik logistik biner.

Interpretasi hasil prediksi: Pada data testing, model terbaik menghasilkan accuracy sebesar 0.847, sensitivity sebesar 0.74, specificity sebesar 0.899, F1-score sebesar 0.761, dan AUC sebesar 0.921. Berdasarkan confusion matrix, model memprediksi benar food_crisis sebanyak 97 observasi dan benar non_food_crisis sebanyak 241 observasi. Kesalahan prediksi terdiri dari false positive sebanyak 27 dan false negative sebanyak 34. Dengan demikian, model dapat digunakan untuk prediksi karena performanya tidak hanya dilihat dari accuracy, tetapi juga dari kemampuan mengenali kelas kejadian melalui sensitivity dan kemampuan membedakan dua kelas melalui AUC.

4 Bagian II — Regresi Logistik Multinomial

4.1 Konsep model

Regresi logistik multinomial digunakan ketika respons bersifat nominal dan memiliki lebih dari dua kategori. Dengan kategori referensi \(Y=1\), model untuk kategori \(j\) adalah:

\[ \log\left(\frac{P(Y_i=j)}{P(Y_i=1)}\right)=\alpha_j+\beta_{j1}X_{1i}+\cdots+\beta_{jk}X_{ki} \]

Ukuran efek yang digunakan adalah relative risk ratio (RRR), yaitu \(\exp(\beta)\).

4.2 Import, struktur data, dan distribusi respons

multinom_data <- readxl::read_excel(resolve_path("multinomial_model_ready_sample.xlsx")) |>
  clean_model_data() |>
  mutate(choice_code=factor(choice_code, levels=sort(unique(choice_code)), labels=c("protect_environment","economic_growth_jobs","other_answer")[seq_along(sort(unique(choice_code)))]))
multinom_dict <- make_dictionary(multinom_data)
multinom_summary <- multinom_data |> count(choice_code, name="n") |> mutate(proporsi=n/sum(n))
safe_table(multinom_dict, "Kamus data multinomial setelah seleksi variabel analisis")
Tabel 22. Kamus data multinomial setelah seleksi variabel analisis
variabel tipe missing unique_value
choice_code factor 0 3
age numeric 0 76
education_code numeric 0 9
income_scale numeric 0 10
gender_label_male numeric 0 2
employment_label_housewife_or_not_working numeric 0 2
employment_label_other numeric 0 2
employment_label_part_time_employee numeric 0 2
employment_label_retired numeric 0 2
employment_label_self_employed numeric 0 2
employment_label_student numeric 0 2
employment_label_unemployed numeric 0 2
safe_table(multinom_summary, "Distribusi respons multinomial choice_code", digits=3)
Tabel 23. Distribusi respons multinomial choice_code
choice_code n proporsi
protect_environment 4893 0.572
economic_growth_jobs 3403 0.398
other_answer 262 0.031
multinom_summary |> ggplot(aes(x=reorder(choice_code,n), y=n, fill=choice_code))+geom_col(width=.70)+geom_text(aes(label=n), hjust=-.15, fontface="bold")+coord_flip()+scale_fill_manual(values=report_palette)+labs(title="Distribusi Environment Priority", subtitle="Respons multinomial dengan kategori nominal.", x="Kategori respons", y="Jumlah observasi", fill="Kategori")+theme_report()
Gambar 3. Distribusi kategori respons multinomial.

Gambar 3. Distribusi kategori respons multinomial.

Respons choice_code bersifat nominal, sehingga tidak tepat diperlakukan sebagai urutan rendah-tinggi. Ketimpangan jumlah observasi antarkategori juga perlu dibaca karena kategori dengan frekuensi kecil dapat lebih sulit diprediksi secara stabil.

4.3 Statistik Deskriptif dan Eksplorasi Data

multinom_descriptive <- numeric_descriptive(multinom_data, response="choice_code")
multinom_x_preview <- model_predictors(multinom_data, response="choice_code")
multinom_group_summary <- multinom_data |>
  select(choice_code, all_of(multinom_x_preview)) |>
  group_by(choice_code) |>
  summarise(across(everything(), ~mean(.x, na.rm=TRUE)), .groups="drop") |>
  pivot_longer(-choice_code, names_to="variabel", values_to="mean") |>
  pivot_wider(names_from=choice_code, values_from=mean)

safe_table(multinom_descriptive, "Statistik deskriptif prediktor numerik pada data multinomial", digits=3, full_width=TRUE)
Tabel 24. Statistik deskriptif prediktor numerik pada data multinomial
variabel n missing mean sd min q1 median q3 max
age 8558 0 42.897 16.192 16 30 41 55 91
education_code 8558 0 3.599 2.026 0 2 3 6 8
income_scale 8558 0 4.923 2.089 1 4 5 6 10
gender_label_male 8558 0 0.479 0.500 0 0 0 1 1
employment_label_housewife_or_not_working 8558 0 0.127 0.332 0 0 0 0 1
employment_label_other 8558 0 0.013 0.112 0 0 0 0 1
employment_label_part_time_employee 8558 0 0.082 0.275 0 0 0 0 1
employment_label_retired 8558 0 0.122 0.327 0 0 0 0 1
employment_label_self_employed 8558 0 0.146 0.353 0 0 0 0 1
employment_label_student 8558 0 0.057 0.231 0 0 0 0 1
employment_label_unemployed 8558 0 0.080 0.271 0 0 0 0 1
safe_table(multinom_group_summary, "Rata-rata prediktor menurut kategori choice_code", digits=3, full_width=TRUE)
Tabel 25. Rata-rata prediktor menurut kategori choice_code
variabel protect_environment economic_growth_jobs other_answer
age 42.518 43.480 42.408
education_code 3.733 3.370 4.069
income_scale 4.949 4.882 4.981
gender_label_male 0.472 0.490 0.485
employment_label_housewife_or_not_working 0.122 0.135 0.099
employment_label_other 0.012 0.012 0.027
employment_label_part_time_employee 0.085 0.079 0.088
employment_label_retired 0.119 0.128 0.115
employment_label_self_employed 0.144 0.150 0.130
employment_label_student 0.064 0.046 0.069
employment_label_unemployed 0.077 0.084 0.088
multinom_plot_vars <- multinom_x_preview[seq_len(min(4, length(multinom_x_preview)))]
multinom_data |>
  select(choice_code, all_of(multinom_plot_vars)) |>
  pivot_longer(-choice_code, names_to="variabel", values_to="nilai") |>
  ggplot(aes(x=choice_code, y=nilai, fill=choice_code))+
  geom_boxplot(alpha=.75, outlier.alpha=.25)+
  facet_wrap(~variabel, scales="free_y")+
  scale_fill_manual(values=report_palette)+
  labs(title="Eksplorasi Prediktor terhadap Environment Priority", x="Kategori respons", y="Nilai prediktor", fill="Kategori")+
  theme_report()
Gambar 4. Eksplorasi prediktor numerik utama menurut kategori choice_code.

Gambar 4. Eksplorasi prediktor numerik utama menurut kategori choice_code.

4.4 Pembagian data training dan testing

multinom_split <- split_train_test(multinom_data, strata="choice_code", prop=.80)
multinom_train <- multinom_split$train; multinom_test <- multinom_split$test
multinom_split_table <- bind_rows(multinom_train |> count(choice_code, name="n") |> mutate(subset="Training"), multinom_test |> count(choice_code, name="n") |> mutate(subset="Testing")) |> group_by(subset) |> mutate(proporsi=n/sum(n)) |> ungroup() |> select(subset, choice_code, n, proporsi)
safe_table(multinom_split_table, "Komposisi respons multinomial pada data training dan testing", digits=3)
Tabel 26. Komposisi respons multinomial pada data training dan testing
subset choice_code n proporsi
Training protect_environment 3914 0.572
Training economic_growth_jobs 2722 0.398
Training other_answer 209 0.031
Testing protect_environment 979 0.572
Testing economic_growth_jobs 681 0.398
Testing other_answer 53 0.031

4.5 Kandidat model dan pemilihan model terbaik

multinom_x <- model_predictors(multinom_train, response="choice_code")
multinom_selection <- select_best_multinom(multinom_train, response="choice_code", predictors=multinom_x)
multinom_best_fit <- multinom_selection$best$fit
multinom_best_null <- nnet::multinom(choice_code ~ 1, data=multinom_train, trace=FALSE, Hess=TRUE, model=TRUE)
multinom_best_wald <- multinom_selection$best$wald
multinom_best_g2 <- multinom_selection$best$g2
multinom_selected_vars <- multinom_best_wald |> group_by(variabel) |> summarise(min_p=min(p_value, na.rm=TRUE), .groups="drop") |> filter(min_p < 0.05) |> pull(variabel)

multinom_model_selection_table <- multinom_selection$summary |>
  mutate(p_value_G2=p_value_G2_tampil) |>
  select(model_ke, formula, jumlah_prediktor, jumlah_variabel_signifikan, AIC, G2, p_value_G2)

multinom_fit_table <- tibble(keterangan=c("Jumlah observasi training","Jumlah prediktor model terbaik","AIC model terbaik","Log-likelihood model terbaik","Log-likelihood model kosong","McFadden pseudo R-square"), nilai=c(nrow(multinom_train), length(multinom_selection$best$vars), AIC(multinom_best_fit), as.numeric(logLik(multinom_best_fit)), as.numeric(logLik(multinom_best_null)), mcfadden_r2(multinom_best_fit,multinom_best_null)))

multinom_best_wald_show <- multinom_best_wald |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
multinom_best_g2_show <- multinom_best_g2 |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)

safe_table(multinom_model_selection_table, "Perbandingan kandidat model multinomial berdasarkan jumlah variabel signifikan dan AIC", digits=4, full_width=TRUE)
Tabel 27. Perbandingan kandidat model multinomial berdasarkan jumlah variabel signifikan dan AIC
model_ke formula jumlah_prediktor jumlah_variabel_signifikan AIC G2 p_value_G2
9 choice_code ~ education_code + employment_label_other + employment_label_student 3 3 10793.68 76.4217 < 0.001
8 choice_code ~ education_code + gender_label_male + employment_label_other + employment_label_student 4 3 10793.70 80.3946 < 0.001
7 choice_code ~ education_code + gender_label_male + employment_label_other + employment_label_part_time_employee + employment_label_student 5 3 10794.93 83.1670 < 0.001
6 choice_code ~ education_code + income_scale + gender_label_male + employment_label_other + employment_label_part_time_employee + employment_label_student 6 3 10796.70 85.4010 < 0.001
5 choice_code ~ education_code + income_scale + gender_label_male + employment_label_other + employment_label_part_time_employee + employment_label_self_employed + employment_label_student 7 3 10798.89 87.2084 < 0.001
4 choice_code ~ age + education_code + income_scale + gender_label_male + employment_label_other + employment_label_part_time_employee + employment_label_self_employed + employment_label_student 8 3 10801.60 88.5022 < 0.001
3 choice_code ~ age + education_code + income_scale + gender_label_male + employment_label_other + employment_label_part_time_employee + employment_label_self_employed + employment_label_student + employment_label_unemployed 9 3 10804.77 89.3259 < 0.001
2 choice_code ~ age + education_code + income_scale + gender_label_male + employment_label_housewife_or_not_working + employment_label_other + employment_label_part_time_employee + employment_label_self_employed + employment_label_student + employment_label_unemployed 10 3 10808.33 89.7669 < 0.001
1 choice_code ~ age + education_code + income_scale + gender_label_male + employment_label_housewife_or_not_working + employment_label_other + employment_label_part_time_employee + employment_label_retired + employment_label_self_employed + employment_label_student + employment_label_unemployed 11 3 10812.31 89.7834 < 0.001
safe_table(multinom_fit_table, "Ringkasan kecocokan model terbaik multinomial", digits=4)
Tabel 28. Ringkasan kecocokan model terbaik multinomial
keterangan nilai
Jumlah observasi training 6845.000
Jumlah prediktor model terbaik 3.000
AIC model terbaik 10793.676
Log-likelihood model terbaik -5388.838
Log-likelihood model kosong -5427.049
McFadden pseudo R-square 0.007
safe_table(multinom_best_g2_show, "Uji simultan G² model terbaik multinomial", digits=4)
Tabel 29. Uji simultan G² model terbaik multinomial
uji G2 df p_value keputusan penjelasan_hasil kesimpulan
Uji likelihood ratio 76.4217 6 < 0.001 Tolak H0 Model dengan prediktor memberikan peningkatan kecocokan dibanding model kosong. Model layak digunakan karena prediktor secara simultan lebih baik daripada model kosong.
safe_table(multinom_best_wald_show |> slice_head(n=30), "Uji Wald parsial dan RRR model terbaik multinomial", digits=4, full_width=TRUE)
Tabel 30. Uji Wald parsial dan RRR model terbaik multinomial
kategori_dibanding_referensi variabel estimate std_error z_value p_value relative_risk_ratio ci_95
economic_growth_jobs education_code -0.0833 0.0125 -6.6628 < 0.001 0.9200 0.898 - 0.943
other_answer education_code 0.1051 0.0352 2.9837 0.003 1.1108 1.037 - 1.19
economic_growth_jobs employment_label_student -0.3087 0.1134 -2.7235 0.006 0.7344 0.588 - 0.917
other_answer employment_label_other 1.0773 0.4126 2.6108 0.009 2.9367 1.308 - 6.593
economic_growth_jobs employment_label_other -0.0571 0.2313 -0.2466 0.805 0.9445 0.6 - 1.486
other_answer employment_label_student 0.0465 0.2850 0.1632 0.870 1.0476 0.599 - 1.832
multinom_effect_interpretation <- make_effect_interpretation(
  multinom_best_wald,
  effect_col="relative_risk_ratio",
  effect_label="RRR",
  response_name="choice_code",
  category_col="kategori_dibanding_referensi"
)
safe_table(multinom_effect_interpretation, "Interpretasi model terbaik multinomial berdasarkan estimate dan relative risk ratio", digits=3, full_width=TRUE)
Tabel 31. Interpretasi model terbaik multinomial berdasarkan estimate dan relative risk ratio
kategori variabel estimate p_value RRR ci_95 interpretasi_hasil
economic_growth_jobs education_code -0.083 < 0.001 0.920 0.898 - 0.943 Dengan variabel lain konstan, setiap kenaikan satu unit pada education_code membuat relative risk memilih kategori economic_growth_jobs dibanding kategori referensi menurun sekitar 8% (estimate = -0.0833; RRR = 0.92; CI 95% = 0.898 - 0.943; p-value = < 0.001). Artinya, education_code merupakan variabel X yang mendukung perbedaan kategori pada choice_code.
other_answer education_code 0.105 0.003 1.111 1.037 - 1.19 Dengan variabel lain konstan, setiap kenaikan satu unit pada education_code membuat relative risk memilih kategori other_answer dibanding kategori referensi meningkat sekitar 11.08% (estimate = 0.1051; RRR = 1.111; CI 95% = 1.037 - 1.19; p-value = 0.003). Artinya, education_code merupakan variabel X yang mendukung perbedaan kategori pada choice_code.
economic_growth_jobs employment_label_student -0.309 0.006 0.734 0.588 - 0.917 Dengan variabel lain konstan, setiap kenaikan satu unit pada employment_label_student membuat relative risk memilih kategori economic_growth_jobs dibanding kategori referensi menurun sekitar 26.56% (estimate = -0.3087; RRR = 0.734; CI 95% = 0.588 - 0.917; p-value = 0.006). Artinya, employment_label_student merupakan variabel X yang mendukung perbedaan kategori pada choice_code.
other_answer employment_label_other 1.077 0.009 2.937 1.308 - 6.593 Dengan variabel lain konstan, setiap kenaikan satu unit pada employment_label_other membuat relative risk memilih kategori other_answer dibanding kategori referensi meningkat sekitar 193.67% (estimate = 1.0773; RRR = 2.937; CI 95% = 1.308 - 6.593; p-value = 0.009). Artinya, employment_label_other merupakan variabel X yang mendukung perbedaan kategori pada choice_code.

Model terbaik multinomial adalah kandidat ke-9. Model ini dipilih karena mempertahankan 3 variabel signifikan, kemudian dibandingkan berdasarkan AIC. Uji \(G^2\) model terbaik memiliki p-value < 0.001, sehingga model dengan prediktor lebih informatif daripada model kosong. Variabel X yang mendukung perbedaan pilihan choice_code adalah: education_code, employment_label_other, employment_label_student. Karena respons multinomial memiliki beberapa kategori, satu variabel dapat signifikan pada salah satu perbandingan kategori meskipun tidak signifikan pada perbandingan lain.

4.6 Uji Asumsi Model Terbaik

4.6.1 Asumsi 1 — Respons Bersifat Nominal

Hipotesis/Kriteria:
\(H_0\): Variabel respons bersifat nominal dan kategori respons saling eksklusif.
\(H_1\): Variabel respons tidak bersifat nominal atau kategori tidak saling eksklusif.
Kriteria: regresi multinomial sesuai jika respons memiliki lebih dari dua kategori nominal tanpa urutan alami.

multinom_nominal_table <- multinom_train |>
  count(choice_code, name="n") |>
  mutate(proporsi=n/sum(n), skala_respons="Nominal")
safe_table(multinom_nominal_table, "Pemeriksaan skala respons nominal pada model multinomial", digits=3, full_width=TRUE)
Tabel 32. Pemeriksaan skala respons nominal pada model multinomial
choice_code n proporsi skala_respons
protect_environment 3914 0.572 Nominal
economic_growth_jobs 2722 0.398 Nominal
other_answer 209 0.031 Nominal

4.6.2 Asumsi 2 — Observasi Independen

Hipotesis/Kriteria:
\(H_0\): Observasi saling independen.
\(H_1\): Observasi tidak saling independen atau terdapat struktur pengelompokan/pengulangan.
Kriteria: setiap baris data harus merepresentasikan unit observasi berbeda dan tidak berulang tanpa koreksi.

multinom_independent_table <- tibble(
  asumsi="Observasi independen",
  pemeriksaan="Setiap baris data diperlakukan sebagai satu unit observasi yang berbeda.",
  hasil="Tidak ada struktur pengulangan responden yang digunakan dalam model-ready.",
  catatan="Apabila terdapat clustering survei atau panel, koreksi standard error dapat dipertimbangkan."
)
safe_table(multinom_independent_table, "Pemeriksaan asumsi observasi independen pada model multinomial", full_width=TRUE)
Tabel 33. Pemeriksaan asumsi observasi independen pada model multinomial
asumsi pemeriksaan hasil catatan
Observasi independen Setiap baris data diperlakukan sebagai satu unit observasi yang berbeda. Tidak ada struktur pengulangan responden yang digunakan dalam model-ready. Apabila terdapat clustering survei atau panel, koreksi standard error dapat dipertimbangkan.

4.6.3 Asumsi 3 — Tidak Ada Multikolinearitas Berat (VIF)

Hipotesis/Kriteria:
\(H_0\): Tidak terdapat multikolinearitas berat antarprediktor.
\(H_1\): Terdapat multikolinearitas berat antarprediktor.
Kriteria praktis: VIF < 5 aman; VIF 5–10 perlu perhatian; VIF > 10 menunjukkan multikolinearitas berat.

multinom_vif_table <- vif_table(multinom_train, multinom_selection$best$vars)
safe_table(multinom_vif_table, "Uji multikolinearitas model terbaik multinomial menggunakan VIF", digits=3, full_width=TRUE)
Tabel 34. Uji multikolinearitas model terbaik multinomial menggunakan VIF
variabel VIF keterangan penjelasan_hasil kesimpulan
employment_label_student 1.004 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
education_code 1.003 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
employment_label_other 1.001 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.

4.6.4 Asumsi 4 — Tidak Ada Perfect Separation

Hipotesis/Kriteria:
\(H_0\): Tidak terdapat perfect separation atau prediksi yang terlalu ekstrem.
\(H_1\): Terdapat indikasi perfect separation.
Kriteria praktis: proporsi probabilitas maksimum > 0,99 yang sangat tinggi dapat menjadi sinyal separasi atau prediksi ekstrem.

multinom_prob_train <- predict(multinom_best_fit, newdata=multinom_train, type="probs")
if(is.null(dim(multinom_prob_train))){multinom_prob_train <- cbind(multinom_prob_train, 1-multinom_prob_train)}
multinom_separation_table <- tibble(
  diagnostik=c("Proporsi probabilitas maksimum > 0,99", "Probabilitas maksimum terbesar", "Probabilitas minimum terkecil"),
  nilai=c(mean(apply(multinom_prob_train, 1, max) > .99), max(multinom_prob_train), min(multinom_prob_train)),
  keterangan=c(
    "Nilai tinggi dapat mengindikasikan prediksi ekstrem.",
    "Jika mendekati 1 untuk banyak observasi, perlu waspada separasi.",
    "Jika mendekati 0 untuk banyak observasi, perlu waspada separasi."
  )
)
safe_table(multinom_separation_table, "Diagnostik perfect separation model terbaik multinomial", digits=4, full_width=TRUE)
Tabel 35. Diagnostik perfect separation model terbaik multinomial
diagnostik nilai keterangan
Proporsi probabilitas maksimum > 0,99 0.0000 Nilai tinggi dapat mengindikasikan prediksi ekstrem.
Probabilitas maksimum terbesar 0.6933 Jika mendekati 1 untuk banyak observasi, perlu waspada separasi.
Probabilitas minimum terkecil 0.0173 Jika mendekati 0 untuk banyak observasi, perlu waspada separasi.

4.6.5 Asumsi 5 — Linearitas pada Skala Logit (Box-Tidwell)

Hipotesis:
\(H_0\): Hubungan prediktor numerik dengan skala logit bersifat linear.
\(H_1\): Hubungan prediktor numerik dengan skala logit tidak linear.
Karena model multinomial memiliki beberapa kategori, pemeriksaan dilakukan secara one-vs-rest.

multinom_bt_table <- purrr::map_dfr(levels(multinom_train$choice_code), function(k){
  tmp <- multinom_train |> mutate(.y_bin=as.integer(choice_code == k))
  box_tidwell_table(tmp, response=".y_bin", predictors=multinom_selection$best$vars, family=binomial(link="logit")) |>
    mutate(kategori_one_vs_rest=k)
}) |>
  mutate(p_value=p_value_tampil) |>
  select(kategori_one_vs_rest, everything(), -p_value_tampil)
safe_table(multinom_bt_table, "Diagnostik Box-Tidwell one-vs-rest untuk model multinomial", digits=4, full_width=TRUE)
Tabel 36. Diagnostik Box-Tidwell one-vs-rest untuk model multinomial
kategori_one_vs_rest variabel estimate p_value keterangan penjelasan_hasil kesimpulan
protect_environment education_code -0.0099 0.752 Tidak ada bukti kuat non-linearitas logit H0 gagal ditolak; hubungan prediktor dengan logit relatif linear. Model layak digunakan karena linearitas logit relatif terpenuhi.
protect_environment employment_label_other NA NA NA NA NA
protect_environment employment_label_student NA NA NA NA NA
economic_growth_jobs education_code 0.0054 0.864 Tidak ada bukti kuat non-linearitas logit H0 gagal ditolak; hubungan prediktor dengan logit relatif linear. Model layak digunakan karena linearitas logit relatif terpenuhi.
economic_growth_jobs employment_label_other NA NA NA NA NA
economic_growth_jobs employment_label_student NA NA NA NA NA
other_answer education_code -0.0289 0.779 Tidak ada bukti kuat non-linearitas logit H0 gagal ditolak; hubungan prediktor dengan logit relatif linear. Model layak digunakan karena linearitas logit relatif terpenuhi.
other_answer employment_label_other NA NA NA NA NA
other_answer employment_label_student NA NA NA NA NA

4.6.6 Asumsi 6 — Independence of Irrelevant Alternatives (IIA)

Hipotesis/Kriteria:
\(H_0\): Perbandingan odds antara dua kategori tidak dipengaruhi secara tidak wajar oleh keberadaan kategori lain.
\(H_1\): Perbandingan odds antara dua kategori dipengaruhi oleh keberadaan kategori lain.
Pada laporan ini, IIA dievaluasi secara substantif karena uji formal memerlukan struktur data dan paket khusus.

multinom_iia_table <- tibble(
  asumsi="Independence of Irrelevant Alternatives (IIA)",
  pemeriksaan="Evaluasi substantif berdasarkan struktur pilihan kategori.",
  hasil="Kategori respons diperlakukan sebagai alternatif nominal yang saling eksklusif.",
  catatan="Uji formal IIA seperti Hausman-McFadden memerlukan struktur data khusus; pada laporan ini IIA dibahas sebagai asumsi substantif model multinomial."
)
safe_table(multinom_iia_table, "Pemeriksaan asumsi IIA pada model multinomial", full_width=TRUE)
Tabel 37. Pemeriksaan asumsi IIA pada model multinomial
asumsi pemeriksaan hasil catatan
Independence of Irrelevant Alternatives (IIA) Evaluasi substantif berdasarkan struktur pilihan kategori. Kategori respons diperlakukan sebagai alternatif nominal yang saling eksklusif. Uji formal IIA seperti Hausman-McFadden memerlukan struktur data khusus; pada laporan ini IIA dibahas sebagai asumsi substantif model multinomial.

4.6.7 Ringkasan Uji Asumsi

multinom_assumption_summary <- tibble(
  uji=c("Skala respons nominal", "Observasi independen", "VIF", "Perfect separation", "Box-Tidwell", "IIA"),
  angka_hasil=c(
    paste0("Jumlah kategori = ", n_distinct(multinom_train$choice_code), "; kelas minimum = ", min(table(multinom_train$choice_code))),
    paste0("Jumlah observasi training = ", nrow(multinom_train)),
    ifelse(all(is.na(multinom_vif_table$VIF)), "VIF tidak dihitung", paste0("VIF maksimum = ", round(max(multinom_vif_table$VIF, na.rm=TRUE), 3))),
    paste0("Proporsi max probability > 0,99 = ", round(multinom_separation_table$nilai[1], 4), "; max probability = ", round(multinom_separation_table$nilai[2], 4)),
    paste0("Jumlah indikasi non-linearitas = ", sum(multinom_bt_table$keterangan == "Ada indikasi non-linearitas logit", na.rm=TRUE)),
    paste0("Kategori alternatif = ", paste(levels(multinom_train$choice_code), collapse=", "))
  ),
  interpretasi_hasil=c(
    "Respons memiliki kategori nominal yang saling eksklusif sehingga sesuai untuk multinomial.",
    "Setiap baris diperlakukan sebagai satu unit observasi pada data model-ready.",
    ifelse(all(is.na(multinom_vif_table$VIF)) || max(multinom_vif_table$VIF, na.rm=TRUE) < 5, "Tidak ada indikasi multikolinearitas berat.", "Terdapat indikasi multikolinearitas yang perlu diperhatikan."),
    ifelse(multinom_separation_table$nilai[1] > .25, "Ada cukup banyak probabilitas prediksi ekstrem sehingga perlu waspada separasi.", "Tidak ada indikasi kuat perfect separation berdasarkan probabilitas ekstrem."),
    ifelse(sum(multinom_bt_table$keterangan == "Ada indikasi non-linearitas logit", na.rm=TRUE) > 0, "Ada prediktor yang berpotensi tidak linear pada skala logit one-vs-rest.", "Tidak ada bukti kuat pelanggaran linearitas logit one-vs-rest."),
    "IIA dievaluasi secara substantif karena uji formal memerlukan struktur/paket khusus."
  ),
  kesimpulan=c(
    "Model multinomial layak digunakan karena respons berbentuk nominal lebih dari dua kategori.",
    "Model dapat digunakan dengan asumsi setiap observasi merupakan unit yang berbeda pada data model-ready.",
    ifelse(all(is.na(multinom_vif_table$VIF)) || max(multinom_vif_table$VIF, na.rm=TRUE) < 5, "Model layak digunakan karena tidak ada indikasi multikolinearitas berat.", "Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena ada indikasi multikolinearitas."),
    ifelse(multinom_separation_table$nilai[1] > .25, "Model dapat digunakan dengan catatan terdapat probabilitas prediksi ekstrem.", "Model layak digunakan karena tidak ada indikasi kuat perfect separation."),
    ifelse(sum(multinom_bt_table$keterangan == "Ada indikasi non-linearitas logit", na.rm=TRUE) > 0, "Model masih dapat digunakan, tetapi asumsi linearitas logit perlu diberi catatan.", "Model layak digunakan karena linearitas logit one-vs-rest relatif terpenuhi."),
    "Model dapat digunakan dengan catatan IIA dibaca sebagai asumsi substantif pada pilihan kategori."
  )
)
safe_table(multinom_assumption_summary, "Ringkasan uji asumsi model terbaik multinomial", digits=4, full_width=TRUE)
Tabel 38. Ringkasan uji asumsi model terbaik multinomial
uji angka_hasil interpretasi_hasil kesimpulan
Skala respons nominal Jumlah kategori = 3; kelas minimum = 209 Respons memiliki kategori nominal yang saling eksklusif sehingga sesuai untuk multinomial. Model multinomial layak digunakan karena respons berbentuk nominal lebih dari dua kategori.
Observasi independen Jumlah observasi training = 6845 Setiap baris diperlakukan sebagai satu unit observasi pada data model-ready. Model dapat digunakan dengan asumsi setiap observasi merupakan unit yang berbeda pada data model-ready.
VIF VIF maksimum = 1.004 Tidak ada indikasi multikolinearitas berat. Model layak digunakan karena tidak ada indikasi multikolinearitas berat.
Perfect separation Proporsi max probability > 0,99 = 0; max probability = 0.6933 Tidak ada indikasi kuat perfect separation berdasarkan probabilitas ekstrem. Model layak digunakan karena tidak ada indikasi kuat perfect separation.
Box-Tidwell Jumlah indikasi non-linearitas = 0 Tidak ada bukti kuat pelanggaran linearitas logit one-vs-rest. Model layak digunakan karena linearitas logit one-vs-rest relatif terpenuhi.
IIA Kategori alternatif = protect_environment, economic_growth_jobs, other_answer IIA dievaluasi secara substantif karena uji formal memerlukan struktur/paket khusus. Model dapat digunakan dengan catatan IIA dibaca sebagai asumsi substantif pada pilihan kategori.

4.7 Prediksi dengan model terbaik

multinom_pred <- predict(multinom_best_fit, newdata=multinom_test, type="class")
multinom_prob <- predict(multinom_best_fit, newdata=multinom_test, type="probs")
multinom_metrics <- multi_metrics(multinom_test$choice_code, multinom_pred)
multinom_class_summary <- multinom_metrics$class_metrics |> filter(!is.na(f1_score))
multinom_best_class <- if(nrow(multinom_class_summary)>0) multinom_class_summary$kelas[which.max(multinom_class_summary$f1_score)] else "-"
multinom_worst_class <- if(nrow(multinom_class_summary)>0) multinom_class_summary$kelas[which.min(multinom_class_summary$f1_score)] else "-"
safe_table(cm_labeled(multinom_metrics$confusion_matrix), "Confusion matrix model terbaik multinomial dengan keterangan aktual dan prediksi")
Tabel 39. Confusion matrix model terbaik multinomial dengan keterangan aktual dan prediksi
Aktual  Prediksi Prediksi: protect_environment Prediksi: economic_growth_jobs Prediksi: other_answer
Aktual: protect_environment 979 0 0
Aktual: economic_growth_jobs 681 0 0
Aktual: other_answer 53 0 0
safe_table(multinom_metrics$accuracy_table, "Metrik agregat testing model terbaik multinomial", digits=3)
Tabel 40. Metrik agregat testing model terbaik multinomial
accuracy error_rate
0.572 0.428
safe_table(multinom_metrics$class_metrics, "Metrik evaluasi per kelas model terbaik multinomial", digits=3)
Tabel 41. Metrik evaluasi per kelas model terbaik multinomial
kelas sensitivity specificity precision f1_score
protect_environment 1 0 0.572 0.727
economic_growth_jobs 0 1 NA NA
other_answer 0 1 NA NA
multinom_prob_df <- as.data.frame(multinom_prob) |> mutate(observasi=row_number()) |> pivot_longer(-observasi, names_to="kategori", values_to="probabilitas")
multinom_prob_df |> group_by(kategori) |> summarise(rata_rata_probabilitas=mean(probabilitas), .groups="drop") |> ggplot(aes(x=reorder(kategori,rata_rata_probabilitas), y=rata_rata_probabilitas, fill=kategori))+geom_col(width=.70)+geom_text(aes(label=round(rata_rata_probabilitas,3)), hjust=-.12, fontface="bold")+coord_flip()+scale_fill_manual(values=report_palette)+scale_y_continuous(labels=percent_format(accuracy=1))+labs(title="Rata-rata Probabilitas Prediksi Model Terbaik Multinomial", subtitle="Diringkas dari data testing.", x="Kategori", y="Rata-rata probabilitas prediksi", fill="Kategori")+theme_report()
Gambar 4. Rata-rata probabilitas prediksi model terbaik multinomial.

Gambar 4. Rata-rata probabilitas prediksi model terbaik multinomial.

Interpretasi hasil prediksi: Prediksi multinomial dilakukan dengan memilih kategori dengan probabilitas terbesar. Pada data testing, accuracy model terbaik adalah 0.572 dan error rate sebesar 0.428. Berdasarkan metrik per kelas, kelas dengan F1-score terbaik adalah protect_environment, sedangkan kelas dengan F1-score terendah adalah protect_environment. Artinya, kemampuan prediksi model tidak merata pada semua kategori; kategori dengan F1-score lebih tinggi lebih mudah dikenali oleh model, sedangkan kategori dengan F1-score lebih rendah perlu dibaca hati-hati terutama bila jumlah observasinya lebih kecil atau polanya tumpang tindih dengan kategori lain.

5 Bagian III — Regresi Logistik Ordinal

5.1 Konsep model

Regresi logistik ordinal digunakan ketika respons memiliki urutan kategori. Pada model cumulative logit:

\[ \log\left(\frac{P(Y_i\le j)}{P(Y_i>j)}\right)=\theta_j-\left(\beta_1X_{1i}+\cdots+\beta_kX_{ki}\right) \]

Model ini mempertahankan informasi urutan tanpa menganggap jarak antar kategori harus sama.

5.2 Import, struktur data, dan distribusi respons

ordinal_data <- readxl::read_excel(resolve_path("ordinal_model_ready_sample.xlsx")) |>
  clean_model_data() |>
  mutate(gov_confidence_ord=ordered(gov_confidence_ord, levels=sort(unique(gov_confidence_ord)), labels=c("none_at_all","not_very_much","quite_a_lot","a_great_deal")[seq_along(sort(unique(gov_confidence_ord)))]))
ordinal_dict <- make_dictionary(ordinal_data)
ordinal_summary <- ordinal_data |> count(gov_confidence_ord, name="n") |> mutate(proporsi=n/sum(n))
safe_table(ordinal_dict, "Kamus data ordinal setelah seleksi variabel analisis")
Tabel 42. Kamus data ordinal setelah seleksi variabel analisis
variabel tipe missing unique_value
gov_confidence_ord ordered/factor 0 4
age numeric 0 82
education_code numeric 0 9
income_scale numeric 0 10
gender_label_male numeric 0 2
employment_label_housewife_or_not_working numeric 0 2
employment_label_other numeric 0 2
employment_label_part_time_employee numeric 0 2
employment_label_retired numeric 0 2
employment_label_self_employed numeric 0 2
employment_label_student numeric 0 2
employment_label_unemployed numeric 0 2
safe_table(ordinal_summary, "Distribusi respons ordinal gov_confidence_ord", digits=3)
Tabel 43. Distribusi respons ordinal gov_confidence_ord
gov_confidence_ord n proporsi
none_at_all 1590 0.225
not_very_much 2229 0.315
quite_a_lot 2218 0.314
a_great_deal 1030 0.146
ordinal_summary |> ggplot(aes(x=gov_confidence_ord, y=n, fill=gov_confidence_ord))+geom_col(width=.70)+geom_text(aes(label=n), vjust=-.45, fontface="bold")+scale_fill_manual(values=report_palette)+labs(title="Distribusi Government Confidence", subtitle="Kategori respons memiliki urutan alami.", x="Tingkat kepercayaan terhadap pemerintah", y="Jumlah observasi", fill="Kategori")+theme_report()
Gambar 5. Distribusi kategori respons ordinal.

Gambar 5. Distribusi kategori respons ordinal.

Variabel gov_confidence_ord memiliki urutan substantif dari kepercayaan rendah ke tinggi. Karena jarak antar kategori tidak harus sama, regresi logistik ordinal lebih tepat dibanding model linear biasa.

5.3 Statistik Deskriptif dan Eksplorasi Data

ordinal_descriptive <- numeric_descriptive(ordinal_data, response="gov_confidence_ord")
ordinal_x_preview <- model_predictors(ordinal_data, response="gov_confidence_ord")
ordinal_group_summary <- ordinal_data |>
  select(gov_confidence_ord, all_of(ordinal_x_preview)) |>
  group_by(gov_confidence_ord) |>
  summarise(across(everything(), ~mean(.x, na.rm=TRUE)), .groups="drop") |>
  pivot_longer(-gov_confidence_ord, names_to="variabel", values_to="mean") |>
  pivot_wider(names_from=gov_confidence_ord, values_from=mean)

safe_table(ordinal_descriptive, "Statistik deskriptif prediktor numerik pada data ordinal", digits=3, full_width=TRUE)
Tabel 44. Statistik deskriptif prediktor numerik pada data ordinal
variabel n missing mean sd min q1 median q3 max
age 7067 0 43.250 16.578 16 29 41 56 99
education_code 7067 0 3.564 2.030 0 2 3 6 8
income_scale 7067 0 4.916 2.097 1 3 5 6 10
gender_label_male 7067 0 0.473 0.499 0 0 0 1 1
employment_label_housewife_or_not_working 7067 0 0.134 0.340 0 0 0 0 1
employment_label_other 7067 0 0.012 0.107 0 0 0 0 1
employment_label_part_time_employee 7067 0 0.087 0.283 0 0 0 0 1
employment_label_retired 7067 0 0.130 0.337 0 0 0 0 1
employment_label_self_employed 7067 0 0.141 0.348 0 0 0 0 1
employment_label_student 7067 0 0.054 0.226 0 0 0 0 1
employment_label_unemployed 7067 0 0.075 0.264 0 0 0 0 1
safe_table(ordinal_group_summary, "Rata-rata prediktor menurut tingkat gov_confidence_ord", digits=3, full_width=TRUE)
Tabel 45. Rata-rata prediktor menurut tingkat gov_confidence_ord
variabel none_at_all not_very_much quite_a_lot a_great_deal
age 42.257 43.435 43.874 43.040
education_code 3.622 3.705 3.611 3.067
income_scale 4.777 4.918 5.110 4.710
gender_label_male 0.489 0.463 0.465 0.486
employment_label_housewife_or_not_working 0.133 0.118 0.133 0.171
employment_label_other 0.012 0.014 0.007 0.015
employment_label_part_time_employee 0.087 0.091 0.086 0.083
employment_label_retired 0.123 0.141 0.132 0.117
employment_label_self_employed 0.136 0.142 0.135 0.157
employment_label_student 0.059 0.053 0.048 0.061
employment_label_unemployed 0.096 0.069 0.062 0.083
ordinal_plot_vars <- ordinal_x_preview[seq_len(min(4, length(ordinal_x_preview)))]
ordinal_data |>
  select(gov_confidence_ord, all_of(ordinal_plot_vars)) |>
  pivot_longer(-gov_confidence_ord, names_to="variabel", values_to="nilai") |>
  ggplot(aes(x=gov_confidence_ord, y=nilai, fill=gov_confidence_ord))+
  geom_boxplot(alpha=.75, outlier.alpha=.25)+
  facet_wrap(~variabel, scales="free_y")+
  scale_fill_manual(values=report_palette)+
  labs(title="Eksplorasi Prediktor terhadap Government Confidence", x="Tingkat respons ordinal", y="Nilai prediktor", fill="Kategori")+
  theme_report()
Gambar 6. Eksplorasi prediktor numerik utama menurut tingkat gov_confidence_ord.

Gambar 6. Eksplorasi prediktor numerik utama menurut tingkat gov_confidence_ord.

5.4 Pembagian data training dan testing

ordinal_split <- split_train_test(ordinal_data, strata="gov_confidence_ord", prop=.80)
ordinal_train <- ordinal_split$train; ordinal_test <- ordinal_split$test
ordinal_split_table <- bind_rows(ordinal_train |> count(gov_confidence_ord, name="n") |> mutate(subset="Training"), ordinal_test |> count(gov_confidence_ord, name="n") |> mutate(subset="Testing")) |> group_by(subset) |> mutate(proporsi=n/sum(n)) |> ungroup() |> select(subset, gov_confidence_ord, n, proporsi)
safe_table(ordinal_split_table, "Komposisi respons ordinal pada data training dan testing", digits=3)
Tabel 46. Komposisi respons ordinal pada data training dan testing
subset gov_confidence_ord n proporsi
Training none_at_all 1272 0.225
Training not_very_much 1783 0.315
Training quite_a_lot 1774 0.314
Training a_great_deal 824 0.146
Testing none_at_all 318 0.225
Testing not_very_much 446 0.315
Testing quite_a_lot 444 0.314
Testing a_great_deal 206 0.146

5.5 Kandidat model dan pemilihan model terbaik

ordinal_x <- model_predictors(ordinal_train, response="gov_confidence_ord")
ordinal_selection <- select_best_polr(ordinal_train, response="gov_confidence_ord", predictors=ordinal_x)
ordinal_best_fit <- ordinal_selection$best$fit
ordinal_best_null <- MASS::polr(gov_confidence_ord ~ 1, data=ordinal_train, Hess=TRUE, method="logistic")
ordinal_best_wald <- ordinal_selection$best$wald
ordinal_best_g2 <- ordinal_selection$best$g2
ordinal_selected_vars <- ordinal_best_wald |> filter(p_value < 0.05) |> pull(variabel)

ordinal_model_selection_table <- ordinal_selection$summary |>
  mutate(p_value_G2=p_value_G2_tampil) |>
  select(model_ke, formula, jumlah_prediktor, jumlah_variabel_signifikan, AIC, G2, p_value_G2)

ordinal_fit_table <- tibble(keterangan=c("Jumlah observasi training","Jumlah prediktor model terbaik","AIC model terbaik","Log-likelihood model terbaik","Log-likelihood model kosong","McFadden pseudo R-square"), nilai=c(nrow(ordinal_train), length(ordinal_selection$best$vars), AIC(ordinal_best_fit), as.numeric(logLik(ordinal_best_fit)), as.numeric(logLik(ordinal_best_null)), mcfadden_r2(ordinal_best_fit,ordinal_best_null)))

ordinal_best_wald_show <- ordinal_best_wald |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
ordinal_best_g2_show <- ordinal_best_g2 |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)

safe_table(ordinal_model_selection_table, "Perbandingan kandidat model ordinal berdasarkan jumlah variabel signifikan dan AIC", digits=4, full_width=TRUE)
Tabel 47. Perbandingan kandidat model ordinal berdasarkan jumlah variabel signifikan dan AIC
model_ke formula jumlah_prediktor jumlah_variabel_signifikan AIC G2 p_value_G2
9 gov_confidence_ord ~ education_code + income_scale + employment_label_unemployed 3 3 15160.10 46.9131 < 0.001
8 gov_confidence_ord ~ education_code + income_scale + employment_label_other + employment_label_unemployed 4 3 15161.02 47.9894 < 0.001
7 gov_confidence_ord ~ education_code + income_scale + employment_label_other + employment_label_retired + employment_label_unemployed 5 3 15161.87 49.1385 < 0.001
6 gov_confidence_ord ~ age + education_code + income_scale + employment_label_other + employment_label_retired + employment_label_unemployed 6 3 15162.71 50.2993 < 0.001
5 gov_confidence_ord ~ age + education_code + income_scale + employment_label_housewife_or_not_working + employment_label_other + employment_label_retired + employment_label_unemployed 7 3 15164.03 50.9830 < 0.001
4 gov_confidence_ord ~ age + education_code + income_scale + employment_label_housewife_or_not_working + employment_label_other + employment_label_part_time_employee + employment_label_retired + employment_label_unemployed 8 3 15165.67 51.3376 < 0.001
3 gov_confidence_ord ~ age + education_code + income_scale + employment_label_housewife_or_not_working + employment_label_other + employment_label_part_time_employee + employment_label_retired + employment_label_student + employment_label_unemployed 9 3 15167.46 51.5538 < 0.001
2 gov_confidence_ord ~ age + education_code + income_scale + gender_label_male + employment_label_housewife_or_not_working + employment_label_other + employment_label_part_time_employee + employment_label_retired + employment_label_student + employment_label_unemployed 10 3 15169.36 51.6491 < 0.001
1 gov_confidence_ord ~ age + education_code + income_scale + gender_label_male + employment_label_housewife_or_not_working + employment_label_other + employment_label_part_time_employee + employment_label_retired + employment_label_self_employed + employment_label_student + employment_label_unemployed 11 3 15171.34 51.6678 < 0.001
safe_table(ordinal_fit_table, "Ringkasan kecocokan model terbaik ordinal", digits=4)
Tabel 48. Ringkasan kecocokan model terbaik ordinal
keterangan nilai
Jumlah observasi training 5653.0000
Jumlah prediktor model terbaik 3.0000
AIC model terbaik 15160.0989
Log-likelihood model terbaik -7574.0494
Log-likelihood model kosong -7597.5060
McFadden pseudo R-square 0.0031
safe_table(ordinal_best_g2_show, "Uji simultan G² model terbaik ordinal", digits=4)
Tabel 49. Uji simultan G² model terbaik ordinal
uji G2 df p_value keputusan penjelasan_hasil kesimpulan
Uji likelihood ratio 46.9131 3 < 0.001 Tolak H0 Model dengan prediktor memberikan peningkatan kecocokan dibanding model kosong. Model layak digunakan karena prediktor secara simultan lebih baik daripada model kosong.
safe_table(ordinal_best_wald_show |> slice_head(n=30), "Uji Wald parsial dan odds ratio model terbaik ordinal", digits=4, full_width=TRUE)
Tabel 50. Uji Wald parsial dan odds ratio model terbaik ordinal
variabel estimate std_error z_value p_value odds_ratio ci_95
education_code -0.0771 0.0124 -6.2100 < 0.001 0.9258 0.904 - 0.949
income_scale 0.0347 0.0122 2.8445 0.004 1.0353 0.904 - 0.949
employment_label_unemployed -0.2576 0.0939 -2.7432 0.006 0.7729 0.904 - 0.949
ordinal_effect_interpretation <- make_effect_interpretation(
  ordinal_best_wald,
  effect_col="odds_ratio",
  effect_label="OR",
  response_name="gov_confidence_ord"
)
safe_table(ordinal_effect_interpretation, "Interpretasi model terbaik ordinal berdasarkan estimate dan odds ratio", digits=3, full_width=TRUE)
Tabel 51. Interpretasi model terbaik ordinal berdasarkan estimate dan odds ratio
variabel estimate p_value OR ci_95 interpretasi_hasil
education_code -0.077 < 0.001 0.926 0.904 - 0.949 Dengan variabel lain konstan, setiap kenaikan satu unit pada education_code membuat odds untuk berada pada tingkat gov_confidence_ord yang lebih tinggi menurun sekitar 7.42% (estimate = -0.0771; OR = 0.926; CI 95% = 0.904 - 0.949; p-value = < 0.001). Artinya, education_code mendukung perbedaan tingkat respons ordinal.
income_scale 0.035 0.004 1.035 0.904 - 0.949 Dengan variabel lain konstan, setiap kenaikan satu unit pada income_scale membuat odds untuk berada pada tingkat gov_confidence_ord yang lebih tinggi meningkat sekitar 3.53% (estimate = 0.0347; OR = 1.035; CI 95% = 0.904 - 0.949; p-value = 0.004). Artinya, income_scale mendukung perbedaan tingkat respons ordinal.
employment_label_unemployed -0.258 0.006 0.773 0.904 - 0.949 Dengan variabel lain konstan, setiap kenaikan satu unit pada employment_label_unemployed membuat odds untuk berada pada tingkat gov_confidence_ord yang lebih tinggi menurun sekitar 22.71% (estimate = -0.2576; OR = 0.773; CI 95% = 0.904 - 0.949; p-value = 0.006). Artinya, employment_label_unemployed mendukung perbedaan tingkat respons ordinal.

Model terbaik ordinal adalah kandidat ke-9, dengan 3 variabel signifikan dan AIC sebesar 1.5160099^{4}. Uji \(G^2\) model terbaik memiliki p-value < 0.001, sehingga model terpilih memberikan peningkatan informasi dibanding model kosong. Variabel X yang mendukung variasi tingkat gov_confidence_ord adalah: education_code, income_scale, employment_label_unemployed. Dengan demikian, prediksi ordinal berikut dilakukan menggunakan model terbaik tersebut.

5.6 Uji Asumsi Model Terbaik

5.6.1 Asumsi 1 — Asumsi Proportional Odds

Hipotesis/Kriteria:
\(H_0\): Efek prediktor relatif sama pada setiap batas kumulatif kategori respons.
\(H_1\): Efek prediktor berbeda pada beberapa batas kumulatif kategori respons.
Kriteria praktis: rentang koefisien logit kumulatif yang kecil menunjukkan efek prediktor relatif stabil; rentang yang besar mengindikasikan potensi pelanggaran proportional odds.

ordinal_parallel <- ordinal_parallel_diagnostic(
  data=ordinal_train,
  response="gov_confidence_ord",
  predictors=ordinal_selection$best$vars
)

safe_table(ordinal_parallel$ringkas, "Ringkasan diagnostik proportional odds berdasarkan rentang koefisien logit kumulatif", digits=4, full_width=TRUE)
Tabel 52. Ringkasan diagnostik proportional odds berdasarkan rentang koefisien logit kumulatif
variabel jumlah_batas min_estimate max_estimate rentang_estimate
employment_label_unemployed 3 -0.0342 0.4038 0.4380
education_code 3 0.0386 0.1609 0.1224
income_scale 3 -0.0535 0.0273 0.0808
safe_table(ordinal_parallel$detail |> slice_head(n=30) |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil), "Detail uji Wald pada logit kumulatif sebagai diagnostik proportional odds", digits=4, full_width=TRUE)
Tabel 53. Detail uji Wald pada logit kumulatif sebagai diagnostik proportional odds
batas_kumulatif variabel estimate z_value p_value
Y <= none_at_all education_code 0.0386 2.3242 0.020
Y <= none_at_all income_scale -0.0535 -3.2975 < 0.001
Y <= none_at_all employment_label_unemployed 0.4038 3.6241 < 0.001
Y <= not_very_much education_code 0.0736 5.2843 < 0.001
Y <= not_very_much income_scale -0.0463 -3.4192 < 0.001
Y <= not_very_much employment_label_unemployed 0.2259 2.1941 0.028
Y <= quite_a_lot education_code 0.1609 7.8662 < 0.001
Y <= quite_a_lot income_scale 0.0273 1.4407 0.150
Y <= quite_a_lot employment_label_unemployed -0.0342 -0.2460 0.806

5.6.2 Asumsi 2 — Tidak Ada Multikolinearitas Berat

Hipotesis/Kriteria:
\(H_0\): Tidak terdapat multikolinearitas berat antarprediktor.
\(H_1\): Terdapat multikolinearitas berat antarprediktor.
Kriteria praktis: VIF < 5 aman; VIF 5–10 perlu perhatian; VIF > 10 menunjukkan multikolinearitas berat.

ordinal_vif_table <- vif_table(ordinal_train, ordinal_selection$best$vars)
ordinal_corr_best_table <- selected_correlation_check(ordinal_train, ordinal_selection$best$vars)

safe_table(ordinal_vif_table, "Uji multikolinearitas model terbaik ordinal menggunakan VIF", digits=3, full_width=TRUE)
Tabel 54. Uji multikolinearitas model terbaik ordinal menggunakan VIF
variabel VIF keterangan penjelasan_hasil kesimpulan
income_scale 1.108 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
education_code 1.100 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
employment_label_unemployed 1.012 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
safe_table(ordinal_corr_best_table, "Pemeriksaan korelasi tinggi antarprediktor model terbaik ordinal", digits=3, full_width=TRUE)
Tabel 55. Pemeriksaan korelasi tinggi antarprediktor model terbaik ordinal
variabel_1 variabel_2 korelasi abs_korelasi keterangan penjelasan_hasil kesimpulan
NA NA Tidak ada pasangan prediktor dengan |r| >= 0,80. Tidak ada indikasi korelasi tinggi antarprediktor. Tidak diperlukan penanganan khusus.

5.6.3 Ringkasan Uji Asumsi

ordinal_assumption_summary <- tibble(
  uji=c("Proportional odds", "VIF", "Korelasi tinggi antarprediktor"),
  angka_hasil=c(
    ifelse(nrow(ordinal_parallel$ringkas)==0, "Tidak dapat dihitung", paste0("Rentang koefisien maksimum = ", round(max(abs(ordinal_parallel$ringkas$rentang_estimate), na.rm=TRUE), 4))),
    ifelse(all(is.na(ordinal_vif_table$VIF)), "VIF tidak dihitung", paste0("VIF maksimum = ", round(max(ordinal_vif_table$VIF, na.rm=TRUE), 3))),
    paste0("Jumlah pasangan |r| >= 0,80 = ", sum(!is.na(ordinal_corr_best_table$abs_korelasi)))
  ),
  interpretasi_hasil=c(
    ifelse(nrow(ordinal_parallel$ringkas)==0, "Diagnostik proportional odds tidak dapat dihitung.", "Semakin kecil rentang koefisien, semakin stabil efek prediktor pada batas kumulatif kategori."),
    ifelse(all(is.na(ordinal_vif_table$VIF)) || max(ordinal_vif_table$VIF, na.rm=TRUE) < 5, "Tidak ada indikasi multikolinearitas berat.", "Terdapat indikasi multikolinearitas yang perlu diperhatikan."),
    ifelse(sum(!is.na(ordinal_corr_best_table$abs_korelasi)) == 0, "Tidak ada pasangan prediktor dengan korelasi tinggi.", "Ada pasangan prediktor dengan korelasi tinggi sehingga koefisien perlu ditafsirkan hati-hati.")
  ),
  kesimpulan=c(
    ifelse(nrow(ordinal_parallel$ringkas)==0, "Model ordinal digunakan dengan catatan diagnostik proportional odds tidak tersedia.", "Model ordinal dapat digunakan karena stabilitas koefisien kumulatif dapat dievaluasi melalui rentang koefisien."),
    ifelse(all(is.na(ordinal_vif_table$VIF)) || max(ordinal_vif_table$VIF, na.rm=TRUE) < 5, "Model layak digunakan karena tidak ada indikasi multikolinearitas berat.", "Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena ada indikasi multikolinearitas."),
    ifelse(sum(!is.na(ordinal_corr_best_table$abs_korelasi)) == 0, "Model layak digunakan karena tidak ada korelasi tinggi antarprediktor model terbaik.", "Model dapat digunakan dengan catatan ada korelasi tinggi antarprediktor.")
  )
)
safe_table(ordinal_assumption_summary, "Ringkasan uji asumsi model terbaik ordinal", digits=4, full_width=TRUE)
Tabel 56. Ringkasan uji asumsi model terbaik ordinal
uji angka_hasil interpretasi_hasil kesimpulan
Proportional odds Rentang koefisien maksimum = 0.438 Semakin kecil rentang koefisien, semakin stabil efek prediktor pada batas kumulatif kategori. Model ordinal dapat digunakan karena stabilitas koefisien kumulatif dapat dievaluasi melalui rentang koefisien.
VIF VIF maksimum = 1.108 Tidak ada indikasi multikolinearitas berat. Model layak digunakan karena tidak ada indikasi multikolinearitas berat.
Korelasi tinggi antarprediktor Jumlah pasangan |r| >= 0,80 = 0 Tidak ada pasangan prediktor dengan korelasi tinggi. Model layak digunakan karena tidak ada korelasi tinggi antarprediktor model terbaik.

5.7 Prediksi dengan model terbaik

ordinal_pred <- predict(ordinal_best_fit, newdata=ordinal_test, type="class")
ordinal_prob <- predict(ordinal_best_fit, newdata=ordinal_test, type="probs")
ordinal_metrics <- multi_metrics(ordinal_test$gov_confidence_ord, ordinal_pred)
ordinal_class_summary <- ordinal_metrics$class_metrics |> filter(!is.na(f1_score))
ordinal_best_class <- if(nrow(ordinal_class_summary)>0) ordinal_class_summary$kelas[which.max(ordinal_class_summary$f1_score)] else "-"
ordinal_worst_class <- if(nrow(ordinal_class_summary)>0) ordinal_class_summary$kelas[which.min(ordinal_class_summary$f1_score)] else "-"
safe_table(cm_labeled(ordinal_metrics$confusion_matrix), "Confusion matrix model terbaik ordinal dengan keterangan aktual dan prediksi")
Tabel 57. Confusion matrix model terbaik ordinal dengan keterangan aktual dan prediksi
Aktual  Prediksi Prediksi: none_at_all Prediksi: not_very_much Prediksi: quite_a_lot Prediksi: a_great_deal
Aktual: none_at_all 1 151 166 0
Aktual: not_very_much 0 229 217 0
Aktual: quite_a_lot 0 200 244 0
Aktual: a_great_deal 0 98 108 0
safe_table(ordinal_metrics$accuracy_table, "Metrik agregat testing model terbaik ordinal", digits=3)
Tabel 58. Metrik agregat testing model terbaik ordinal
accuracy error_rate
0.335 0.665
safe_table(ordinal_metrics$class_metrics, "Metrik evaluasi per kelas model terbaik ordinal", digits=3)
Tabel 59. Metrik evaluasi per kelas model terbaik ordinal
kelas sensitivity specificity precision f1_score
none_at_all 0.003 1.000 1.000 0.006
not_very_much 0.513 0.536 0.338 0.407
quite_a_lot 0.550 0.494 0.332 0.414
a_great_deal 0.000 1.000 NA NA
ordinal_prob_df <- as.data.frame(ordinal_prob) |> mutate(observasi=row_number()) |> pivot_longer(-observasi, names_to="kategori", values_to="probabilitas")
ordinal_prob_df |> group_by(kategori) |> summarise(rata_rata_probabilitas=mean(probabilitas), .groups="drop") |> ggplot(aes(x=kategori, y=rata_rata_probabilitas, fill=kategori))+geom_col(width=.70)+geom_text(aes(label=round(rata_rata_probabilitas,3)), vjust=-.45, fontface="bold")+scale_fill_manual(values=report_palette)+scale_y_continuous(labels=percent_format(accuracy=1))+labs(title="Rata-rata Probabilitas Prediksi Model Terbaik Ordinal", subtitle="Diringkas dari data testing.", x="Kategori ordinal", y="Rata-rata probabilitas prediksi", fill="Kategori")+theme_report()
Gambar 6. Rata-rata probabilitas prediksi model terbaik ordinal.

Gambar 6. Rata-rata probabilitas prediksi model terbaik ordinal.

Interpretasi hasil prediksi: Accuracy testing model terbaik ordinal adalah 0.335 dengan error rate sebesar 0.665. Berdasarkan metrik per kelas, kategori dengan F1-score terbaik adalah quite_a_lot, sedangkan kategori dengan F1-score terendah adalah none_at_all. Karena respons bersifat ordinal, hasil prediksi tidak hanya dibaca dari benar-salah secara umum, tetapi juga dari pola kesalahannya: kesalahan ke kategori yang berdekatan lebih ringan secara substantif dibanding kesalahan ke kategori yang jauh.

6 Bagian IV — Regresi Poisson Tahunan

6.1 Konsep model

Regresi Poisson digunakan untuk respons berupa hitungan non-negatif. Model menggunakan link log:

\[ \log(\mu_i)=\beta_0+\beta_1X_{1i}+\cdots+\beta_kX_{ki} \]

Ukuran efek yang digunakan adalah incidence rate ratio (IRR), yaitu \(\exp(\beta)\).

6.2 Import, struktur data, dan distribusi respons

poisson_data <- readxl::read_excel(resolve_path("poisson_model_ready.xlsx")) |>
  clean_model_data()
poisson_overdispersion_file <- readxl::read_excel(resolve_path("poisson_overdispersion_check.xlsx"))
poisson_duplicate_check <- tibble(variabel_1="lag_conflict_count", variabel_2="lag_actor_count", identik=if(all(c("lag_conflict_count","lag_actor_count") %in% names(poisson_data))) isTRUE(all.equal(poisson_data$lag_conflict_count, poisson_data$lag_actor_count)) else NA, keputusan="lag_actor_count tidak digunakan pada model akhir")
poisson_data <- poisson_data |> select(-any_of("lag_actor_count"))
poisson_dict <- make_dictionary(poisson_data)
poisson_summary <- tibble(n_observasi=nrow(poisson_data), min_case_count=min(poisson_data$case_count,na.rm=TRUE), mean_case_count=mean(poisson_data$case_count,na.rm=TRUE), variance_case_count=var(poisson_data$case_count,na.rm=TRUE), median_case_count=median(poisson_data$case_count,na.rm=TRUE), max_case_count=max(poisson_data$case_count,na.rm=TRUE), proporsi_nol=mean(poisson_data$case_count==0,na.rm=TRUE))
safe_table(poisson_dict, "Kamus data Poisson tahunan setelah seleksi variabel analisis")
Tabel 60. Kamus data Poisson tahunan setelah seleksi variabel analisis
variabel tipe missing unique_value
case_count numeric 0 12
year_centered numeric 0 36
log1p_lag_case_count numeric 0 12
log1p_lag_total_fatalities numeric 0 341
lag_conflict_count numeric 0 12
lag_share_gov_actor numeric 0 13
region_label_Americas numeric 0 2
region_label_Asia numeric 0 2
region_label_Europe numeric 0 2
region_label_Middle_East numeric 0 2
safe_table(poisson_duplicate_check, "Cek duplikasi prediktor lag pada data Poisson tahunan")
Tabel 61. Cek duplikasi prediktor lag pada data Poisson tahunan
variabel_1 variabel_2 identik keputusan
lag_conflict_count lag_actor_count TRUE lag_actor_count tidak digunakan pada model akhir
safe_table(poisson_overdispersion_file, "Ringkasan awal cek overdispersion Poisson tahunan")
Tabel 62. Ringkasan awal cek overdispersion Poisson tahunan
metric value
pearson_chi_square 3552.07569486401
df_residual 2906
dispersion_ratio 1.222324740145908
interpretation Tidak ada indikasi kuat overdispersion
safe_table(poisson_summary, "Ringkasan deskriptif respons case_count pada data Poisson tahunan", digits=3)
Tabel 63. Ringkasan deskriptif respons case_count pada data Poisson tahunan
n_observasi min_case_count mean_case_count variance_case_count median_case_count max_case_count proporsi_nol
2916 0 0.382 0.933 0 12 0.775
poisson_data |> ggplot(aes(x=case_count))+geom_histogram(binwidth=1,boundary=-.5,fill="#0891b2",color="white")+scale_x_continuous(breaks=scales::pretty_breaks())+labs(title="Distribusi Violence Case Count Tahunan", subtitle="Respons berupa data hitungan non-negatif.", x="Jumlah kejadian kekerasan", y="Frekuensi")+theme_report()
Gambar 7. Distribusi respons case_count pada data Poisson tahunan.

Gambar 7. Distribusi respons case_count pada data Poisson tahunan.

case_count memenuhi bentuk dasar respons Poisson karena berupa hitungan non-negatif. Rata-rata dan varians respons perlu diperhatikan karena model Poisson klasik mengasumsikan equidispersion, yaitu rata-rata dan varians kondisional relatif sama.

6.3 Statistik Deskriptif dan Eksplorasi Data

poisson_descriptive <- numeric_descriptive(poisson_data, response="case_count")
poisson_x_preview <- model_predictors(poisson_data, response="case_count")
poisson_response_exploration <- tibble(
  ukuran=c("Mean case_count", "Variance case_count", "Variance / Mean", "Proporsi nilai nol"),
  nilai=c(
    mean(poisson_data$case_count, na.rm=TRUE),
    var(poisson_data$case_count, na.rm=TRUE),
    var(poisson_data$case_count, na.rm=TRUE)/mean(poisson_data$case_count, na.rm=TRUE),
    mean(poisson_data$case_count == 0, na.rm=TRUE)
  )
)

safe_table(poisson_descriptive, "Statistik deskriptif prediktor numerik pada data Poisson tahunan", digits=3, full_width=TRUE)
Tabel 64. Statistik deskriptif prediktor numerik pada data Poisson tahunan
variabel n missing mean sd min q1 median q3 max
year_centered 2916 0 17.500 10.390 0 8.75 17.5 26.25 35.000
log1p_lag_case_count 2916 0 0.199 0.411 0 0.00 0.0 0.00 2.565
log1p_lag_total_fatalities 2916 0 1.081 2.129 0 0.00 0.0 0.00 9.783
lag_conflict_count 2916 0 0.368 0.945 0 0.00 0.0 0.00 12.000
lag_share_gov_actor 2916 0 0.099 0.280 0 0.00 0.0 0.00 1.000
region_label_Americas 2916 0 0.148 0.355 0 0.00 0.0 0.00 1.000
region_label_Asia 2916 0 0.185 0.389 0 0.00 0.0 0.00 1.000
region_label_Europe 2916 0 0.123 0.329 0 0.00 0.0 0.00 1.000
region_label_Middle_East 2916 0 0.123 0.329 0 0.00 0.0 0.00 1.000
safe_table(poisson_response_exploration, "Eksplorasi awal respons case_count untuk indikasi overdispersi dan zero inflation", digits=4, full_width=TRUE)
Tabel 65. Eksplorasi awal respons case_count untuk indikasi overdispersi dan zero inflation
ukuran nilai
Mean case_count 0.3824
Variance case_count 0.9333
Variance / Mean 2.4409
Proporsi nilai nol 0.7750
poisson_plot_vars <- poisson_x_preview[seq_len(min(4, length(poisson_x_preview)))]
poisson_data |>
  select(case_count, all_of(poisson_plot_vars)) |>
  pivot_longer(-case_count, names_to="variabel", values_to="nilai") |>
  ggplot(aes(x=nilai, y=case_count))+
  geom_point(alpha=.35, color="#172033")+
  geom_smooth(method="glm", method.args=list(family=poisson(link="log")), se=TRUE, color="#6d28d9", fill="#6d28d9", alpha=.18)+
  facet_wrap(~variabel, scales="free_x")+
  labs(title="Eksplorasi Hubungan Prediktor dengan Violence Case Count", x="Nilai prediktor", y="case_count")+
  theme_report()
Gambar 8. Hubungan prediktor numerik utama dengan case_count.

Gambar 8. Hubungan prediktor numerik utama dengan case_count.

6.4 Pembagian data training dan testing

poisson_split <- split_train_test(poisson_data, prop=.80)
poisson_train <- poisson_split$train; poisson_test <- poisson_split$test
poisson_split_table <- tibble(subset=c("Training","Testing"), n=c(nrow(poisson_train),nrow(poisson_test)), mean_case_count=c(mean(poisson_train$case_count),mean(poisson_test$case_count)), proporsi_nol=c(mean(poisson_train$case_count==0),mean(poisson_test$case_count==0)))
safe_table(poisson_split_table, "Komposisi training dan testing pada data Poisson tahunan", digits=3)
Tabel 66. Komposisi training dan testing pada data Poisson tahunan
subset n mean_case_count proporsi_nol
Training 2332 0.373 0.777
Testing 584 0.420 0.767

6.5 Kandidat model dan pemilihan model terbaik

poisson_x <- model_predictors(poisson_train, response="case_count")
poisson_selection <- select_best_glm(poisson_train, response="case_count", predictors=poisson_x, family=poisson(link="log"), effect_name="incidence_rate_ratio")
poisson_best_fit <- poisson_selection$best$fit
poisson_best_null <- glm(case_count ~ 1, data=poisson_train, family=poisson(link="log"))
poisson_best_wald <- poisson_selection$best$wald
poisson_best_g2 <- poisson_selection$best$g2
poisson_selected_vars <- poisson_best_wald |> filter(p_value < 0.05) |> pull(variabel)

poisson_model_selection_table <- poisson_selection$summary |>
  mutate(p_value_G2=p_value_G2_tampil) |>
  select(model_ke, formula, jumlah_prediktor, jumlah_variabel_signifikan, AIC, G2, p_value_G2)

poisson_fit_table <- tibble(keterangan=c("Jumlah observasi training","Jumlah prediktor model terbaik","Null deviance model terbaik","Residual deviance model terbaik","AIC model terbaik","Log-likelihood model terbaik","Log-likelihood model kosong","McFadden pseudo R-square"), nilai=c(nrow(poisson_train), length(poisson_selection$best$vars), poisson_best_fit$null.deviance, poisson_best_fit$deviance, AIC(poisson_best_fit), as.numeric(logLik(poisson_best_fit)), as.numeric(logLik(poisson_best_null)), mcfadden_r2(poisson_best_fit,poisson_best_null)))

poisson_best_wald_show <- poisson_best_wald |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
poisson_best_g2_show <- poisson_best_g2 |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)

safe_table(poisson_model_selection_table, "Perbandingan kandidat model Poisson berdasarkan jumlah variabel signifikan dan AIC", digits=4, full_width=TRUE)
Tabel 67. Perbandingan kandidat model Poisson berdasarkan jumlah variabel signifikan dan AIC
model_ke formula jumlah_prediktor jumlah_variabel_signifikan AIC G2 p_value_G2
4 case_count ~ log1p_lag_case_count + log1p_lag_total_fatalities + lag_conflict_count + region_label_Asia + region_label_Europe + region_label_Middle_East 6 5 2628.673 1576.612 < 0.001
5 case_count ~ log1p_lag_case_count + log1p_lag_total_fatalities + lag_conflict_count + region_label_Europe + region_label_Middle_East 5 5 2628.763 1574.522 < 0.001
3 case_count ~ year_centered + log1p_lag_case_count + log1p_lag_total_fatalities + lag_conflict_count + region_label_Asia + region_label_Europe + region_label_Middle_East 7 5 2630.443 1576.842 < 0.001
2 case_count ~ year_centered + log1p_lag_case_count + log1p_lag_total_fatalities + lag_conflict_count + lag_share_gov_actor + region_label_Asia + region_label_Europe + region_label_Middle_East 8 5 2632.297 1576.988 < 0.001
1 case_count ~ year_centered + log1p_lag_case_count + log1p_lag_total_fatalities + lag_conflict_count + lag_share_gov_actor + region_label_Americas + region_label_Asia + region_label_Europe + region_label_Middle_East 9 5 2634.290 1576.995 < 0.001
safe_table(poisson_fit_table, "Ringkasan kecocokan model terbaik Poisson tahunan", digits=4)
Tabel 68. Ringkasan kecocokan model terbaik Poisson tahunan
keterangan nilai
Jumlah observasi training 2332.0000
Jumlah prediktor model terbaik 6.0000
Null deviance model terbaik 2993.7489
Residual deviance model terbaik 1417.1366
AIC model terbaik 2628.6732
Log-likelihood model terbaik -1307.3366
Log-likelihood model kosong -2095.6427
McFadden pseudo R-square 0.3762
safe_table(poisson_best_g2_show, "Uji simultan G² model terbaik Poisson tahunan", digits=4)
Tabel 69. Uji simultan G² model terbaik Poisson tahunan
uji G2 df p_value keputusan penjelasan_hasil kesimpulan
Uji likelihood ratio 1576.612 6 < 0.001 Tolak H0 Model dengan prediktor memberikan peningkatan kecocokan dibanding model kosong. Model layak digunakan karena prediktor secara simultan lebih baik daripada model kosong.
safe_table(poisson_best_wald_show, "Uji Wald parsial dan IRR model terbaik Poisson tahunan", digits=4, full_width=TRUE)
Tabel 70. Uji Wald parsial dan IRR model terbaik Poisson tahunan
variabel estimate std_error z_value p_value incidence_rate_ratio ci_95
log1p_lag_case_count 2.0547 0.2477 8.2959 < 0.001 7.8045 4.803 - 12.682
region_label_Europe -0.9163 0.2249 -4.0742 < 0.001 0.4000 0.257 - 0.622
lag_conflict_count -0.1906 0.0484 -3.9377 < 0.001 0.8264 0.752 - 0.909
log1p_lag_total_fatalities 0.1243 0.0325 3.8245 < 0.001 1.1324 1.062 - 1.207
region_label_Middle_East -0.2955 0.1311 -2.2543 0.024 0.7442 0.576 - 0.962
region_label_Asia 0.1172 0.0805 1.4567 0.145 1.1243 0.96 - 1.316
poisson_effect_interpretation <- make_effect_interpretation(
  poisson_best_wald,
  effect_col="incidence_rate_ratio",
  effect_label="IRR",
  response_name="case_count"
)
safe_table(poisson_effect_interpretation, "Interpretasi model terbaik Poisson berdasarkan estimate dan incidence rate ratio", digits=3, full_width=TRUE)
Tabel 71. Interpretasi model terbaik Poisson berdasarkan estimate dan incidence rate ratio
variabel estimate p_value IRR ci_95 interpretasi_hasil
log1p_lag_case_count 2.055 < 0.001 7.805 4.803 - 12.682 Dengan variabel lain konstan, setiap kenaikan satu unit pada skala log(1 + X) pada log1p_lag_case_count membuat expected count case_count meningkat sekitar 680.45% (estimate = 2.0547; IRR = 7.805; CI 95% = 4.803 - 12.682; p-value = < 0.001). Artinya, log1p_lag_case_count mendukung perubahan rata-rata jumlah kejadian pada model Poisson. Karena prediktor berbentuk log(1 + X), interpretasi ini berlaku pada skala log, bukan kenaikan satu angka mentah.
region_label_Europe -0.916 < 0.001 0.400 0.257 - 0.622 Dengan variabel lain konstan, setiap kenaikan satu unit pada region_label_Europe membuat expected count case_count menurun sekitar 60% (estimate = -0.9163; IRR = 0.4; CI 95% = 0.257 - 0.622; p-value = < 0.001). Artinya, region_label_Europe mendukung perubahan rata-rata jumlah kejadian pada model Poisson.
lag_conflict_count -0.191 < 0.001 0.826 0.752 - 0.909 Dengan variabel lain konstan, setiap kenaikan satu unit pada lag_conflict_count membuat expected count case_count menurun sekitar 17.36% (estimate = -0.1906; IRR = 0.826; CI 95% = 0.752 - 0.909; p-value = < 0.001). Artinya, lag_conflict_count mendukung perubahan rata-rata jumlah kejadian pada model Poisson.
log1p_lag_total_fatalities 0.124 < 0.001 1.132 1.062 - 1.207 Dengan variabel lain konstan, setiap kenaikan satu unit pada skala log(1 + X) pada log1p_lag_total_fatalities membuat expected count case_count meningkat sekitar 13.24% (estimate = 0.1243; IRR = 1.132; CI 95% = 1.062 - 1.207; p-value = < 0.001). Artinya, log1p_lag_total_fatalities mendukung perubahan rata-rata jumlah kejadian pada model Poisson. Karena prediktor berbentuk log(1 + X), interpretasi ini berlaku pada skala log, bukan kenaikan satu angka mentah.
region_label_Middle_East -0.296 0.024 0.744 0.576 - 0.962 Dengan variabel lain konstan, setiap kenaikan satu unit pada region_label_Middle_East membuat expected count case_count menurun sekitar 25.58% (estimate = -0.2955; IRR = 0.744; CI 95% = 0.576 - 0.962; p-value = 0.024). Artinya, region_label_Middle_East mendukung perubahan rata-rata jumlah kejadian pada model Poisson.

Model terbaik Poisson adalah kandidat ke-4, dengan 5 variabel signifikan dan AIC sebesar 2628.673. Uji \(G^2\) model terbaik memiliki p-value < 0.001, sehingga prediktor pada model terbaik secara simultan berkontribusi terhadap expected count case_count. Variabel X yang mendukung case_count adalah: log1p_lag_case_count, region_label_Europe, lag_conflict_count, log1p_lag_total_fatalities, region_label_Middle_East. Prediksi berikutnya dilakukan menggunakan model terbaik tersebut.

6.6 Uji Asumsi Model Terbaik

6.6.1 Asumsi 1 — Overdispersi (Pearson Dispersion)

Hipotesis:
\(H_0\): Tidak terdapat overdispersi; varians kondisional relatif sesuai dengan mean kondisional.
\(H_1\): Terdapat overdispersi; varians kondisional lebih besar daripada mean kondisional.
Kriteria praktis: dispersion Pearson mendekati 1 menunjukkan tidak ada overdispersi berat.

pearson_chi_square <- sum(residuals(poisson_best_fit, type="pearson")^2)
df_residual <- df.residual(poisson_best_fit)
dispersion_pearson <- pearson_chi_square/df_residual
overdispersion_p_value <- pchisq(pearson_chi_square, df=df_residual, lower.tail=FALSE)

poisson_overdispersion_table <- tibble(
  uji="Pearson chi-square overdispersion",
  pearson_chi_square=pearson_chi_square,
  df_residual=df_residual,
  dispersion_pearson=dispersion_pearson,
  p_value=format_p(overdispersion_p_value),
  keputusan=case_when(
    dispersion_pearson < 1.5 ~ "Tidak ada indikasi overdispersion berat",
    dispersion_pearson < 2.5 ~ "Ada indikasi overdispersion ringan-sedang",
    TRUE ~ "Ada indikasi overdispersion kuat"
  ),
  penjelasan_hasil=case_when(
    dispersion_pearson < 1.5 ~ "Asumsi equidispersion relatif terpenuhi.",
    dispersion_pearson < 2.5 ~ "Terdapat indikasi overdispersi ringan-sedang.",
    TRUE ~ "Terdapat indikasi overdispersi kuat."
  ),
  kesimpulan=case_when(
    dispersion_pearson < 1.5 ~ "Model Poisson layak digunakan karena tidak ada indikasi overdispersion berat.",
    TRUE ~ "Model Poisson dapat digunakan dengan catatan terdapat indikasi overdispersion."
  )
)
safe_table(poisson_overdispersion_table, "Uji overdispersi Pearson model terbaik Poisson tahunan", digits=4, full_width=TRUE)
Tabel 72. Uji overdispersi Pearson model terbaik Poisson tahunan
uji pearson_chi_square df_residual dispersion_pearson p_value keputusan penjelasan_hasil kesimpulan
Pearson chi-square overdispersion 2560.188 2325 1.1012 < 0.001 Tidak ada indikasi overdispersion berat Asumsi equidispersion relatif terpenuhi. Model Poisson layak digunakan karena tidak ada indikasi overdispersion berat.

6.6.2 Asumsi 2 — Proporsi Nilai Nol

Hipotesis/Kriteria:
\(H_0\): Proporsi nilai nol masih wajar untuk model Poisson.
\(H_1\): Proporsi nilai nol terlalu tinggi dan mengindikasikan potensi zero inflation.
Kriteria praktis: proporsi nol yang sangat besar perlu dibandingkan dengan konteks data dan hasil prediksi model.

poisson_zero_table <- tibble(
  ukuran=c("Jumlah observasi", "Jumlah case_count = 0", "Proporsi nilai nol"),
  nilai=c(nrow(poisson_train), sum(poisson_train$case_count == 0, na.rm=TRUE), mean(poisson_train$case_count == 0, na.rm=TRUE)),
  keterangan=c("Total observasi training", "Banyaknya observasi tanpa kejadian", "Proporsi nol yang tinggi dapat mengindikasikan zero inflation"),
  penjelasan_hasil=c("Total observasi yang digunakan pada model terbaik.", "Banyaknya observasi tanpa kejadian pada data training.", "Jika proporsi nilai nol sangat tinggi, terdapat potensi zero inflation."),
  kesimpulan=c("Tidak diperlukan.", "Tidak diperlukan.", "Pertimbangkan zero-inflated Poisson, hurdle model, atau negative binomial jika juga terjadi overdispersi.")
)
safe_table(poisson_zero_table, "Pemeriksaan proporsi nilai nol pada data training Poisson", digits=4, full_width=TRUE)
Tabel 73. Pemeriksaan proporsi nilai nol pada data training Poisson
ukuran nilai keterangan penjelasan_hasil kesimpulan
Jumlah observasi 2332.000 Total observasi training Total observasi yang digunakan pada model terbaik. Tidak diperlukan.
Jumlah case_count = 0 1812.000 Banyaknya observasi tanpa kejadian Banyaknya observasi tanpa kejadian pada data training. Tidak diperlukan.
Proporsi nilai nol 0.777 Proporsi nol yang tinggi dapat mengindikasikan zero inflation Jika proporsi nilai nol sangat tinggi, terdapat potensi zero inflation. Pertimbangkan zero-inflated Poisson, hurdle model, atau negative binomial jika juga terjadi overdispersi.

6.6.3 Asumsi 3 — Goodness-of-Fit (Deviance Test)

Hipotesis:
\(H_0\): Model Poisson memiliki kecocokan yang memadai terhadap data.
\(H_1\): Model Poisson tidak memiliki kecocokan yang memadai terhadap data.
Kriteria: p-value kecil menunjukkan indikasi lack of fit.

poisson_gof_table <- assumption_glm_table(poisson_best_fit, label="Poisson") |>
  filter(uji == "Goodness-of-fit deviance") |>
  mutate(p_value=p_value_tampil) |>
  select(-p_value_tampil)
safe_table(poisson_gof_table, "Goodness-of-Fit Deviance Test model terbaik Poisson", digits=4, full_width=TRUE)
Tabel 74. Goodness-of-Fit Deviance Test model terbaik Poisson
uji statistik df p_value keterangan penjelasan_hasil kesimpulan
Goodness-of-fit deviance 1417.137 2325 1.000 p-value besar menunjukkan tidak ada bukti kuat lack of fit berdasarkan deviance. H0 gagal ditolak; tidak ada bukti kuat lack of fit. Model layak digunakan karena tidak ada bukti kuat lack of fit berdasarkan deviance.

6.6.4 Asumsi 4 — Multikolinearitas (VIF)

Hipotesis/Kriteria:
\(H_0\): Tidak terdapat multikolinearitas berat antarprediktor.
\(H_1\): Terdapat multikolinearitas berat antarprediktor.
Kriteria praktis: VIF < 5 aman; VIF 5–10 perlu perhatian; VIF > 10 menunjukkan multikolinearitas berat.

poisson_vif_table <- vif_table(poisson_train, poisson_selection$best$vars)
poisson_corr_best_table <- selected_correlation_check(poisson_train, poisson_selection$best$vars)
safe_table(poisson_vif_table, "Uji multikolinearitas model terbaik Poisson menggunakan VIF", digits=3, full_width=TRUE)
Tabel 75. Uji multikolinearitas model terbaik Poisson menggunakan VIF
variabel VIF keterangan penjelasan_hasil kesimpulan
log1p_lag_case_count 44.888 Indikasi multikolinearitas berat Terdapat indikasi multikolinearitas berat. Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena terdapat indikasi multikolinearitas.
log1p_lag_total_fatalities 16.325 Indikasi multikolinearitas berat Terdapat indikasi multikolinearitas berat. Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena terdapat indikasi multikolinearitas.
lag_conflict_count 13.738 Indikasi multikolinearitas berat Terdapat indikasi multikolinearitas berat. Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena terdapat indikasi multikolinearitas.
region_label_Asia 1.087 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
region_label_Europe 1.079 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
region_label_Middle_East 1.067 Tidak ada indikasi multikolinearitas berat Asumsi tidak ada multikolinearitas berat relatif terpenuhi. Model layak digunakan karena tidak terdapat indikasi multikolinearitas berat.
safe_table(poisson_corr_best_table, "Pemeriksaan korelasi tinggi antarprediktor model terbaik Poisson", digits=3, full_width=TRUE)
Tabel 76. Pemeriksaan korelasi tinggi antarprediktor model terbaik Poisson
variabel_1 variabel_2 korelasi abs_korelasi keterangan penjelasan_hasil kesimpulan
log1p_lag_case_count log1p_lag_total_fatalities 0.946 0.946 Ada pasangan prediktor dengan korelasi tinggi; interpretasi koefisien perlu lebih hati-hati. Terdapat korelasi tinggi antarprediktor. Evaluasi prediktor yang redundant, gabungkan variabel yang mirip, atau gunakan reduksi dimensi.
log1p_lag_case_count lag_conflict_count 0.935 0.935 Ada pasangan prediktor dengan korelasi tinggi; interpretasi koefisien perlu lebih hati-hati. Terdapat korelasi tinggi antarprediktor. Evaluasi prediktor yang redundant, gabungkan variabel yang mirip, atau gunakan reduksi dimensi.
log1p_lag_total_fatalities lag_conflict_count 0.808 0.808 Ada pasangan prediktor dengan korelasi tinggi; interpretasi koefisien perlu lebih hati-hati. Terdapat korelasi tinggi antarprediktor. Evaluasi prediktor yang redundant, gabungkan variabel yang mirip, atau gunakan reduksi dimensi.

6.6.5 Asumsi 5 — Likelihood Ratio Test

Hipotesis:
\(H_0\): Semua koefisien prediktor sama dengan nol; model dengan prediktor tidak lebih baik daripada model kosong.
\(H_1\): Minimal ada satu koefisien prediktor tidak sama dengan nol; model dengan prediktor lebih baik daripada model kosong.

poisson_lrt_show <- poisson_best_g2 |> mutate(p_value=p_value_tampil) |> select(-p_value_tampil)
safe_table(poisson_lrt_show, "Likelihood Ratio Test atau uji G² model terbaik Poisson", digits=4, full_width=TRUE)
Tabel 77. Likelihood Ratio Test atau uji G² model terbaik Poisson
uji G2 df p_value keputusan penjelasan_hasil kesimpulan
Uji likelihood ratio 1576.612 6 < 0.001 Tolak H0 Model dengan prediktor memberikan peningkatan kecocokan dibanding model kosong. Model layak digunakan karena prediktor secara simultan lebih baik daripada model kosong.

6.6.6 Diagnostik Pengaruh Observasi

Diagnostik pengaruh observasi bukan uji hipotesis formal. Pemeriksaan ini digunakan untuk melihat apakah ada observasi yang terlalu memengaruhi estimasi model. Kriteria praktis: Cook’s distance > 1 perlu diperiksa, dan leverage tinggi dilihat dengan batas \(2p/n\).

poisson_influence_table <- influence_check_table(poisson_best_fit)
safe_table(poisson_influence_table, "Diagnostik pengaruh observasi model terbaik Poisson tahunan", digits=4, full_width=TRUE)
Tabel 78. Diagnostik pengaruh observasi model terbaik Poisson tahunan
diagnostik nilai keterangan penjelasan_hasil kesimpulan
Maximum Cook’s distance 0.0819 Nilai Cook’s distance yang besar menunjukkan observasi yang berpengaruh kuat. Semakin kecil nilai maksimum Cook’s distance, semakin kecil pengaruh observasi ekstrem terhadap model. Model dapat digunakan; nilai maksimum Cook’s distance menjadi catatan stabilitas model.
Jumlah Cook’s distance > 1 0.0000 Jumlah observasi dengan Cook’s distance > 1 perlu diperiksa lebih lanjut. Tidak ada observasi dengan Cook’s distance > 1. Model layak digunakan karena tidak ada Cook’s distance > 1.
Maximum leverage 0.2471 Leverage tinggi menunjukkan observasi dengan kombinasi prediktor yang ekstrem. Nilai leverage maksimum menunjukkan observasi dengan kombinasi prediktor paling ekstrem. Model dapat digunakan; leverage maksimum menjadi catatan untuk observasi dengan kombinasi prediktor ekstrem.
Jumlah leverage > 2p/n 230.0000 Aturan praktis leverage tinggi menggunakan batas 2p/n.  Ada observasi dengan leverage tinggi. Model digunakan dengan catatan terdapat observasi berleverage tinggi.

6.6.7 Ringkasan Uji Asumsi

poisson_gof_p_numeric <- assumption_glm_table(poisson_best_fit, label="Poisson") |>
  filter(uji == "Goodness-of-fit deviance") |>
  pull(p_value)

poisson_assumption_summary <- tibble(
  uji=c("Overdispersi Pearson", "Proporsi nilai nol", "Goodness-of-fit deviance", "VIF", "Likelihood Ratio Test", "Cook's distance"),
  angka_hasil=c(
    paste0("Dispersion Pearson = ", round(dispersion_pearson, 4), "; p-value = ", format_p(overdispersion_p_value)),
    paste0("Proporsi nol = ", round(poisson_zero_table$nilai[3], 4), "; jumlah nol = ", poisson_zero_table$nilai[2]),
    paste0("Deviance = ", round(poisson_gof_table$statistik, 3), "; df = ", poisson_gof_table$df, "; p-value = ", poisson_gof_table$p_value),
    ifelse(all(is.na(poisson_vif_table$VIF)), "VIF tidak dihitung", paste0("VIF maksimum = ", round(max(poisson_vif_table$VIF, na.rm=TRUE), 3))),
    paste0("G² = ", round(poisson_best_g2$G2, 3), "; df = ", poisson_best_g2$df, "; p-value = ", poisson_best_g2$p_value_tampil),
    paste0("Maksimum Cook's distance = ", round(poisson_influence_table$nilai[1], 4), "; jumlah Cook > 1 = ", poisson_influence_table$nilai[2])
  ),
  interpretasi_hasil=c(
    ifelse(dispersion_pearson < 1.5, "Tidak ada indikasi overdispersi berat.", "Ada indikasi overdispersi sehingga standard error Poisson perlu dibaca hati-hati."),
    ifelse(poisson_zero_table$nilai[3] > .50, "Proporsi nilai nol cukup tinggi dan dapat mengindikasikan zero inflation.", "Proporsi nilai nol tidak terlalu dominan berdasarkan batas praktis 50%."),
    ifelse(poisson_gof_p_numeric < .05, "Ada indikasi lack of fit berdasarkan deviance.", "Tidak ada bukti kuat lack of fit berdasarkan deviance."),
    ifelse(all(is.na(poisson_vif_table$VIF)) || max(poisson_vif_table$VIF, na.rm=TRUE) < 5, "Tidak ada indikasi multikolinearitas berat.", "Terdapat indikasi multikolinearitas yang perlu diperhatikan."),
    ifelse(poisson_best_g2$p_value < .05, "Model terbaik signifikan secara simultan dibanding model kosong.", "Model terbaik belum signifikan secara simultan dibanding model kosong."),
    ifelse(poisson_influence_table$nilai[2] > 0, "Ada observasi yang berpengaruh kuat dan perlu dicek.", "Tidak ada observasi dengan Cook's distance > 1.")
  ),
  kesimpulan=c(
    ifelse(dispersion_pearson < 1.5, "Model Poisson layak digunakan karena tidak ada indikasi overdispersi berat.", "Model Poisson dapat digunakan dengan catatan ada indikasi overdispersi."),
    ifelse(poisson_zero_table$nilai[3] > .50, "Model dapat digunakan dengan catatan proporsi nol cukup tinggi.", "Model layak digunakan karena proporsi nol tidak terlalu dominan."),
    ifelse(poisson_gof_p_numeric < .05, "Model digunakan dengan catatan terdapat indikasi lack of fit berdasarkan deviance.", "Model layak digunakan karena tidak ada bukti kuat lack of fit berdasarkan deviance."),
    ifelse(all(is.na(poisson_vif_table$VIF)) || max(poisson_vif_table$VIF, na.rm=TRUE) < 5, "Model layak digunakan karena tidak ada indikasi multikolinearitas berat.", "Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena ada indikasi multikolinearitas."),
    ifelse(poisson_best_g2$p_value < .05, "Model layak digunakan karena prediktor secara simultan mendukung expected count case_count.", "Model dapat dilaporkan dengan catatan dukungan simultan belum kuat."),
    ifelse(poisson_influence_table$nilai[2] > 0, "Model dapat digunakan dengan catatan ada observasi berpengaruh.", "Model layak digunakan karena tidak ada Cook's distance > 1.")
  )
)
safe_table(poisson_assumption_summary, "Ringkasan uji asumsi model terbaik Poisson", digits=4, full_width=TRUE)
Tabel 79. Ringkasan uji asumsi model terbaik Poisson
uji angka_hasil interpretasi_hasil kesimpulan
Overdispersi Pearson Dispersion Pearson = 1.1012; p-value = < 0.001 Tidak ada indikasi overdispersi berat. Model Poisson layak digunakan karena tidak ada indikasi overdispersi berat.
Proporsi nilai nol Proporsi nol = 0.777; jumlah nol = 1812 Proporsi nilai nol cukup tinggi dan dapat mengindikasikan zero inflation. Model dapat digunakan dengan catatan proporsi nol cukup tinggi.
Goodness-of-fit deviance Deviance = 1417.137; df = 2325; p-value = 1.000 Tidak ada bukti kuat lack of fit berdasarkan deviance. Model layak digunakan karena tidak ada bukti kuat lack of fit berdasarkan deviance.
VIF VIF maksimum = 44.888 Terdapat indikasi multikolinearitas yang perlu diperhatikan. Model masih dapat digunakan, tetapi interpretasi koefisien perlu hati-hati karena ada indikasi multikolinearitas.
Likelihood Ratio Test G² = 1576.612; df = 6; p-value = < 0.001 Model terbaik signifikan secara simultan dibanding model kosong. Model layak digunakan karena prediktor secara simultan mendukung expected count case_count.
Cook’s distance Maksimum Cook’s distance = 0.0819; jumlah Cook > 1 = 0 Tidak ada observasi dengan Cook’s distance > 1. Model layak digunakan karena tidak ada Cook’s distance > 1.

6.7 Prediksi dengan model terbaik

poisson_pred_mu <- predict(poisson_best_fit, newdata=poisson_test, type="response")
poisson_pred_table <- tibble(actual=poisson_test$case_count, predicted_mu=poisson_pred_mu, residual=actual-predicted_mu, abs_error=abs(residual), squared_error=residual^2)
poisson_metrics <- poisson_pred_table |> summarise(MAE=mean(abs_error), RMSE=sqrt(mean(squared_error)), mean_actual=mean(actual), mean_predicted=mean(predicted_mu), correlation_actual_predicted=cor(actual,predicted_mu))
safe_table(poisson_metrics, "Metrik prediksi testing model terbaik Poisson tahunan", digits=4)
Tabel 80. Metrik prediksi testing model terbaik Poisson tahunan
MAE RMSE mean_actual mean_predicted correlation_actual_predicted
0.3721 0.7836 0.4195 0.3873 0.6662
poisson_pred_table |> ggplot(aes(x=predicted_mu, y=actual))+geom_point(alpha=.45,color="#172033")+geom_abline(slope=1,intercept=0,linetype="dashed",color="#e11d48")+labs(title="Aktual vs Prediksi Expected Count", subtitle="Titik yang dekat dengan garis diagonal menunjukkan prediksi yang lebih akurat.", x="Predicted expected count", y="Actual count")+theme_report()
Gambar 8. Scatterplot aktual versus prediksi expected count model terbaik Poisson.

Gambar 8. Scatterplot aktual versus prediksi expected count model terbaik Poisson.

poisson_main_x <- poisson_selected_vars[1]
if(is.na(poisson_main_x) || length(poisson_main_x)==0){poisson_main_x <- poisson_selection$best$vars[1]}

poisson_profile <- make_profile_grid(poisson_train, poisson_selection$best$vars, xvar=poisson_main_x, n=120)
poisson_profile_pred <- predict(poisson_best_fit, newdata=poisson_profile, type="link", se.fit=TRUE)
poisson_profile <- poisson_profile |>
  mutate(
    fit_link = poisson_profile_pred$fit,
    se_link = poisson_profile_pred$se.fit,
    predicted_mu = exp(fit_link),
    ci_low = exp(fit_link - 1.96*se_link),
    ci_high = exp(fit_link + 1.96*se_link)
  )

poisson_profile |>
  ggplot(aes(x=.data[[poisson_main_x]], y=predicted_mu))+
  geom_ribbon(aes(ymin=ci_low, ymax=ci_high), fill="#6d28d9", alpha=.18)+
  geom_line(linewidth=1.25, color="#6d28d9")+
  labs(
    title="Kurva Prediksi Expected Violence Case Count",
    subtitle="Garis menunjukkan expected count; arsiran menunjukkan confidence interval 95%. Prediktor lain ditahan pada nilai median.",
    x=poisson_main_x,
    y="Predicted expected count"
  )+
  theme_report()
Gambar 9. Kurva prediksi expected count model terbaik Poisson dengan confidence interval 95 persen.

Gambar 9. Kurva prediksi expected count model terbaik Poisson dengan confidence interval 95 persen.

Interpretasi hasil prediksi: Prediksi Poisson menghasilkan expected count, bukan kategori. Pada data testing, MAE sebesar 0.372 menunjukkan bahwa rata-rata kesalahan absolut prediksi sekitar 0.372 kejadian. RMSE sebesar 0.784 menunjukkan besarnya kesalahan prediksi setelah memberi penalti lebih besar pada error yang ekstrem. Mean aktual adalah 0.42, sedangkan mean prediksi adalah 0.387. Korelasi aktual-prediksi sebesar 0.666 menunjukkan tingkat kesesuaian pola antara jumlah kejadian aktual dan expected count hasil model. Jika mean prediksi mendekati mean aktual dan korelasi bernilai positif, model dapat dipakai sebagai pendekatan prediksi rata-rata jumlah kejadian.

7 Perbandingan Empat Model

comparison_table <- tibble(
  model=c("Logistik biner","Logistik multinomial","Logistik ordinal","Poisson tahunan"),
  respons=c("food_crisis","choice_code","gov_confidence_ord","case_count"),
  sifat_respons=c("Biner","Nominal lebih dari dua kategori","Ordinal","Hitungan non-negatif"),
  kriteria_model_terbaik=c("Jumlah variabel signifikan, lalu AIC","Jumlah variabel signifikan, lalu AIC","Jumlah variabel signifikan, lalu AIC","Jumlah variabel signifikan, lalu AIC"),
  uji_model=c("G² likelihood ratio","G² likelihood ratio","G² likelihood ratio","G² likelihood ratio"),
  uji_koefisien=c("Wald z-test","Wald z-test","Wald z-test","Wald z-test"),
  ukuran_efek=c("Odds ratio","Relative risk ratio","Odds ratio","Incidence rate ratio"),
  bentuk_prediksi=c("Probabilitas dan kelas 0/1","Probabilitas tiap kategori","Probabilitas tiap tingkat ordinal","Expected count")
)
safe_table(comparison_table, "Perbandingan model berdasarkan skala respons, pemilihan model, uji model, ukuran efek, dan bentuk prediksi", full_width=TRUE)
Tabel 81. Perbandingan model berdasarkan skala respons, pemilihan model, uji model, ukuran efek, dan bentuk prediksi
model respons sifat_respons kriteria_model_terbaik uji_model uji_koefisien ukuran_efek bentuk_prediksi
Logistik biner food_crisis Biner Jumlah variabel signifikan, lalu AIC G² likelihood ratio Wald z-test Odds ratio Probabilitas dan kelas 0/1
Logistik multinomial choice_code Nominal lebih dari dua kategori Jumlah variabel signifikan, lalu AIC G² likelihood ratio Wald z-test Relative risk ratio Probabilitas tiap kategori
Logistik ordinal gov_confidence_ord Ordinal Jumlah variabel signifikan, lalu AIC G² likelihood ratio Wald z-test Odds ratio Probabilitas tiap tingkat ordinal
Poisson tahunan case_count Hitungan non-negatif Jumlah variabel signifikan, lalu AIC G² likelihood ratio Wald z-test Incidence rate ratio Expected count

Secara keseluruhan, model dipilih berdasarkan skala respons dan kemudian diseleksi berdasarkan jumlah variabel signifikan terlebih dahulu, baru AIC. Pendekatan ini menjaga agar model akhir tidak semata-mata mengejar AIC kecil, tetapi tetap mempertahankan variabel X yang secara statistik mendukung variabel Y. Setelah model terbaik diperoleh, interpretasi diarahkan pada variabel X yang signifikan, kemudian prediksi dilakukan memakai model terbaik pada data testing.