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)

1 Pendahuluan

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).

2 Tujuan Analisis

  • Menggambarkan jumlah kejahatan berdasarkan provinsi.
  • Membandingkan jumlah kejahatan berdasarkan kelompok wilayah.
  • Memeriksa asumsi ANOVA sebelum menarik kesimpulan statistik.

3 Data dan Variabel

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.

4 Pengujian Asumsi ANOVA

4.1 Independensi Pengamatan (Independence)

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.

4.1.1 Struktur pengamatan

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.

4.1.2 Indikasi keterkaitan 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.

4.1.3 Ringkasan penilaian independensi

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.

4.2 Normalitas Galat (Normality)

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:

  • \(H_0\): residual berdistribusi normal
  • \(H_1\): residual tidak berdistribusi normal
  • Taraf signifikansi \(\alpha = 0{,}05\)

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).

4.2.1 Model dan residual

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

4.2.2 Uji formal normalitas

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)

4.2.3 Normal Q-Q Plot

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"))

4.2.4 Histogram dan kurva normal

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"))

4.2.5 Ringkasan penilaian normalitas

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("&bull; 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.

4.3 Homogenitas Varians (Homoscedasticity)

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:

  • \(H_0\): \(\sigma_1^2 = \sigma_2^2 = \cdots = \sigma_6^2\) (varians antarwilayah homogen)
  • \(H_1\): minimal ada satu wilayah dengan varians yang berbeda
  • Taraf signifikansi \(\alpha = 0{,}05\)

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).

4.3.1 Varians per kelompok wilayah

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.

4.3.2 Uji formal homogenitas varians

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)

4.3.3 Visualisasi homogenitas varians

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"))

4.3.4 Ringkasan penilaian homogenitas varians

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("&bull; 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.

4.4 Efek Aditif (Additivity)

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).

4.4.1 Dekomposisi efek perlakuan

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"))

4.4.2 Hubungan rata-rata dan simpangan baku

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()

4.4.3 Uji Box-Cox untuk skala yang aditif

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()

4.4.4 Residual terhadap nilai prediksi

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.

4.4.5 Ringkasan penilaian efek aditif

nol_dalam_ci <- ci_lam[1] <= 0 & 0 <= ci_lam[2]

cat('<div class="callout"><b>Kesimpulan sementara:</b><br>')

Kesimpulan sementara:

cat(sprintf("&bull; 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("&bull; Box-Cox: &lambda; optimal = <b>%.2f</b>, selang kepercayaan 95%% = [%.2f; %.2f]; &lambda; = 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.

4.5 Residual Memiliki Rataan Nol

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:

  • \(H_0\): \(E(\varepsilon) = 0\)
  • \(H_1\): \(E(\varepsilon) \neq 0\)
  • Taraf signifikansi \(\alpha = 0{,}05\)

Pemeriksaan dilakukan pada Y mentah dan ln(Y).

4.5.1 Jumlah dan rata-rata residual

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.

4.5.2 Residual positif dan negatif saling meniadakan

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.

4.5.3 Rata-rata residual per kelompok wilayah

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.

4.5.4 Uji-t satu sampel pada residual

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.

4.5.5 Visualisasi residual terhadap garis nol

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")
  )

4.5.6 Ringkasan penilaian rata-rata residual

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("&bull; 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.