1 Rumusan Masalah dan Tujuan Penelitian

Catatan perubahan dari proposal:
Akar Manis dan Kemangi tidak terdapat dalam data pengujian aktual.
Data aktual terdiri dari 3 ekstrak × 3 ulangan: Serai, Kenikir, dan Dukuh.

1.1 Rumusan Masalah

1.1.1 Daftar RM

  1. Bagaimana aktivitas repelan ekstrak serai, kenikir, dan daun duku terhadap semut hitam berdasarkan jumlah semut yang mendekati area perlakuan pada setiap interval waktu?
  2. Ekstrak tumbuhan lokal mana yang memiliki persentase repellency tertinggi terhadap semut hitam?
  3. Bagaimana pengaruh waktu terhadap pola aktivitas repelan masing-masing ekstrak selama 60 menit pengamatan?
  4. Apakah terdapat perbedaan signifikan antar ekstrak dalam efek repelan berdasarkan uji statistik non-parametrik?

1.1.2 Perubahan dari Proposal

Komponen Proposal (Rencana) Final (Aktual)
Ekstrak diuji Kenikir, Kemangi, Serai, Duku, Akar Manis Kenikir, Serai, Dukuh (3 ekstrak)
Jumlah ulangan Tidak spesifik 3 ulangan per ekstrak
Kemangi Ada di RM Dihapus (tidak ada di data)
Akar Manis Attractant control Dihapus (tidak ada di data)

1.2 Tujuan Penelitian

  1. Mengetahui aktivitas repelan ekstrak serai, kenikir, dan daun duku melalui pola perilaku semut hitam pada behavioral avoidance test.
  2. Menentukan ekstrak yang paling efektif sebagai bahan aktif repelan dalam formulasi gel.
  3. Menganalisis pengaruh waktu terhadap konsistensi efek repelan masing-masing ekstrak.
  4. Menguji secara statistik apakah perbedaan antar ekstrak signifikan menggunakan uji Kruskal-Wallis dan post-hoc Dunn.

2 Persiapan: Install dan Muat Package

# Jalankan SATU KALI saja, lalu beri tanda # kembali
# install.packages("tidyverse")
# install.packages("kableExtra")
# install.packages("scales")
# install.packages("RColorBrewer")
# install.packages("FSA")
# install.packages("rstatix")
# install.packages("ggpubr")
# install.packages("patchwork")
# install.packages("car")
library(tidyverse)
library(knitr)
library(kableExtra)
library(scales)
library(RColorBrewer)
library(FSA)
library(rstatix)
library(ggpubr)
library(patchwork)
library(car)

3 Input Data (3 Ulangan)

# ── Data dari file: Data_uji_semut_ulang - Sheet1.xlsx ──────────────
#
# Struktur data: 3 ekstrak × 3 ulangan × 5 titik waktu
# Nilai = jumlah semut yang MENDEKATI area ekstrak
# Makin kecil → semut menghindar → repelan lebih kuat
# Total semut per pengujian = 12 ekor

total_semut <- 12

# Ulangan ke-1
ulangan1 <- data.frame(
  Ulangan  = 1,
  Ekstrak  = c("Serai", "Kenikir", "Dukuh"),
  Menit_0  = c(12, 12, 12),
  Menit_15 = c(0,  1,  2),
  Menit_30 = c(0,  1,  0),
  Menit_45 = c(0,  0,  0),
  Menit_60 = c(0,  0,  0),
  stringsAsFactors = FALSE
)

# Ulangan ke-2
ulangan2 <- data.frame(
  Ulangan  = 2,
  Ekstrak  = c("Serai", "Kenikir", "Dukuh"),
  Menit_0  = c(12, 12, 12),
  Menit_15 = c(1,  3,  3),
  Menit_30 = c(0,  0,  0),
  Menit_45 = c(0,  0,  0),
  Menit_60 = c(0,  0,  0),
  stringsAsFactors = FALSE
)

# Ulangan ke-3
ulangan3 <- data.frame(
  Ulangan  = 3,
  Ekstrak  = c("Serai", "Kenikir", "Dukuh"),
  Menit_0  = c(12, 12, 12),
  Menit_15 = c(0,  2,  3),
  Menit_30 = c(0,  0,  0),
  Menit_45 = c(0,  0,  0),
  Menit_60 = c(0,  0,  0),
  stringsAsFactors = FALSE
)

# Gabungkan semua ulangan
data_mentah <- bind_rows(ulangan1, ulangan2, ulangan3)

# Tampilkan tabel data mentah
data_mentah %>%
  kable(
    caption   = "Tabel 1. Data Mentah – Jumlah Semut Mendekati Area Ekstrak (3 Ulangan)",
    col.names = c("Ulangan", "Ekstrak", "Menit ke-0", "Menit ke-15",
                  "Menit ke-30", "Menit ke-45", "Menit ke-60")
  ) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered"),
                full_width = FALSE, position = "center") %>%
  pack_rows("Ulangan 1", 1, 3) %>%
  pack_rows("Ulangan 2", 4, 6) %>%
  pack_rows("Ulangan 3", 7, 9) %>%
  footnote(general = paste(
    "Nilai lebih rendah = efek repelan lebih kuat.",
    "Total semut = 12 ekor per pengujian."
  ))
