pal_wilayah <- c(
"Sumatera" = "#E4572E",
"Jawa" = "#17BEBB",
"Sulawesi" = "#FFC914",
"Kalimantan" = "#2E86AB",
"Bali–Nusa Tenggara" = "#76B041",
"Maluku–Papua" = "#9B5DE5"
)
theme_crime <- function(base = 13) {
theme_minimal(base_size = base) +
theme(
plot.title.position = "plot",
plot.title = element_text(face = "bold", size = base + 5, colour = "#1B2A41"),
plot.subtitle = element_text(colour = "#5C6B80", margin = margin(b = 10)),
plot.caption = element_text(colour = "#8A97A8", size = base - 3),
axis.title = element_text(colour = "#1B2A41", face = "bold"),
axis.text = element_text(colour = "#334155"),
panel.grid.minor = element_blank(),
panel.grid.major = element_line(colour = "#E5EAF0"),
legend.position = "bottom",
legend.title = element_text(face = "bold")
)
}
fmt_id <- label_number(big.mark = ".", decimal.mark = ",")
df <- tribble(
~Provinsi, ~Jumlah, ~Wilayah,
"Sumatera Utara", 45961, "Sumatera",
"DKI Jakarta", 36767, "Jawa",
"Jawa Barat", 36722, "Jawa",
"Jawa Timur", 27044, "Jawa",
"Sulawesi Selatan", 24725, "Sulawesi",
"Sumatera Selatan", 20682, "Sumatera",
"Banten", 19135, "Jawa",
"Lampung", 14018, "Sumatera",
"Riau", 13876, "Sumatera",
"Nusa Tenggara Timur", 10106, "Bali–Nusa Tenggara",
"Sulawesi Utara", 8879, "Sulawesi",
"Jawa Tengah", 8835, "Jawa",
"Sumatera Barat", 8795, "Sumatera",
"Papua", 7979, "Maluku–Papua",
"Sulawesi Tengah", 7924, "Sulawesi",
"Aceh", 7851, "Sumatera",
"Kalimantan Timur", 6425, "Kalimantan",
"Jambi", 5650, "Sumatera",
"Bali", 5500, "Bali–Nusa Tenggara",
"Kalimantan Selatan", 4375, "Kalimantan",
"Maluku", 4351, "Maluku–Papua",
"Bengkulu", 4201, "Sumatera",
"Nusa Tenggara Barat", 4005, "Bali–Nusa Tenggara",
"Sulawesi Tenggara", 3935, "Sulawesi",
"Kalimantan Barat", 3608, "Kalimantan",
"DI Yogyakarta", 3521, "Jawa",
"Kepulauan Riau", 3199, "Sumatera",
"Papua Barat", 2803, "Maluku–Papua",
"Kalimantan Tengah", 2782, "Kalimantan",
"Papua Barat Daya", 2696, "Maluku–Papua",
"Gorontalo", 2599, "Sulawesi",
"Kepulauan Bangka Belitung", 2486, "Sumatera",
"Sulawesi Barat", 1659, "Sulawesi",
"Kalimantan Utara", 1339, "Kalimantan",
"Papua Selatan", 384, "Maluku–Papua"
) %>%
mutate(Wilayah = factor(Wilayah, levels = names(pal_wilayah)))
stopifnot(nrow(df) == 35)
Kriminalitas merupakan salah satu fenomena sosial yang perlu dipahami melalui data. Analisis ini menggunakan data jumlah kejahatan per provinsi di Indonesia yang bersumber dari dashboard Pusiknas Polri.
Analisis bertujuan untuk menggambarkan persebaran jumlah kejahatan dan memeriksa asumsi yang diperlukan sebelum melakukan analisis varians (Analysis of Variance atau ANOVA).
Analisis menggunakan satu variabel numerik dan satu variabel kategorik.
| Variabel | Peran | Keterangan |
|---|---|---|
| Jumlah kejahatan | Dependen (Y) | Jumlah kejahatan pada setiap provinsi |
| Wilayah | Faktor (X) | Kelompok wilayah berdasarkan pulau |
Unit pengamatan adalah provinsi. Baris “Tidak diketahui” tidak disertakan dalam analisis.
Asumsi independensi menyatakan bahwa setiap pengamatan harus saling independen, sehingga nilai pada satu pengamatan tidak bergantung pada pengamatan lainnya.
Dalam analisis ini, unit pengamatan adalah provinsi di Indonesia, dengan variabel dependen berupa jumlah kejahatan dan faktor berupa kelompok wilayah. Setiap provinsi dicatat sebagai satu pengamatan.
Independensi perlu dipertimbangkan karena provinsi yang berdekatan mungkin memiliki kondisi sosial dan karakteristik kriminalitas yang serupa. Selain itu, data yang digunakan merupakan data rekapitulasi, bukan hasil pengambilan sampel acak atau eksperimen dengan perlakuan yang diacak.
Oleh karena itu, asumsi independensi tidak dapat dipastikan hanya dari tabel data. Asumsi ini perlu dinilai berdasarkan proses pengumpulan data dan kemungkinan keterkaitan antarprovinsi.
df %>%
group_by(Wilayah) %>%
summarise(
`Jumlah provinsi` = n(),
Provinsi = paste(sort(Provinsi), collapse = ", "),
.groups = "drop"
) %>%
kable(align = c("l", "c", "l")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
column_spec(1, bold = TRUE) %>%
column_spec(2, width = "9em")
| Wilayah | Jumlah provinsi | Provinsi |
|---|---|---|
| Sumatera | 10 | Aceh, Bengkulu, Jambi, Kepulauan Bangka Belitung, Kepulauan Riau, Lampung, Riau, Sumatera Barat, Sumatera Selatan, Sumatera Utara |
| Jawa | 6 | Banten, DI Yogyakarta, DKI Jakarta, Jawa Barat, Jawa Tengah, Jawa Timur |
| Sulawesi | 6 | Gorontalo, Sulawesi Barat, Sulawesi Selatan, Sulawesi Tengah, Sulawesi Tenggara, Sulawesi Utara |
| Kalimantan | 5 | Kalimantan Barat, Kalimantan Selatan, Kalimantan Tengah, Kalimantan Timur, Kalimantan Utara |
| Bali–Nusa Tenggara | 3 | Bali, Nusa Tenggara Barat, Nusa Tenggara Timur |
| Maluku–Papua | 5 | Maluku, Papua, Papua Barat, Papua Barat Daya, Papua Selatan |
cat("Provinsi duplikat:", sum(duplicated(df$Provinsi)), "\n")
## Provinsi duplikat: 0
Setiap provinsi muncul tepat satu kali, sehingga tidak ada pengamatan ganda. Ini hanya memastikan struktur datanya, bukan independensi antarprovinsi.
df %>%
mutate(Provinsi = fct_reorder(Provinsi, Jumlah)) %>%
ggplot(aes(x = Jumlah, y = Provinsi, fill = Wilayah)) +
geom_col(width = 0.75) +
scale_fill_manual(values = pal_wilayah) +
scale_x_continuous(labels = fmt_id, expand = expansion(mult = c(0, 0.08))) +
labs(
title = "Jumlah Kejahatan per Provinsi",
subtitle = "Warna menunjukkan kelompok wilayah",
x = "Jumlah kejahatan", y = NULL, fill = "Wilayah",
caption = "Sumber: Pusiknas Polri"
) +
theme_crime() +
theme(panel.grid.major.y = element_blank())
ggplot(df, aes(x = Wilayah, y = Jumlah, colour = Wilayah, fill = Wilayah)) +
geom_boxplot(alpha = 0.15, outlier.shape = NA, width = 0.55) +
geom_jitter(width = 0.12, size = 3, alpha = 0.9) +
stat_summary(fun = mean, geom = "point", shape = 23, size = 4,
fill = "white", colour = "#1B2A41", stroke = 1.2) +
scale_y_log10(labels = fmt_id) +
scale_colour_manual(values = pal_wilayah) +
scale_fill_manual(values = pal_wilayah) +
guides(colour = "none", fill = "none") +
coord_flip() +
labs(
title = "Sebaran Jumlah Kejahatan per Wilayah",
subtitle = "Titik = provinsi, berlian putih = rata-rata (sumbu log10 hanya untuk visualisasi)",
x = NULL, y = "Jumlah kejahatan (skala log)",
caption = "Sumber: Pusiknas Polri"
) +
theme_crime()
Provinsi dalam wilayah yang sama cenderung berkelompok pada tingkat jumlah kejahatan yang mirip, misalnya provinsi-provinsi di Jawa yang umumnya berada di bagian atas. Pola ini sejalan dengan kekhawatiran bahwa provinsi yang berdekatan tidak sepenuhnya independen. Grafik ini bersifat deskriptif, bukan uji formal.
tribble(
~Aspek, ~`Kondisi pada data ini`, ~Implikasi,
"Unit pengamatan", "Satu baris per provinsi, 35 pengamatan", "Struktur data sesuai, tanpa duplikasi",
"Pengacakan perlakuan", "Tidak ada; data rekapitulasi observasional", "Independensi tidak dijamin oleh desain",
"Pengambilan sampel", "Bukan sampel acak; mencakup seluruh provinsi", "Tidak ada mekanisme yang menjamin independensi",
"Kedekatan geografis", "Provinsi bertetangga berpotensi serupa", "Risiko ketergantungan spasial"
) %>%
kable() %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
column_spec(1, bold = TRUE)
| Aspek | Kondisi pada data ini | Implikasi |
|---|---|---|
| Unit pengamatan | Satu baris per provinsi, 35 pengamatan | Struktur data sesuai, tanpa duplikasi |
| Pengacakan perlakuan | Tidak ada; data rekapitulasi observasional | Independensi tidak dijamin oleh desain |
| Pengambilan sampel | Bukan sampel acak; mencakup seluruh provinsi | Tidak ada mekanisme yang menjamin independensi |
| Kedekatan geografis | Provinsi bertetangga berpotensi serupa | Risiko ketergantungan spasial |
Kesimpulan sementara: asumsi independensi tidak dapat dipastikan terpenuhi. Data berupa rekapitulasi observasional tanpa pengacakan, dan ada indikasi keserupaan antarprovinsi dalam wilayah yang sama. Hasil ANOVA selanjutnya sebaiknya ditafsirkan dengan hati-hati, dan keterbatasan ini dicantumkan di bagian kesimpulan.
Residual atau galat percobaan harus berdistribusi normal:
\[\varepsilon_{ij} \sim N(0, \sigma^2), \qquad \varepsilon_{ij} = Y_{ij} - \hat{Y}_{ij}\]
dengan \(\varepsilon_{ij}\) adalah kesalahan prediksi. Pada ANOVA satu arah, \(\hat{Y}_{ij}\) adalah rata-rata kelompok wilayah tempat provinsi tersebut berada.
Pengujian dilakukan pada residual model, bukan pada data mentah, dengan hipotesis:
Metode yang digunakan: uji Shapiro-Wilk, uji Kolmogorov-Smirnov (dengan koreksi Lilliefors, karena rata-rata dan simpangan baku residual diduga dari data), uji Anderson-Darling, serta Normal Q-Q Plot dan histogram residual.
Karena data jumlah kejahatan sangat miring ke kanan, pengujian dilakukan pada dua model: Y mentah dan ln(Y).
library(nortest)
df <- df %>% mutate(ln_Jumlah = log(Jumlah))
m_raw <- aov(Jumlah ~ Wilayah, data = df)
m_ln <- aov(ln_Jumlah ~ Wilayah, data = df)
lv <- c("Y mentah (Jumlah)", "ln(Y)")
res_df <- bind_rows(
tibble(Provinsi = df$Provinsi, Wilayah = df$Wilayah, Model = lv[1],
Fitted = fitted(m_raw), Residual = residuals(m_raw)),
tibble(Provinsi = df$Provinsi, Wilayah = df$Wilayah, Model = lv[2],
Fitted = fitted(m_ln), Residual = residuals(m_ln))
) %>%
mutate(Model = factor(Model, levels = lv))
res_df %>%
group_by(Model) %>%
summarise(
n = n(),
`Rata-rata residual` = mean(Residual),
`Simpangan baku` = sd(Residual),
Skewness = mean((Residual - mean(Residual))^3) / sd(Residual)^3,
.groups = "drop"
) %>%
kable(digits = 4, format.args = list(scientific = FALSE)) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Model | n | Rata-rata residual | Simpangan baku | Skewness |
|---|---|---|---|---|
| Y mentah (Jumlah) | 35 | 0 | 9304.6844 | 1.2858 |
| ln(Y) | 35 | 0 | 0.8281 | -0.2455 |
uji_normalitas <- function(x) {
sw <- shapiro.test(x)
ll <- lillie.test(x)
ad <- ad.test(x)
tibble(
Uji = c("Shapiro-Wilk", "Kolmogorov-Smirnov (Lilliefors)", "Anderson-Darling"),
Statistik = c(unname(sw$statistic), unname(ll$statistic), unname(ad$statistic)),
p_value = c(sw$p.value, ll$p.value, ad$p.value)
)
}
hasil_uji <- res_df %>%
group_by(Model) %>%
group_modify(~ uji_normalitas(.x$Residual)) %>%
ungroup() %>%
mutate(
Keputusan = if_else(p_value > 0.05,
"Normal (gagal tolak H0)",
"Tidak normal (tolak H0)"),
Keputusan = cell_spec(Keputusan, format = "html", bold = TRUE,
color = if_else(p_value > 0.05, "#2E7D32", "#C62828")),
`p-value` = if_else(p_value < 0.0001, "< 0,0001",
formatC(p_value, format = "f", digits = 4, decimal.mark = ","))
)
idx <- table(hasil_uji$Model)
hasil_uji %>%
select(Uji, Statistik, `p-value`, Keputusan) %>%
kable(format = "html", digits = 4, escape = FALSE,
align = c("l", "c", "c", "l")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
pack_rows(index = setNames(as.integer(idx), names(idx)),
label_row_css = "background-color:#1B2A41; color:#fff;")
| Uji | Statistik | p-value | Keputusan |
|---|---|---|---|
| Y mentah (Jumlah) | |||
| Shapiro-Wilk | 0.8913 | 0,0023 | Tidak normal (tolak H0) |
| Kolmogorov-Smirnov (Lilliefors) | 0.1853 | 0,0037 | Tidak normal (tolak H0) |
| Anderson-Darling | 1.2446 | 0,0026 | Tidak normal (tolak H0) |
| ln(Y) | |||
| Shapiro-Wilk | 0.9839 | 0,8757 | Normal (gagal tolak H0) |
| Kolmogorov-Smirnov (Lilliefors) | 0.0855 | 0,7443 | Normal (gagal tolak H0) |
| Anderson-Darling | 0.2578 | 0,6987 | Normal (gagal tolak H0) |
ggplot(res_df, aes(sample = Residual)) +
stat_qq_line(colour = "#1B2A41", linetype = "dashed", linewidth = 0.8) +
stat_qq(aes(colour = Wilayah), size = 3, alpha = 0.9) +
facet_wrap(~ Model, scales = "free") +
scale_colour_manual(values = pal_wilayah) +
labs(
title = "Normal Q-Q Plot Residual",
subtitle = "Titik yang mengikuti garis putus-putus menandakan residual mendekati normal",
x = "Kuantil teoretis", y = "Kuantil residual", colour = "Wilayah",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41"))
kurva <- res_df %>%
group_by(Model) %>%
group_modify(~ {
m <- mean(.x$Residual); s <- sd(.x$Residual)
tibble(Residual = seq(m - 4 * s, m + 4 * s, length.out = 300),
dens = dnorm(Residual, m, s))
}) %>%
ungroup()
ggplot(res_df, aes(x = Residual)) +
geom_histogram(aes(y = after_stat(density)), bins = 12,
fill = "#17BEBB", colour = "white", alpha = 0.6) +
geom_density(colour = "#E4572E", linewidth = 1.1) +
geom_line(data = kurva, aes(y = dens), colour = "#1B2A41",
linetype = "dashed", linewidth = 0.9) +
facet_wrap(~ Model, scales = "free") +
labs(
title = "Distribusi Residual Model ANOVA",
subtitle = "Garis oranye = kepadatan residual, garis putus-putus = kurva normal teoretis",
x = "Residual", y = "Kepadatan",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41"))
ringkas <- hasil_uji %>%
group_by(Model) %>%
summarise(lolos = sum(p_value > 0.05), .groups = "drop")
cat('<div class="callout"><b>Kesimpulan sementara:</b><br>')
Kesimpulan sementara:
for (i in seq_len(nrow(ringkas))) {
cat(sprintf("• Model <b>%s</b>: %d dari 3 uji tidak menolak normalitas residual.<br>",
ringkas$Model[i], ringkas$lolos[i]))
}
• Model Y mentah (Jumlah): 0 dari 3 uji tidak menolak
normalitas residual.
• Model ln(Y): 3 dari 3 uji tidak menolak
normalitas residual.
cat("Interpretasi akhir dilengkapi dengan membaca Q-Q plot dan histogram di atas.</div>")
Interpretasi akhir dilengkapi dengan membaca Q-Q plot dan histogram di
atas.
Varians seluruh kelompok perlakuan harus relatif sama:
\[\sigma_1^2 = \sigma_2^2 = \sigma_3^2 = \cdots = \sigma_k^2\]
Pada analisis ini, kelompok perlakuan adalah 6 kelompok wilayah. Hipotesis yang diuji:
Metode yang digunakan:
| Uji | Keterangan |
|---|---|
| Bartlett | Sensitif terhadap pelanggaran normalitas |
| Levene | Berbasis simpangan terhadap rata-rata kelompok, lebih tahan terhadap ketidaknormalan |
| Brown-Forsythe | Varian Levene berbasis median, paling tahan terhadap data miring dan pencilan |
Seperti pada uji normalitas, pengujian dilakukan pada dua model: Y mentah dan ln(Y).
lv_model <- c("Y mentah (Jumlah)", "ln(Y)")
long_df <- bind_rows(
df %>% transmute(Provinsi, Wilayah, Model = lv_model[1], Nilai = Jumlah),
df %>% transmute(Provinsi, Wilayah, Model = lv_model[2], Nilai = ln_Jumlah)
) %>%
mutate(Model = factor(Model, levels = lv_model))
sd_df <- long_df %>%
group_by(Model, Wilayah) %>%
summarise(n = n(), SD = sd(Nilai), Varians = var(Nilai), .groups = "drop")
idx_sd <- table(sd_df$Model)
sd_df %>%
select(Wilayah, n, SD, Varians) %>%
kable(format = "html", digits = 2,
format.args = list(big.mark = ".", decimal.mark = ",", scientific = FALSE),
align = c("l", "c", "r", "r")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
pack_rows(index = setNames(as.integer(idx_sd), names(idx_sd)),
label_row_css = "background-color:#1B2A41; color:#fff;")
| Wilayah | n | SD | Varians |
|---|---|---|---|
| Y mentah (Jumlah) | |||
| Sumatera | 10 | 13.031,63 | 169.823.419,21 |
| Jawa | 6 | 14.023,14 | 196.648.460,80 |
| Sulawesi | 6 | 8.554,69 | 73.182.741,77 |
| Kalimantan | 5 | 1.891,20 | 3.576.647,70 |
| Bali–Nusa Tenggara | 3 | 3.179,95 | 10.112.077,00 |
| Maluku–Papua | 5 | 2.807,39 | 7.881.412,30 |
| ln(Y) | |||
| Sumatera | 10 | 0,90 | 0,81 |
| Jawa | 6 | 0,93 | 0,86 |
| Sulawesi | 6 | 0,97 | 0,95 |
| Kalimantan | 5 | 0,59 | 0,34 |
| Bali–Nusa Tenggara | 3 | 0,47 | 0,22 |
| Maluku–Papua | 5 | 1,14 | 1,30 |
sd_df %>%
group_by(Model) %>%
summarise(
`Varians terbesar` = max(Varians),
`Varians terkecil` = min(Varians),
`Rasio varians` = max(Varians) / min(Varians),
`Rasio SD` = max(SD) / min(SD),
.groups = "drop"
) %>%
kable(format = "html", digits = 2,
format.args = list(big.mark = ".", decimal.mark = ",", scientific = FALSE)) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Model | Varians terbesar | Varians terkecil | Rasio varians | Rasio SD |
|---|---|---|---|---|
| Y mentah (Jumlah) | 196.648.460,8 | 3.576.647,70 | 54,98 | 7,41 |
| ln(Y) | 1,3 | 0,22 | 5,86 | 2,42 |
Rasio di atas bersifat deskriptif. Semakin dekat ke 1, semakin seragam varians antarkelompok.
library(car)
uji_homogen <- function(formula, data) {
bt <- bartlett.test(formula, data = data)
lev_m <- leveneTest(formula, data = data, center = mean)
lev_med <- leveneTest(formula, data = data, center = median)
tibble(
Uji = c("Bartlett", "Levene", "Brown-Forsythe"),
Statistik = c(unname(bt$statistic), lev_m$`F value`[1], lev_med$`F value`[1]),
p_value = c(bt$p.value, lev_m$`Pr(>F)`[1], lev_med$`Pr(>F)`[1])
)
}
hasil_homogen <- bind_rows(
uji_homogen(Jumlah ~ Wilayah, df) %>% mutate(Model = lv_model[1]),
uji_homogen(ln_Jumlah ~ Wilayah, df) %>% mutate(Model = lv_model[2])
) %>%
mutate(Model = factor(Model, levels = lv_model)) %>%
mutate(
Keputusan = if_else(p_value > 0.05,
"Homogen (gagal tolak H0)",
"Tidak homogen (tolak H0)"),
Keputusan = cell_spec(Keputusan, format = "html", bold = TRUE,
color = if_else(p_value > 0.05, "#2E7D32", "#C62828")),
`p-value` = if_else(p_value < 0.0001, "< 0,0001",
formatC(p_value, format = "f", digits = 4, decimal.mark = ","))
)
idx_h <- table(hasil_homogen$Model)
hasil_homogen %>%
select(Uji, Statistik, `p-value`, Keputusan) %>%
kable(format = "html", digits = 4, escape = FALSE,
align = c("l", "c", "c", "l")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
pack_rows(index = setNames(as.integer(idx_h), names(idx_h)),
label_row_css = "background-color:#1B2A41; color:#fff;")
| Uji | Statistik | p-value | Keputusan |
|---|---|---|---|
| Y mentah (Jumlah) | |||
| Bartlett | 19.9510 | 0,0013 | Tidak homogen (tolak H0) |
| Levene | 2.4678 | 0,0558 | Homogen (gagal tolak H0) |
| Brown-Forsythe | 1.7677 | 0,1509 | Homogen (gagal tolak H0) |
| ln(Y) | |||
| Bartlett | 2.6061 | 0,7604 | Homogen (gagal tolak H0) |
| Levene | 0.5737 | 0,7196 | Homogen (gagal tolak H0) |
| Brown-Forsythe | 0.4926 | 0,7790 | Homogen (gagal tolak H0) |
ggplot(sd_df, aes(x = fct_rev(Wilayah), y = SD, fill = Wilayah)) +
geom_col(width = 0.7) +
geom_text(aes(label = paste0("n = ", n)), hjust = -0.15, size = 3.6,
colour = "#334155") +
coord_flip() +
facet_wrap(~ Model, scales = "free_x") +
scale_fill_manual(values = pal_wilayah) +
scale_y_continuous(labels = fmt_id, expand = expansion(mult = c(0, 0.2))) +
guides(fill = "none") +
labs(
title = "Simpangan Baku per Kelompok Wilayah",
subtitle = "Batang yang tingginya mirip menandakan varians antarkelompok relatif seragam",
x = NULL, y = "Simpangan baku",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41"))
ggplot(res_df, aes(x = Wilayah, y = Residual, colour = Wilayah, fill = Wilayah)) +
geom_hline(yintercept = 0, colour = "#1B2A41", linetype = "dashed") +
geom_boxplot(alpha = 0.15, outlier.shape = NA, width = 0.55) +
geom_jitter(width = 0.12, size = 2.8, alpha = 0.9) +
coord_flip() +
facet_wrap(~ Model, scales = "free_x") +
scale_colour_manual(values = pal_wilayah) +
scale_fill_manual(values = pal_wilayah) +
guides(colour = "none", fill = "none") +
labs(
title = "Sebaran Residual per Kelompok Wilayah",
subtitle = "Lebar sebaran residual yang mirip antarwilayah menandakan varians homogen",
x = NULL, y = "Residual",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41"))
ggplot(res_df, aes(x = Fitted, y = Residual, colour = Wilayah)) +
geom_hline(yintercept = 0, colour = "#1B2A41", linetype = "dashed") +
geom_point(size = 3.2, alpha = 0.9) +
facet_wrap(~ Model, scales = "free") +
scale_colour_manual(values = pal_wilayah) +
labs(
title = "Residual terhadap Nilai Prediksi",
subtitle = "Pola menyebar (corong) menandakan varians berubah mengikuti besarnya nilai prediksi",
x = "Nilai prediksi (rata-rata kelompok)", y = "Residual", colour = "Wilayah",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41"))
ringkas_h <- hasil_homogen %>%
group_by(Model) %>%
summarise(lolos = sum(p_value > 0.05), .groups = "drop")
cat('<div class="callout"><b>Kesimpulan sementara:</b><br>')
Kesimpulan sementara:
for (i in seq_len(nrow(ringkas_h))) {
cat(sprintf("• Model <b>%s</b>: %d dari 3 uji tidak menolak homogenitas varians.<br>",
ringkas_h$Model[i], ringkas_h$lolos[i]))
}
• Model Y mentah (Jumlah): 2 dari 3 uji tidak menolak
homogenitas varians.
• Model ln(Y): 3 dari 3 uji tidak menolak
homogenitas varians.
cat("Interpretasi akhir dilengkapi dengan grafik simpangan baku dan plot residual di atas.</div>")
Interpretasi akhir dilengkapi dengan grafik simpangan baku dan plot
residual di atas.
Dalam model ANOVA diasumsikan bahwa respons dapat dijelaskan oleh efek perlakuan dan galat acak. Pada model faktorial dua faktor:
\[Y_{ijk} = \mu + \alpha_i + \beta_j + (\alpha\beta)_{ij} + \varepsilon_{ijk}\]
dengan \(\mu\) rata-rata umum, \(\alpha_i\) efek faktor A, \(\beta_j\) efek faktor B, \((\alpha\beta)_{ij}\) efek interaksi, dan \(\varepsilon_{ijk}\) galat.
Analisis ini hanya memiliki satu faktor (wilayah), sehingga model yang dipakai adalah model satu arah:
\[Y_{ij} = \mu + \alpha_i + \varepsilon_{ij}\]
Pada model ini tidak ada faktor kedua maupun interaksi yang perlu diuji. Asumsi aditif di sini berarti efek wilayah dan galat dijumlahkan pada skala pengukuran yang dipakai. Jika efek sebenarnya bersifat perkalian (\(Y = \mu \cdot \alpha_i \cdot \varepsilon\)), model aditif tidak cocok pada skala asli. Transformasi logaritma mengubahnya menjadi aditif:
\[\ln Y = \ln \mu + \ln \alpha_i + \ln \varepsilon\]
Asumsi ini diperiksa melalui analisis residual dan melalui tiga pemeriksaan tambahan di bawah. Seperti pada poin sebelumnya, pemeriksaan dilakukan pada Y mentah dan ln(Y).
efek_fun <- function(data, var, nama_model) {
mu <- mean(data[[var]])
data %>%
group_by(Wilayah) %>%
summarise(n = n(), Rata2 = mean(.data[[var]]), .groups = "drop") %>%
mutate(Model = nama_model, mu = mu, alpha = Rata2 - mu)
}
efek_df <- bind_rows(
efek_fun(df, "Jumlah", lv_model[1]),
efek_fun(df, "ln_Jumlah", lv_model[2])
) %>%
mutate(Model = factor(Model, levels = lv_model))
idx_e <- table(efek_df$Model)
efek_df %>%
transmute(
Wilayah, n,
`Rata-rata kelompok` = Rata2,
`μ (rata-rata umum)` = mu,
`α (efek wilayah)` = alpha
) %>%
kable(format = "html", digits = 3,
format.args = list(big.mark = ".", decimal.mark = ",", scientific = FALSE),
align = c("l", "c", "r", "r", "r")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
pack_rows(index = setNames(as.integer(idx_e), names(idx_e)),
label_row_css = "background-color:#1B2A41; color:#fff;")
| Wilayah | n | Rata-rata kelompok | μ (rata-rata umum) | α (efek wilayah) |
|---|---|---|---|---|
| Y mentah (Jumlah) | ||||
| Sumatera | 10 | 12.671,900 | 10.423,343 | 2.248,557 |
| Jawa | 6 | 22.004,000 | 10.423,343 | 11.580,657 |
| Sulawesi | 6 | 8.286,833 | 10.423,343 | -2.136,510 |
| Kalimantan | 5 | 3.705,800 | 10.423,343 | -6.717,543 |
| Bali–Nusa Tenggara | 3 | 6.537,000 | 10.423,343 | -3.886,343 |
| Maluku–Papua | 5 | 3.642,600 | 10.423,343 | -6.780,743 |
| ln(Y) | ||||
| Sumatera | 10 | 9,068 | 8,758 | 0,311 |
| Jawa | 6 | 9,723 | 8,758 | 0,966 |
| Sulawesi | 6 | 8,623 | 8,758 | -0,134 |
| Kalimantan | 5 | 8,095 | 8,758 | -0,663 |
| Bali–Nusa Tenggara | 3 | 8,710 | 8,758 | -0,048 |
| Maluku–Papua | 5 | 7,830 | 8,758 | -0,927 |
efek_df %>%
group_by(Model) %>%
summarise(`Σ n·α (harus mendekati 0)` = sum(n * alpha), .groups = "drop") %>%
kable(format = "html", digits = 6,
format.args = list(scientific = FALSE)) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Model | Σ n·α (harus mendekati 0) |
|---|---|
| Y mentah (Jumlah) | 0 |
| ln(Y) | 0 |
Setiap nilai pengamatan dapat dituliskan sebagai rata-rata umum ditambah efek wilayahnya ditambah galat. Jumlah efek yang dibobot ukuran kelompok bernilai nol, sebagaimana syarat model ANOVA.
ggplot(efek_df, aes(x = fct_rev(Wilayah), y = alpha, fill = Wilayah)) +
geom_hline(yintercept = 0, colour = "#1B2A41") +
geom_col(width = 0.7) +
coord_flip() +
facet_wrap(~ Model, scales = "free_x") +
scale_fill_manual(values = pal_wilayah) +
scale_y_continuous(labels = fmt_id) +
guides(fill = "none") +
labs(
title = "Efek Wilayah terhadap Rata-Rata Umum",
subtitle = "Batang ke kanan = di atas rata-rata umum, ke kiri = di bawah rata-rata umum",
x = NULL, y = "Efek wilayah (α)",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41"))
Jika efek bersifat perkalian, simpangan baku kelompok akan naik seiring rata-ratanya. Hubungan ini diperiksa pada data mentah. Setelah transformasi log, hubungan tersebut seharusnya melemah.
sl_df <- df %>%
group_by(Wilayah) %>%
summarise(Rata2 = mean(Jumlah), SD = sd(Jumlah), n = n(), .groups = "drop")
fit_sl <- lm(log(SD) ~ log(Rata2), data = sl_df)
slope <- unname(coef(fit_sl)[2])
ggplot(sl_df, aes(x = Rata2, y = SD)) +
geom_smooth(method = "lm", se = FALSE, colour = "#1B2A41",
linetype = "dashed", linewidth = 0.9) +
geom_point(aes(colour = Wilayah), size = 5) +
geom_text(aes(label = Wilayah), vjust = -1.2, size = 3.8, colour = "#334155") +
scale_x_log10(labels = fmt_id) +
scale_y_log10(labels = fmt_id) +
scale_colour_manual(values = pal_wilayah) +
guides(colour = "none") +
labs(
title = "Rata-Rata vs Simpangan Baku per Wilayah (Y mentah)",
subtitle = sprintf("Kemiringan garis pada skala log-log = %.2f (mendekati 1 berarti efek perkalian)", slope),
x = "Rata-rata kelompok (skala log)", y = "Simpangan baku kelompok (skala log)",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime()
Metode Box-Cox mencari pangkat \(\lambda\) yang membuat model paling sesuai dengan asumsi aditif dan galat normal. Nilai \(\lambda = 0\) setara dengan transformasi logaritma.
bc <- MASS::boxcox(Jumlah ~ Wilayah, data = df,
lambda = seq(-1, 1.5, by = 0.01), plot = FALSE)
bc_df <- tibble(lambda = bc$x, loglik = bc$y)
lam_hat <- bc_df$lambda[which.max(bc_df$loglik)]
cut_ll <- max(bc_df$loglik) - qchisq(0.95, 1) / 2
ci_lam <- range(bc_df$lambda[bc_df$loglik >= cut_ll])
ggplot(bc_df, aes(lambda, loglik)) +
annotate("rect", xmin = ci_lam[1], xmax = ci_lam[2],
ymin = -Inf, ymax = Inf, fill = "#17BEBB", alpha = 0.15) +
geom_line(colour = "#E4572E", linewidth = 1.3) +
geom_hline(yintercept = cut_ll, colour = "#1B2A41", linetype = "dashed") +
geom_vline(xintercept = lam_hat, colour = "#1B2A41") +
geom_vline(xintercept = 0, colour = "#9B5DE5", linetype = "dotted", linewidth = 1) +
annotate("text", x = lam_hat, y = max(bc_df$loglik), vjust = -0.8,
label = sprintf("λ optimal = %.2f", lam_hat), fontface = "bold",
colour = "#1B2A41") +
labs(
title = "Profil Likelihood Box-Cox",
subtitle = "Area biru = selang kepercayaan 95% untuk λ; garis ungu putus-putus = λ = 0 (ln)",
x = expression(lambda), y = "Log-likelihood",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime()
Plot residual terhadap nilai prediksi
(grafik-residual-fitted) sudah ditampilkan pada bagian
homogenitas varians dan dibaca sekaligus untuk asumsi ini. Pola corong
pada Y mentah menunjukkan galat yang membesar bersama rata-ratanya (ciri
efek perkalian). Sebaran yang merata pada ln(Y) menunjukkan model aditif
cocok pada skala logaritma.
nol_dalam_ci <- ci_lam[1] <= 0 & 0 <= ci_lam[2]
cat('<div class="callout"><b>Kesimpulan sementara:</b><br>')
Kesimpulan sementara:
cat(sprintf("• Kemiringan log(SD) terhadap log(rata-rata) pada Y mentah = <b>%.2f</b>.<br>", slope))
• Kemiringan log(SD) terhadap log(rata-rata) pada Y mentah =
1.14.
cat(sprintf("• Box-Cox: λ optimal = <b>%.2f</b>, selang kepercayaan 95%% = [%.2f; %.2f]; λ = 0 (transformasi ln) %s dalam selang tersebut.<br>",
lam_hat, ci_lam[1], ci_lam[2],
if (nol_dalam_ci) "<b>berada</b>" else "<b>tidak berada</b>"))
• Box-Cox: λ optimal = 0.06, selang kepercayaan 95% = [-0.18;
0.31]; λ = 0 (transformasi ln) berada dalam selang
tersebut.
cat("Interpretasi akhir dilengkapi dengan grafik efek wilayah dan plot residual terhadap nilai prediksi.</div>")
Interpretasi akhir dilengkapi dengan grafik efek wilayah dan plot
residual terhadap nilai prediksi.
Rata-rata dari residual diharapkan mendekati nol:
\[E(\varepsilon) = 0, \qquad \bar{e} = \frac{1}{n}\sum_{i=1}^{k}\sum_{j=1}^{n_i} e_{ij} \approx 0\]
Pada model ANOVA satu arah dengan intercept, nilai prediksi untuk setiap pengamatan adalah rata-rata kelompoknya, sehingga residual di dalam tiap kelompok saling meniadakan:
\[\sum_{j=1}^{n_i}\left(Y_{ij}-\bar{Y}_{i\cdot}\right)=0 \quad\Longrightarrow\quad \sum_{i}\sum_{j} e_{ij}=0\]
Hipotesis untuk uji-t satu sampel pada residual:
Pemeriksaan dilakukan pada Y mentah dan ln(Y).
rata_num <- res_df %>%
group_by(Model) %>%
summarise(
n = n(),
jumlah = sum(Residual),
rata = mean(Residual),
sd_res = sd(Residual),
n_pos = sum(Residual > 0),
n_neg = sum(Residual < 0),
jml_pos = sum(Residual[Residual > 0]),
jml_neg = sum(Residual[Residual < 0]),
.groups = "drop"
)
sci <- function(x) formatC(x, format = "e", digits = 2, decimal.mark = ",")
num <- function(x) formatC(x, format = "f", digits = 2, big.mark = ".", decimal.mark = ",")
rata_num %>%
transmute(
Model,
n,
`Σ residual` = sci(jumlah),
`Rata-rata residual` = sci(rata),
`Simpangan baku` = num(sd_res)
) %>%
kable(format = "html", align = c("l", "c", "r", "r", "r")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
| Model | n | Σ residual | Rata-rata residual | Simpangan baku |
|---|---|---|---|---|
| Y mentah (Jumlah) | 35 | -9,09e-13 | -2,49e-14 | 9.304,68 |
| ln(Y) | 35 | -1,83e-15 | -5,24e-17 | 0,83 |
Nilai seperti 1e-15 pada rata-rata residual dibandingkan
simpangan baku yang bernilai ribuan (Y mentah) atau mendekati satu
(ln(Y)) dapat dianggap nol. Selisihnya murni berasal dari pembulatan
komputasi.
rata_num %>%
transmute(
Model,
`Jumlah residual positif` = n_pos,
`Σ residual positif` = num(jml_pos),
`Jumlah residual negatif` = n_neg,
`Σ residual negatif` = num(jml_neg),
`Selisih total` = sci(jml_pos + jml_neg)
) %>%
kable(format = "html", align = c("l", "c", "r", "c", "r", "r")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
| Model | Jumlah residual positif | Σ residual positif | Jumlah residual negatif | Σ residual negatif | Selisih total |
|---|---|---|---|---|---|
| Y mentah (Jumlah) | 14 | 107.402,93 | 21 | -107.402,93 | 0,00e+00 |
| ln(Y) | 20 | 11,46 | 15 | -11,46 | -1,78e-15 |
Total residual positif sama besar dengan total residual negatif (berlawanan tanda), sehingga jumlahnya nol.
rw <- res_df %>%
group_by(Model, Wilayah) %>%
summarise(n = n(), rata = mean(Residual), .groups = "drop")
idx_rw <- table(rw$Model)
rw %>%
transmute(Wilayah, n, `Rata-rata residual` = sci(rata)) %>%
kable(format = "html", align = c("l", "c", "r")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
pack_rows(index = setNames(as.integer(idx_rw), names(idx_rw)),
label_row_css = "background-color:#1B2A41; color:#fff;")
| Wilayah | n | Rata-rata residual |
|---|---|---|
| Y mentah (Jumlah) | ||
| Sumatera | 10 | -1,50e-12 |
| Jawa | 6 | 1,52e-13 |
| Sulawesi | 6 | 2,73e-12 |
| Kalimantan | 5 | 8,19e-13 |
| Bali–Nusa Tenggara | 3 | -2,73e-12 |
| Maluku–Papua | 5 | 1,82e-13 |
| ln(Y) | ||
| Sumatera | 10 | -8,88e-17 |
| Jawa | 6 | 8,79e-17 |
| Sulawesi | 6 | 3,70e-17 |
| Kalimantan | 5 | 3,33e-17 |
| Bali–Nusa Tenggara | 3 | -2,31e-16 |
| Maluku–Papua | 5 | -2,33e-16 |
Rata-rata residual di setiap kelompok wilayah juga nol. Ini sesuai dengan sifat model satu arah, yaitu nilai prediksinya adalah rata-rata kelompok.
uji_t <- res_df %>%
group_by(Model) %>%
group_modify(~ {
tt <- t.test(.x$Residual, mu = 0)
tibble(
`Rata-rata` = sci(unname(tt$estimate)),
`Statistik t` = sci(unname(tt$statistic)),
db = unname(tt$parameter),
`p-value` = formatC(tt$p.value, format = "f", digits = 4, decimal.mark = ","),
p_value = tt$p.value
)
}) %>%
ungroup() %>%
mutate(
Keputusan = if_else(p_value > 0.05,
"Rata-rata residual = 0 (gagal tolak H0)",
"Rata-rata residual ≠ 0 (tolak H0)"),
Keputusan = cell_spec(Keputusan, format = "html", bold = TRUE,
color = if_else(p_value > 0.05, "#2E7D32", "#C62828"))
)
uji_t %>%
select(Model, `Rata-rata`, `Statistik t`, db, `p-value`, Keputusan) %>%
kable(format = "html", escape = FALSE,
align = c("l", "r", "r", "c", "c", "l")) %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
| Model | Rata-rata | Statistik t | db | p-value | Keputusan |
|---|---|---|---|---|---|
| Y mentah (Jumlah) | -2,49e-14 | -1,59e-17 | 34 | 1,0000 | Rata-rata residual = 0 (gagal tolak H0) |
| ln(Y) | -5,24e-17 | -3,74e-16 | 34 | 1,0000 | Rata-rata residual = 0 (gagal tolak H0) |
Statistik t bernilai hampir nol dan p-value sama dengan 1. Hasil ini tidak menunjukkan bahwa residual diperoleh dari populasi dengan rata-rata nol secara kebetulan, melainkan konsekuensi dari cara model diduga. Karena itu, uji-t ini bersifat konfirmasi, bukan bukti independen.
urut_prov <- df %>%
arrange(desc(Wilayah), desc(Provinsi)) %>%
pull(Provinsi)
res_df %>%
mutate(Provinsi = factor(Provinsi, levels = urut_prov)) %>%
ggplot(aes(x = Residual, y = Provinsi, colour = Wilayah)) +
geom_vline(xintercept = 0, colour = "#1B2A41", linewidth = 0.9) +
geom_segment(aes(x = 0, xend = Residual, yend = Provinsi), linewidth = 1) +
geom_point(size = 3) +
facet_wrap(~ Model, scales = "free_x") +
scale_colour_manual(values = pal_wilayah) +
scale_x_continuous(labels = fmt_id) +
labs(
title = "Residual per Provinsi terhadap Garis Nol",
subtitle = "Residual positif dan negatif tersebar di kedua sisi garis nol dan saling meniadakan",
x = "Residual", y = NULL, colour = "Wilayah",
caption = "Sumber: olahan data Pusiknas Polri"
) +
theme_crime() +
theme(
panel.grid.major.y = element_blank(),
strip.text = element_text(face = "bold", size = 13, colour = "#1B2A41")
)
cat('<div class="callout"><b>Kesimpulan sementara:</b><br>')
Kesimpulan sementara:
for (i in seq_len(nrow(rata_num))) {
nol <- abs(rata_num$rata[i]) < 1e-8 * rata_num$sd_res[i]
cat(sprintf("• Model <b>%s</b>: rata-rata residual = %s, %s.<br>",
rata_num$Model[i], sci(rata_num$rata[i]),
if (nol) "<b>dapat dianggap nol</b>" else "<b>perlu diperiksa kembali</b>"))
}
• Model Y mentah (Jumlah): rata-rata residual = -2,49e-14,
dapat dianggap nol.
• Model ln(Y): rata-rata residual =
-5,24e-17, dapat dianggap nol.
cat("Asumsi ini terpenuhi secara matematis pada model dengan intercept.</div>")
Asumsi ini terpenuhi secara matematis pada model dengan intercept.