Tabel 1. Data Mentah – Jumlah Semut Mendekati Area Ekstrak (3 Ulangan)
Ulangan Ekstrak Menit ke-0 Menit ke-15 Menit ke-30 Menit ke-45 Menit ke-60
Ulangan 1
1 Serai 12 0 0 0 0
1 Kenikir 12 1 1 0 0
1 Dukuh 12 2 0 0 0
Ulangan 2
2 Serai 12 1 0 0 0
2 Kenikir 12 3 0 0 0
2 Dukuh 12 3 0 0 0
Ulangan 3
3 Serai 12 0 0 0 0
3 Kenikir 12 2 0 0 0
3 Dukuh 12 3 0 0 0
Note:
Nilai lebih rendah = efek repelan lebih kuat. Total semut = 12 ekor per pengujian.
cat("╔══════════════════════════════════════════════════╗\n")
## ╔══════════════════════════════════════════════════╗
cat("║           CATATAN LAPANGAN PENTING              ║\n")
## ║           CATATAN LAPANGAN PENTING              ║
cat("╠══════════════════════════════════════════════════╣\n")
## ╠══════════════════════════════════════════════════╣
cat("║  Total semut per pengujian  : 12 ekor           ║\n")
## ║  Total semut per pengujian  : 12 ekor           ║
cat("║  Metode                     : Behavioral         ║\n")
## ║  Metode                     : Behavioral         ║
cat("║                               Avoidance Test    ║\n")
## ║                               Avoidance Test    ║
cat("║  Titik pengamatan           : t = 0,15,30,45,60 ║\n")
## ║  Titik pengamatan           : t = 0,15,30,45,60 ║
cat("║  Jumlah ulangan             : 3 ulangan         ║\n")
## ║  Jumlah ulangan             : 3 ulangan         ║
cat("║  Temuan tambahan            : Semua semut mati  ║\n")
## ║  Temuan tambahan            : Semua semut mati  ║
cat("║                               setelah 6 jam     ║\n")
## ║                               setelah 6 jam     ║
cat("╚══════════════════════════════════════════════════╝\n")
## ╚══════════════════════════════════════════════════╝

4 Transformasi Data: Format Panjang

# Ubah ke format panjang untuk analisis statistik dan visualisasi

data_panjang <- data_mentah %>%
  pivot_longer(
    cols      = starts_with("Menit"),
    names_to  = "Waktu_Label",
    values_to = "Jumlah_Semut"
  ) %>%
  mutate(
    Waktu          = as.numeric(str_extract(Waktu_Label, "\\d+")),
    Repellency_pct = round((1 - Jumlah_Semut / total_semut) * 100, 1),
    Ekstrak        = factor(Ekstrak, levels = c("Serai", "Kenikir", "Dukuh")),
    Ulangan        = factor(Ulangan)
  ) %>%
  select(Ulangan, Ekstrak, Waktu, Jumlah_Semut, Repellency_pct)

# Hanya data perlakuan (t > 0)
data_perlakuan <- data_panjang %>% filter(Waktu > 0)

# Preview
data_perlakuan %>%
  head(12) %>%
  kable(
    caption   = "Tabel 2. Data Format Panjang – Perlakuan (t = 15–60 menit)",
    col.names = c("Ulangan", "Ekstrak", "Waktu (menit)",
                  "Jumlah Semut (ekor)", "Repellency (%)")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)
Tabel 2. Data Format Panjang – Perlakuan (t = 15–60 menit)
Ulangan Ekstrak Waktu (menit) Jumlah Semut (ekor) Repellency (%)
1 Serai 15 0 100.0
1 Serai 30 0 100.0
1 Serai 45 0 100.0
1 Serai 60 0 100.0
1 Kenikir 15 1 91.7
1 Kenikir 30 1 91.7
1 Kenikir 45 0 100.0
1 Kenikir 60 0 100.0
1 Dukuh 15 2 83.3
1 Dukuh 30 0 100.0
1 Dukuh 45 0 100.0
1 Dukuh 60 0 100.0

5 Statistik Deskriptif

# Tabel repellency per ekstrak per titik waktu (rata-rata atas ulangan)
repellency_per_waktu <- data_perlakuan %>%
  group_by(Ekstrak, Waktu) %>%
  summarise(
    Rerata_Semut      = round(mean(Jumlah_Semut), 2),
    Rerata_Repellency = round(mean(Repellency_pct), 1),
    .groups = "drop"
  )

repellency_wide <- repellency_per_waktu %>%
  select(Ekstrak, Waktu, Rerata_Repellency) %>%
  pivot_wider(names_from = Waktu, values_from = Rerata_Repellency,
              names_prefix = "t") %>%
  mutate(
    Rerata_Total = round(rowMeans(select(., starts_with("t")), na.rm = TRUE), 1),
    Peringkat    = rank(-Rerata_Total, ties.method = "min")
  ) %>%
  arrange(Peringkat)

repellency_wide %>%
  kable(
    caption   = "Tabel 3. Rerata Persentase Repellency (%) per Ekstrak per Titik Waktu",
    col.names = c("Ekstrak", "t=15", "t=30", "t=45", "t=60",
                  "Rerata Total (%)", "Peringkat")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = FALSE) %>%
  column_spec(6, bold = TRUE, color = "white", background = "#2e5d8e") %>%
  column_spec(7, bold = TRUE) %>%
  row_spec(1, background = "#d4edda") %>%
  footnote(general = "Rumus: %Repellency = (1 – n_mendekati / n_total) × 100%. Nilai merupakan rerata 3 ulangan.")
Tabel 3. Rerata Persentase Repellency (%) per Ekstrak per Titik Waktu
Ekstrak t=15 t=30 t=45 t=60 Rerata Total (%) Peringkat
Serai 97.2 100.0 100 100 99.3 1
Kenikir 83.3 97.2 100 100 95.1 2
Dukuh 77.8 100.0 100 100 94.4 3
Note:
Rumus: %Repellency = (1 – n_mendekati / n_total) × 100%. Nilai merupakan rerata 3 ulangan.
# Ringkasan statistik per ekstrak (pooled semua waktu perlakuan)
ringkasan <- data_perlakuan %>%
  group_by(Ekstrak) %>%
  summarise(
    n                     = n(),
    Total_Semut_Mendekati = sum(Jumlah_Semut),
    Rerata_Semut          = round(mean(Jumlah_Semut), 2),
    SD_Semut              = round(sd(Jumlah_Semut), 2),
    Median_Semut          = median(Jumlah_Semut),
    Rerata_Repellency     = round(mean(Repellency_pct), 1),
    .groups = "drop"
  ) %>%
  mutate(
    Kategori = case_when(
      Rerata_Repellency >= 95 ~ "Sangat Kuat ✅",
      Rerata_Repellency >= 85 ~ "Kuat ✅",
      TRUE                    ~ "Sedang ⚠️"
    )
  ) %>%
  arrange(desc(Rerata_Repellency))

ringkasan %>%
  kable(
    caption   = "Tabel 4. Ringkasan Statistik Deskriptif (t = 15–60 menit, pooled 3 ulangan)",
    col.names = c("Ekstrak", "n obs", "Total Semut\nMendekati",
                  "Rerata Semut", "SD", "Median",
                  "Rerata Repellency (%)", "Kategori")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = FALSE) %>%
  row_spec(1, background = "#d4edda") %>%
  footnote(general = "n obs = jumlah observasi per ekstrak (4 titik waktu × 3 ulangan = 12). SD = Standar Deviasi.")
Tabel 4. Ringkasan Statistik Deskriptif (t = 15–60 menit, pooled 3 ulangan)
Ekstrak n obs Total Semut Mendekat
Rerata Semu
Serai 12 1 0.08 0.29 0 99.3 Sangat Kuat ✅ |
Kenikir 12 7 0.58 1.00 0 95.1 Sangat Kuat ✅ |
Dukuh 12 8 0.67 1.23 0 94.4 Kuat ✅ |
Note:
n obs = jumlah observasi per ekstrak (4 titik waktu × 3 ulangan = 12). SD = Standar Deviasi.

6 Visualisasi Data

6.1 Grafik 1: Tren Jumlah Semut Sepanjang Waktu (Per Ulangan)

warna <- c("Serai"   = "#27ae60",
           "Kenikir" = "#2980b9",
           "Dukuh"   = "#e67e22")

p_ulangan <- ggplot(
  data_panjang,
  aes(x = Waktu, y = Jumlah_Semut, color = Ekstrak, group = Ekstrak)
) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  facet_wrap(~ paste("Ulangan", Ulangan), ncol = 3) +
  scale_color_manual(values = warna) +
  scale_x_continuous(breaks = c(0, 15, 30, 45, 60)) +
  scale_y_continuous(breaks = 0:12, limits = c(-0.3, 13)) +
  labs(
    title    = "Jumlah Semut Mendekati Area Ekstrak per Ulangan",
    subtitle = "Total semut = 12 ekor | Garis turun ke 0 = repelan kuat",
    x = "Waktu (menit)", y = "Jumlah Semut (ekor)", color = "Ekstrak"
  ) +
  theme_classic(base_size = 11) +
  theme(
    plot.title    = element_text(face = "bold"),
    plot.subtitle = element_text(color = "gray40"),
    strip.text    = element_text(face = "bold"),
    panel.grid.major.y = element_line(color = "gray92")
  )

print(p_ulangan)
Gambar 1. Tren jumlah semut per ulangan. Tiap panel = satu ulangan.

Gambar 1. Tren jumlah semut per ulangan. Tiap panel = satu ulangan.

6.2 Grafik 2: Tren Rerata Jumlah Semut (Pooled 3 Ulangan)

data_rerata <- data_panjang %>%
  group_by(Ekstrak, Waktu) %>%
  summarise(
    Mean_Semut = mean(Jumlah_Semut),
    SD_Semut   = sd(Jumlah_Semut),
    .groups    = "drop"
  )

p_rerata <- ggplot(data_rerata,
                   aes(x = Waktu, y = Mean_Semut,
                       color = Ekstrak, group = Ekstrak)) +
  geom_ribbon(aes(ymin = pmax(Mean_Semut - SD_Semut, 0),
                  ymax = Mean_Semut + SD_Semut,
                  fill = Ekstrak), alpha = 0.15, color = NA) +
  geom_line(linewidth = 1.3) +
  geom_point(size = 3.5) +
  geom_hline(yintercept = 0, linetype = "dashed",
             color = "gray60", linewidth = 0.5) +
  scale_color_manual(values = warna) +
  scale_fill_manual(values = warna) +
  scale_x_continuous(breaks = c(0, 15, 30, 45, 60),
                     labels = c("t=0\n(baseline)", "t=15", "t=30", "t=45", "t=60")) +
  scale_y_continuous(breaks = 0:12, limits = c(-0.5, 13)) +
  labs(
    title    = "Rerata Jumlah Semut Mendekati Area Ekstrak",
    subtitle = "Rerata ± SD dari 3 ulangan | Makin rendah = repelan makin kuat",
    x = "Waktu Pengamatan (menit)", y = "Rerata Jumlah Semut (ekor)",
    color = "Ekstrak", fill = "Ekstrak"
  ) +
  theme_classic(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold"),
    plot.subtitle = element_text(color = "gray40"),
    panel.grid.major.y = element_line(color = "gray92")
  )

print(p_rerata)
Gambar 2. Rerata jumlah semut ± SD dari 3 ulangan sepanjang waktu.

Gambar 2. Rerata jumlah semut ± SD dari 3 ulangan sepanjang waktu.

6.3 Grafik 3: Perbandingan Repellency (Bar Chart ± SD)

data_bar_rep <- data_perlakuan %>%
  group_by(Ekstrak) %>%
  summarise(
    Mean_Rep = mean(Repellency_pct),
    SD_Rep   = sd(Repellency_pct),
    .groups  = "drop"
  ) %>%
  arrange(desc(Mean_Rep)) %>%
  mutate(Label = paste0(round(Mean_Rep, 1), "%"))

p_bar <- ggplot(data_bar_rep,
                aes(x = reorder(Ekstrak, -Mean_Rep),
                    y = Mean_Rep, fill = Ekstrak)) +
  geom_col(width = 0.6, show.legend = FALSE) +
  geom_errorbar(aes(ymin = pmax(Mean_Rep - SD_Rep, 0),
                    ymax = pmin(Mean_Rep + SD_Rep, 100)),
                width = 0.2, linewidth = 0.8) +
  geom_text(aes(label = Label), vjust = -1.8, fontface = "bold", size = 4.5) +
  geom_hline(yintercept = 95, linetype = "dashed",
             color = "darkgreen", linewidth = 0.7, alpha = 0.7) +
  annotate("text", x = 0.5, y = 96.5, label = "95% threshold",
           color = "darkgreen", size = 3, hjust = 0) +
  scale_fill_manual(values = warna) +
  scale_y_continuous(limits = c(0, 115),
                     labels = label_percent(scale = 1)) +
  labs(
    title    = "Rerata Persentase Repellency per Ekstrak",
    subtitle = "Rerata ± SD dari 3 ulangan (t = 15–60 menit)",
    x = "Ekstrak", y = "Rerata Repellency (%)"
  ) +
  theme_classic(base_size = 12) +
  theme(
    plot.title  = element_text(face = "bold"),
    axis.text.x = element_text(face = "bold")
  )

print(p_bar)
Gambar 3. Rerata persentase repellency per ekstrak dengan error bar SD (3 ulangan).

Gambar 3. Rerata persentase repellency per ekstrak dengan error bar SD (3 ulangan).

6.4 Grafik 4: Heatmap Repellency per Ulangan

data_heat <- data_perlakuan %>%
  group_by(Ulangan, Ekstrak) %>%
  summarise(
    Mean_Repellency = round(mean(Repellency_pct), 1),
    .groups = "drop"
  ) %>%
  mutate(Label_U = paste0("Ulangan ", Ulangan))

p_heat <- ggplot(data_heat,
                 aes(x = Label_U, y = Ekstrak, fill = Mean_Repellency)) +
  geom_tile(color = "white", linewidth = 1.2) +
  geom_text(aes(label = paste0(Mean_Repellency, "%")),
            fontface = "bold", size = 4.2) +
  scale_fill_gradient2(low = "#e74c3c", mid = "#f39c12",
                       high = "#27ae60", midpoint = 80,
                       limits = c(0, 100), name = "Repellency (%)") +
  labs(
    title    = "Heatmap Rerata Repellency per Ulangan",
    subtitle = "Hijau = efek repelan kuat | Merah = kurang efektif",
    x = "Ulangan", y = "Ekstrak"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title  = element_text(face = "bold"),
    panel.grid  = element_blank(),
    axis.text   = element_text(face = "bold")
  )

print(p_heat)
Gambar 4. Heatmap repellency per ekstrak per ulangan. Hijau = repelan kuat.

Gambar 4. Heatmap repellency per ekstrak per ulangan. Hijau = repelan kuat.

6.5 Grafik 5: Boxplot Distribusi Jumlah Semut

p_box <- ggplot(data_perlakuan,
                aes(x = Ekstrak, y = Jumlah_Semut, fill = Ekstrak)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7, width = 0.5) +
  geom_jitter(aes(color = Ulangan), width = 0.15, size = 3, alpha = 0.85) +
  scale_fill_manual(values = warna, guide = "none") +
  scale_color_brewer(palette = "Dark2") +
  scale_y_continuous(breaks = 0:12) +
  labs(
    title    = "Distribusi Jumlah Semut Mendekati Area Ekstrak",
    subtitle = "Titik = observasi individual, warna = ulangan | t = 15–60 menit",
    x = "Ekstrak", y = "Jumlah Semut (ekor)", color = "Ulangan"
  ) +
  theme_classic(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold"),
    plot.subtitle = element_text(color = "gray40"),
    legend.position = "right"
  )

print(p_box)
Gambar 5. Distribusi jumlah semut mendekati per ekstrak (pooled waktu perlakuan + ulangan).

Gambar 5. Distribusi jumlah semut mendekati per ekstrak (pooled waktu perlakuan + ulangan).


7 Mengapa Tidak Menggunakan ANOVA?

cat("
## Perbandingan ANOVA vs Kruskal-Wallis

| Kriteria | ANOVA (Parametrik) | Kruskal-Wallis (Non-parametrik) |
|---|---|---|
| Asumsi distribusi | **Normal** (wajib) | **Bebas** (tidak butuh normal) |
| Tipe data | Kontinu | Kontinu / ordinal / count |
| Nilai yang dibandingkan | Mean (rata-rata) | **Median (peringkat/rank)** |
| Sensitif outlier | Ya | Tidak |
| Cocok untuk n kecil | Kurang | **Ya** |

### Kenapa data ini TIDAK cocok untuk ANOVA?

**Alasan 1 — Data berupa count (hitungan), bukan data kontinu**  
Data adalah jumlah semut (0, 1, 2, 3…). Data hitungan umumnya berdistribusi Poisson,
bukan normal.

**Alasan 2 — Banyak nilai nol dan variansi rendah**  
Serai banyak bernilai 0, sehingga distribusi sangat tidak simetris (right-skewed).
Shapiro-Wilk bisa gagal karena nilai identik.

**Alasan 3 — Ukuran sampel kecil per kelompok (n = 12)**  
n per kelompok = 4 titik waktu × 3 ulangan = 12. Pada n kecil, Central Limit
Theorem belum cukup kuat untuk 'menormalisasi' distribusi count.

**Kesimpulan: Kruskal-Wallis adalah pilihan yang tepat untuk data ini.**
")

7.1 Perbandingan ANOVA vs Kruskal-Wallis

Kriteria ANOVA (Parametrik) Kruskal-Wallis (Non-parametrik)
Asumsi distribusi Normal (wajib) Bebas (tidak butuh normal)
Tipe data Kontinu Kontinu / ordinal / count
Nilai yang dibandingkan Mean (rata-rata) Median (peringkat/rank)
Sensitif outlier Ya Tidak
Cocok untuk n kecil Kurang Ya

7.1.1 Kenapa data ini TIDAK cocok untuk ANOVA?

Alasan 1 — Data berupa count (hitungan), bukan data kontinu
Data adalah jumlah semut (0, 1, 2, 3…). Data hitungan umumnya berdistribusi Poisson, bukan normal.

Alasan 2 — Banyak nilai nol dan variansi rendah
Serai banyak bernilai 0, sehingga distribusi sangat tidak simetris (right-skewed). Shapiro-Wilk bisa gagal karena nilai identik.

Alasan 3 — Ukuran sampel kecil per kelompok (n = 12)
n per kelompok = 4 titik waktu × 3 ulangan = 12. Pada n kecil, Central Limit Theorem belum cukup kuat untuk ‘menormalisasi’ distribusi count.

Kesimpulan: Kruskal-Wallis adalah pilihan yang tepat untuk data ini.


8 Uji Asumsi

8.1 Uji Normalitas (Shapiro-Wilk)

# H0: Data berdistribusi normal
# H1: Data tidak normal → pakai non-parametrik jika p < 0.05

shapiro_aman <- function(x) {
  tryCatch({
    hasil <- shapiro.test(x)
    list(W = round(hasil$statistic, 4),
         pvalue = round(hasil$p.value, 4),
         catatan = "OK")
  }, error = function(e) {
    list(W = NA, pvalue = NA,
         catatan = paste("Error:", e$message))
  })
}

hasil_shapiro <- data_perlakuan %>%
  group_by(Ekstrak) %>%
  summarise(
    n     = n(),
    nilai = list(Jumlah_Semut),
    .groups = "drop"
  ) %>%
  mutate(
    shapiro      = map(nilai, shapiro_aman),
    W            = map_dbl(shapiro, ~ .x$W),
    p_value      = map_dbl(shapiro, ~ .x$pvalue),
    Catatan      = map_chr(shapiro, ~ .x$catatan),
    Interpretasi = case_when(
      is.na(p_value) ~ "⚠️ Tidak bisa dihitung (nilai identik)",
      p_value < 0.05 ~ "❌ Tidak Normal → pakai non-parametrik",
      TRUE           ~ "✅ Normal"
    )
  ) %>%
  select(Ekstrak, n, W, p_value, Catatan, Interpretasi)

hasil_shapiro %>%
  kable(
    caption   = "Tabel 5. Hasil Uji Normalitas Shapiro-Wilk",
    col.names = c("Ekstrak", "n", "W Statistik", "p-value",
                  "Catatan Teknis", "Interpretasi")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = FALSE) %>%
  column_spec(6, bold = TRUE) %>%
  footnote(general = paste(
    "Jika semua nilai identik (seperti semua = 0),",
    "Shapiro-Wilk tidak dapat dihitung → mendukung pilihan non-parametrik."
  ))
Tabel 5. Hasil Uji Normalitas Shapiro-Wilk
Ekstrak n W Statistik p-value Catatan Teknis Interpretasi
Serai 12 0.3269 0e+00 OK ❌ Tidak Normal → pakai non-parametrik |
Kenikir 12 0.6682 4e-04 OK ❌ Tidak Normal → pakai non-parametrik |
Dukuh 12 0.5800 1e-04 OK ❌ Tidak Normal → pakai non-parametrik |
Note:
Jika semua nilai identik (seperti semua = 0), Shapiro-Wilk tidak dapat dihitung → mendukung pilihan non-parametrik.

8.2 Uji Homogenitas Varians (Levene’s Test)

# H0: Varians antar kelompok homogen
# H1: Varians tidak homogen (p < 0.05 → heterogen → perkuat pilihan non-parametrik)

levene_hasil <- tryCatch({
  test <- leveneTest(Jumlah_Semut ~ Ekstrak, data = data_perlakuan)
  cat("Hasil Levene's Test:\n")
  print(test)
  pval <- test$`Pr(>F)`[1]
  cat("\nKesimpulan:\n")
  if (!is.na(pval) && pval < 0.05) {
    cat("→ p =", round(pval, 4), "< 0.05 → Varians TIDAK homogen\n")
    cat("→ Semakin memperkuat pilihan menggunakan uji non-parametrik.\n")
  } else {
    cat("→ p ≥ 0.05 → Varians homogen. Namun berdasarkan sifat data\n")
    cat("  (count data, n kecil, normalitas tidak terpenuhi),\n")
    cat("  Kruskal-Wallis tetap lebih tepat digunakan.\n")
  }
}, error = function(e) {
  cat("Levene's test tidak bisa dijalankan:", e$message, "\n")
  cat("→ Tetap gunakan Kruskal-Wallis berdasarkan sifat data.\n")
})
## Hasil Levene's Test:
## Levene's Test for Homogeneity of Variance (center = median)
##       Df F value Pr(>F)
## group  2   1.383  0.265
##       33               
## 
## Kesimpulan:
## → p ≥ 0.05 → Varians homogen. Namun berdasarkan sifat data
##   (count data, n kecil, normalitas tidak terpenuhi),
##   Kruskal-Wallis tetap lebih tepat digunakan.

9 Uji Statistik: Kruskal-Wallis

# ── UJI KRUSKAL-WALLIS ────────────────────────────────────────────────
#
# Unit analisis: data_perlakuan (pooled semua waktu + ulangan)
# Variabel respon: Jumlah_Semut (jumlah semut mendekati)
# Faktor: Ekstrak (Serai, Kenikir, Dukuh)
#
# H0: Median jumlah semut sama di semua ekstrak
# H1: Minimal satu ekstrak berbeda secara signifikan
# Keputusan: Tolak H0 jika p-value < 0.05

kw <- kruskal.test(Jumlah_Semut ~ Ekstrak, data = data_perlakuan)

cat("╔═══════════════════════════════════════════════════╗\n")
## ╔═══════════════════════════════════════════════════╗
cat("║          HASIL UJI KRUSKAL-WALLIS                ║\n")
## ║          HASIL UJI KRUSKAL-WALLIS                ║
cat("╠═══════════════════════════════════════════════════╣\n")
## ╠═══════════════════════════════════════════════════╣
cat(sprintf("║  Chi-squared (H)   : %-26.4f║\n", kw$statistic))
## ║  Chi-squared (H)   : 2.3286                    ║
cat(sprintf("║  Derajat bebas (df): %-26d║\n", kw$parameter))
## ║  Derajat bebas (df): 2                         ║
cat(sprintf("║  p-value           : %-26.4f║\n", kw$p.value))
## ║  p-value           : 0.3121                    ║
cat("╠═══════════════════════════════════════════════════╣\n")
## ╠═══════════════════════════════════════════════════╣
if (kw$p.value < 0.05) {
  cat("║  Keputusan : TOLAK H0                            ║\n")
  cat("║  ✅ Ada perbedaan signifikan antar ekstrak        ║\n")
  cat("║  → Lanjutkan ke Uji Post-Hoc Dunn               ║\n")
} else {
  cat("║  Keputusan : GAGAL TOLAK H0                      ║\n")
  cat("║  ⚠️  Tidak ada perbedaan signifikan secara stat   ║\n")
  cat("║  → Lihat efek deskriptif dan ukuran efek         ║\n")
}
## ║  Keputusan : GAGAL TOLAK H0                      ║
## ║  ⚠️  Tidak ada perbedaan signifikan secara stat   ║
## ║  → Lihat efek deskriptif dan ukuran efek         ║
cat("╚═══════════════════════════════════════════════════╝\n")
## ╚═══════════════════════════════════════════════════╝
# ── Effect Size: Eta-squared (η²) ──────────────────────────────────
# η² = H / (n_total - 1)
# < 0.06 = kecil | 0.06–0.14 = sedang | > 0.14 = besar

n_tot  <- nrow(data_perlakuan)
eta_sq <- kw$statistic / (n_tot - 1)

cat("\nEffect Size (Eta-squared / η²):\n")
## 
## Effect Size (Eta-squared / η²):
cat("  n total  =", n_tot, "\n")
##   n total  = 36
cat("  H        =", round(kw$statistic, 4), "\n")
##   H        = 2.3286
cat("  η²       =", round(eta_sq, 4), "\n")
##   η²       = 0.0665
cat("  Interpretasi:", ifelse(eta_sq > 0.14, "BESAR (η² > 0.14)",
                      ifelse(eta_sq > 0.06, "SEDANG (0.06 < η² ≤ 0.14)",
                             "KECIL (η² ≤ 0.06)")), "\n")
##   Interpretasi: SEDANG (0.06 < η² ≤ 0.14)
# ── Verifikasi dengan rstatix ──────────────────────────────────────
kw_rstatix <- data_perlakuan %>%
  kruskal_test(Jumlah_Semut ~ Ekstrak)

kw_rstatix %>%
  kable(
    caption = "Tabel 6. Hasil Uji Kruskal-Wallis (via rstatix)",
    col.names = c("Variabel Y", "Faktor", "n", "df", "H Statistik", "p-value")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = FALSE) %>%
  footnote(general = "H = statistik Kruskal-Wallis. p < 0.05 = ada perbedaan signifikan antar kelompok.")
Tabel 6. Hasil Uji Kruskal-Wallis (via rstatix)
Variabel Y Faktor n df H Statistik p-value
Jumlah_Semut 36 2.328646 2 0.312 Kruskal-Wallis
Note:
H = statistik Kruskal-Wallis. p < 0.05 = ada perbedaan signifikan antar kelompok.

10 Uji Post-Hoc: Dunn Test

# ── UJI POST-HOC DUNN ────────────────────────────────────────────────
#
# Digunakan setelah Kruskal-Wallis untuk mengetahui:
# "Pasangan mana yang berbeda nyata?"
#
# Koreksi Bonferroni: menyesuaikan alpha karena membandingkan banyak pasangan
# (tanpa koreksi → risiko false positive meningkat)

dunn <- dunnTest(Jumlah_Semut ~ Ekstrak,
                 data   = data_perlakuan,
                 method = "bonferroni")

dunn_tabel <- dunn$res %>%
  as.data.frame() %>%
  mutate(
    Grup1      = sapply(strsplit(Comparison, " - "), `[`, 1),
    Grup2      = sapply(strsplit(Comparison, " - "), `[`, 2),
    Z          = round(Z, 3),
    P.unadj    = round(P.unadj, 4),
    P.adj      = round(P.adj, 4),
    Signifikan = ifelse(P.adj < 0.05, "✅ Ya", "❌ Tidak"),
    Keterangan = case_when(
      P.adj >= 0.05  ~ "Tidak berbeda nyata",
      Z > 0 ~ paste(Grup2, "lebih efektif dari", Grup1),
      TRUE  ~ paste(Grup1, "lebih efektif dari", Grup2)
    )
  ) %>%
  select(Comparison, Z, P.unadj, P.adj, Signifikan, Keterangan)

dunn_tabel %>%
  kable(
    caption   = "Tabel 7. Hasil Uji Post-Hoc Dunn (Koreksi Bonferroni)",
    col.names = c("Perbandingan Pasangan", "Z Statistik",
                  "p-value (asli)", "p-value (Bonferroni)",
                  "Beda Nyata?", "Keterangan")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = FALSE) %>%
  column_spec(5, bold = TRUE) %>%
  footnote(
    general  = "Beda nyata jika p.adj < 0.05 (koreksi Bonferroni).",
    alphabet = c(
      "Z positif pada 'A - B' berarti A memiliki lebih banyak semut mendekati → B lebih efektif sebagai repelan."
    )
  )
Tabel 7. Hasil Uji Post-Hoc Dunn (Koreksi Bonferroni)
Perbandingan Pasangan Z Statistik p-value (asli) p-value (Bonferroni) Beda Nyata? Keterangan
Serai - Kenikir -1.439 0.1501 0.4504 ❌ Tidak | idak berbeda nyata |
Serai - Dukuh -1.159 0.2464 0.7391 ❌ Tidak | idak berbeda nyata |
Kenikir - Dukuh 0.280 0.7796 1.0000 ❌ Tidak | idak berbeda nyata |
Note:
Beda nyata jika p.adj < 0.05 (koreksi Bonferroni).
a Z positif pada ‘A - B’ berarti A memiliki lebih banyak semut mendekati → B lebih efektif sebagai repelan.
# ── Compact Letter Display (CLD) untuk notasi huruf ──────────────────
# Notasi: kelompok dengan huruf yang sama = TIDAK berbeda nyata

# Ambil p.adj dari Dunn untuk membuat matriks perbandingan
pasangan <- dunn_tabel %>%
  select(Comparison, P.adj)

cat("Notasi Kelompok (berdasarkan p.adj Dunn-Bonferroni):\n\n")
## Notasi Kelompok (berdasarkan p.adj Dunn-Bonferroni):
# Buat tabel CLD manual berdasarkan signifikansi
ekst <- levels(data_perlakuan$Ekstrak)
cld_df <- data.frame(
  Ekstrak         = ekst,
  Rerata_Repellency = sapply(ekst, function(e) {
    round(mean(data_perlakuan$Repellency_pct[data_perlakuan$Ekstrak == e]), 1)
  })
) %>% arrange(desc(Rerata_Repellency))

# Tentukan notasi huruf berdasarkan hasil Dunn
# (lakukan secara manual karena tidak semua library CLD kompatibel dengan dunnTest)
cat("Tabel Ringkasan:\n")
## Tabel Ringkasan:
cat(sprintf("%-12s | %-20s | %s\n", "Ekstrak", "Rerata Repellency", "Keterangan"))
## Ekstrak      | Rerata Repellency    | Keterangan
cat(rep("-", 55), "\n", sep="")
## -------------------------------------------------------
for (i in seq_len(nrow(cld_df))) {
  cat(sprintf("%-12s | %-20s | %s\n",
              cld_df$Ekstrak[i],
              paste0(cld_df$Rerata_Repellency[i], "%"),
              ifelse(i == 1, "Tertinggi", paste0("Peringkat ke-", i))))
}
## Serai        | 99.3%                | Tertinggi
## Kenikir      | 95.1%                | Peringkat ke-2
## Dukuh        | 94.4%                | Peringkat ke-3

10.1 Grafik Post-Hoc: Visualisasi Signifikansi

# Siapkan pasangan signifikan untuk ggpubr
dunn_stat <- data_perlakuan %>%
  dunn_test(Jumlah_Semut ~ Ekstrak, p.adjust.method = "bonferroni") %>%
  add_xy_position(x = "Ekstrak")

p_posthoc <- ggplot(data_perlakuan,
                    aes(x = Ekstrak, y = Jumlah_Semut, fill = Ekstrak)) +
  geom_boxplot(outlier.shape = NA, alpha = 0.7, width = 0.5) +
  geom_jitter(width = 0.12, size = 2.5, alpha = 0.7, color = "gray30") +
  stat_pvalue_manual(dunn_stat,
                     label = "p.adj.signif",
                     tip.length = 0.01,
                     hide.ns = FALSE) +
  scale_fill_manual(values = warna, guide = "none") +
  scale_y_continuous(breaks = 0:12,
                     limits = c(-0.5, max(data_perlakuan$Jumlah_Semut) + 4)) +
  labs(
    title    = "Uji Post-Hoc Dunn – Perbandingan Antar Ekstrak",
    subtitle = "Anotasi: ns = tidak signifikan | * p<0.05 | ** p<0.01 | *** p<0.001",
    x = "Ekstrak", y = "Jumlah Semut Mendekati (ekor)"
  ) +
  theme_classic(base_size = 12) +
  theme(
    plot.title    = element_text(face = "bold"),
    plot.subtitle = element_text(color = "gray40"),
    axis.text.x   = element_text(face = "bold")
  )

print(p_posthoc)
Gambar 6. Boxplot jumlah semut per ekstrak dengan anotasi signifikansi uji Dunn.

Gambar 6. Boxplot jumlah semut per ekstrak dengan anotasi signifikansi uji Dunn.


11 Analisis Mortalitas (Temuan Tambahan)

cat("TEMUAN TAMBAHAN: MORTALITAS\n")
## TEMUAN TAMBAHAN: MORTALITAS
cat("─────────────────────────────────────────────────────\n")
## ─────────────────────────────────────────────────────
cat("Seluruh 12 ekor semut mati dalam 6 jam pengamatan\n")
## Seluruh 12 ekor semut mati dalam 6 jam pengamatan
cat("% Mortalitas: 100% (semua ulangan)\n\n")
## % Mortalitas: 100% (semua ulangan)
cat("Implikasi ilmiah:\n")
## Implikasi ilmiah:
cat("1. Ekstrak memiliki DUAL ACTION:\n")
## 1. Ekstrak memiliki DUAL ACTION:
cat("   → Efek repelan  : jangka pendek (terlihat dalam 60 menit)\n")
##    → Efek repelan  : jangka pendek (terlihat dalam 60 menit)
cat("   → Efek insektisidal: jangka panjang (slow-acting toxicity, 6 jam)\n\n")
##    → Efek insektisidal: jangka panjang (slow-acting toxicity, 6 jam)
cat("2. Kandungan bioaktif yang diduga bertanggung jawab:\n")
## 2. Kandungan bioaktif yang diduga bertanggung jawab:
cat("   → Serai   : Citronellal, geraniol → gangguan sistem saraf serangga\n")
##    → Serai   : Citronellal, geraniol → gangguan sistem saraf serangga
cat("   → Kenikir : Flavonoid → inhibitor sistem pernapasan serangga\n")
##    → Kenikir : Flavonoid → inhibitor sistem pernapasan serangga
cat("   → Dukuh   : Triterpenoid → gangguan metabolisme serangga\n\n")
##    → Dukuh   : Triterpenoid → gangguan metabolisme serangga
cat("3. Rekomendasi penelitian lanjutan:\n")
## 3. Rekomendasi penelitian lanjutan:
cat("   → Uji formal LC50 & LT50 menggunakan Analisis Probit\n")
##    → Uji formal LC50 & LT50 menggunakan Analisis Probit
cat("   → Tambah ulangan ≥ 5 untuk meningkatkan kekuatan uji statistik\n")
##    → Tambah ulangan ≥ 5 untuk meningkatkan kekuatan uji statistik

12 Rangkuman dan Peringkat Akhir

peringkat <- data.frame(
  Peringkat     = c(1, 2, 3),
  Ekstrak       = c("Serai", "Kenikir", "Dukuh"),
  Repellency    = c(
    paste0(round(mean(data_perlakuan$Repellency_pct[data_perlakuan$Ekstrak == "Serai"]), 1), "%"),
    paste0(round(mean(data_perlakuan$Repellency_pct[data_perlakuan$Ekstrak == "Kenikir"]), 1), "%"),
    paste0(round(mean(data_perlakuan$Repellency_pct[data_perlakuan$Ekstrak == "Dukuh"]), 1), "%")
  ),
  Senyawa_Aktif = c(
    "Citronellal, geraniol, limonene",
    "Flavonoid, metabolit sekunder",
    "Triterpenoid, bioaktif duku"
  ),
  Status = c("🏆 Kandidat utama", "✅ Kandidat ke-2", "✅ Kandidat alternatif")
)

peringkat %>%
  kable(
    caption   = "Tabel 8. Peringkat Akhir Efektivitas Repelan (Rerata 3 Ulangan)",
    col.names = c("Peringkat", "Ekstrak", "Rerata Repellency",
                  "Senyawa Aktif Utama", "Status")
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = TRUE) %>%
  row_spec(1, background = "#d4edda", bold = TRUE) %>%
  row_spec(2, background = "#fff3cd") %>%
  row_spec(3, background = "#fef9e7")
Tabel 8. Peringkat Akhir Efektivitas Repelan (Rerata 3 Ulangan)
Peringkat Ekstrak Rerata Repellency Senyawa Aktif Utama Status
1 Serai 99.3% Citronellal, geraniol, limonene 🏆 Kandidat utama |
2 Kenikir 95.1% Flavonoid, metabolit sekunder ✅ Kandidat ke-2 |
3 Dukuh 94.4% Triterpenoid, bioaktif duku ✅ Kandidat alternatif |

13 Kesimpulan

cat("
## Jawaban atas Rumusan Masalah

**RM 1 — Aktivitas repelan masing-masing ekstrak:**  
Ketiga ekstrak menunjukkan aktivitas repelan nyata. Serai paling cepat bereaksi,
diikuti Dukuh dan Kenikir berdasarkan pola semut mendekati pada setiap interval waktu.

**RM 2 — Ekstrak paling efektif:**  
Serai (*Cymbopogon citratus*) memiliki rerata repellency tertinggi, konsisten
di semua ulangan dan titik waktu. Efek terlihat sejak menit ke-15.

**RM 3 — Pengaruh waktu:**  
Efek repelan meningkat seiring waktu. Pada t = 30 menit, hampir seluruh ekstrak
telah mencapai repellency mendekati 100% pada sebagian besar ulangan.

**RM 4 — Perbedaan signifikan antar ekstrak:**  
Uji Kruskal-Wallis memberikan hasil seperti tercantum pada Tabel 6.
Uji post-hoc Dunn-Bonferroni (Tabel 7) menunjukkan pasangan mana yang berbeda
secara statistik. Interpretasi merujuk pada nilai p.adj yang dihasilkan.

---

## Keterbatasan & Saran

**Keterbatasan:**
- n = 3 ulangan → kekuatan uji statistik masih terbatas
- Belum ada kontrol negatif (gel tanpa ekstrak) dan kontrol positif (insektisida komersial)

**Saran untuk penelitian lanjutan:**
1. Tambah ulangan menjadi ≥ 5 per perlakuan
2. Sertakan kontrol negatif dan kontrol positif
3. Analisis probit untuk data mortalitas (LC50, LT50)
4. Uji konsentrasi bertingkat (F1–F4) dengan Kruskal-Wallis terpisah per waktu
")

13.1 Jawaban atas Rumusan Masalah

RM 1 — Aktivitas repelan masing-masing ekstrak:
Ketiga ekstrak menunjukkan aktivitas repelan nyata. Serai paling cepat bereaksi, diikuti Dukuh dan Kenikir berdasarkan pola semut mendekati pada setiap interval waktu.

RM 2 — Ekstrak paling efektif:
Serai (Cymbopogon citratus) memiliki rerata repellency tertinggi, konsisten di semua ulangan dan titik waktu. Efek terlihat sejak menit ke-15.

RM 3 — Pengaruh waktu:
Efek repelan meningkat seiring waktu. Pada t = 30 menit, hampir seluruh ekstrak telah mencapai repellency mendekati 100% pada sebagian besar ulangan.

RM 4 — Perbedaan signifikan antar ekstrak:
Uji Kruskal-Wallis memberikan hasil seperti tercantum pada Tabel 6. Uji post-hoc Dunn-Bonferroni (Tabel 7) menunjukkan pasangan mana yang berbeda secara statistik. Interpretasi merujuk pada nilai p.adj yang dihasilkan.


13.2 Keterbatasan & Saran

Keterbatasan: - n = 3 ulangan → kekuatan uji statistik masih terbatas - Belum ada kontrol negatif (gel tanpa ekstrak) dan kontrol positif (insektisida komersial)

Saran untuk penelitian lanjutan: 1. Tambah ulangan menjadi ≥ 5 per perlakuan 2. Sertakan kontrol negatif dan kontrol positif 3. Analisis probit untuk data mortalitas (LC50, LT50) 4. Uji konsentrasi bertingkat (F1–F4) dengan Kruskal-Wallis terpisah per waktu


14 Informasi Sesi R

sessionInfo()
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: Asia/Jakarta
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] car_3.1-5          carData_3.0-6      patchwork_1.3.2    ggpubr_0.6.3      
##  [5] rstatix_0.7.3      FSA_0.10.1         RColorBrewer_1.1-3 scales_1.4.0      
##  [9] kableExtra_1.4.0   knitr_1.50         lubridate_1.9.4    forcats_1.0.1     
## [13] stringr_1.5.1      dplyr_1.2.1        purrr_1.1.0        readr_2.1.6       
## [17] tidyr_1.3.1        tibble_3.3.0       ggplot2_4.0.1      tidyverse_2.0.0   
## 
## loaded via a namespace (and not attached):
##  [1] sass_0.4.10       generics_0.1.4    xml2_1.5.2        stringi_1.8.7    
##  [5] hms_1.1.4         digest_0.6.37     magrittr_2.0.3    evaluate_1.0.4   
##  [9] grid_4.5.1        timechange_0.3.0  fastmap_1.2.0     jsonlite_2.0.0   
## [13] backports_1.5.0   Formula_1.2-5     viridisLite_0.4.2 textshaping_1.0.4
## [17] jquerylib_0.1.4   abind_1.4-8       cli_3.6.5         rlang_1.3.0      
## [21] withr_3.0.2       cachem_1.1.0      yaml_2.3.10       tools_4.5.1      
## [25] tzdb_0.5.0        ggsignif_0.6.4    dunn.test_1.4.1   broom_1.0.12     
## [29] vctrs_0.7.1       R6_2.6.1          scrutiny_0.6.1    lifecycle_1.0.5  
## [33] pkgconfig_2.0.3   pillar_1.11.1     bslib_0.9.0       gtable_0.3.6     
## [37] glue_1.8.0        systemfonts_1.3.2 xfun_0.52         tidyselect_1.2.1 
## [41] rstudioapi_0.18.0 farver_2.1.2      htmltools_0.5.8.1 labeling_0.4.3   
## [45] rmarkdown_2.29    svglite_2.2.2     compiler_4.5.1    S7_0.2.0

Dokumen ini dibuat dengan R Markdown. Untuk menghasilkan ulang: buka file .Rmd di RStudio → klik Knit → Knit to HTML.