library(tidyverse)
library(lubridate)
library(scales)
library(reshape2)
library(ggtext)
library(patchwork)
library(kableExtra)
library(corrplot)
library(readxl)

1 Pendahuluan & Konteks Data

ISPU (Indeks Standar Pencemaran Udara) adalah indeks komposit yang mengukur tingkat pencemaran udara berdasarkan enam parameter: PM10, PM2.5, SO₂, CO, O₃, dan NO₂. Analisis ini menggunakan data dari 5 Stasiun Pemantauan Kualitas Udara (SPKU) milik Dinas Lingkungan Hidup DKI Jakarta selama periode 2015–2024, mencakup sekitar 3.300 hari pengukuran per stasiun.

Catatan Cakupan Data: Dataset sumber sebenarnya tersedia sejak 2010, namun analisis ini dipersempit ke 2015–2024 karena periode 2010–2014 memiliki cakupan stasiun yang belum lengkap (jumlah hari terekam jauh di bawah baseline tahun-tahun setelahnya) sehingga kurang adil untuk dibandingkan, dan karena satu dekade terakhir lebih relevan untuk konteks kebijakan kualitas udara Jakarta saat ini.

Skala Kategori ISPU:
🟢 BAIK (0–50): Kualitas udara memuaskan, polusi udara tidak menimbulkan risiko.
🟡 SEDANG (51–100): Kualitas udara dapat diterima, namun beberapa polutan memiliki efek bagi kelompok sensitif.
🟠 TIDAK SEHAT (101–199): Anggota kelompok sensitif dapat mengalami efek kesehatan; masyarakat umum tidak terpengaruh.
🔴 SANGAT TIDAK SEHAT (200–299): Risiko kesehatan meningkat untuk semua orang.
🟣 BERBAHAYA (300+): Darurat kesehatan; seluruh populasi berisiko.


2 Persiapan Data

# ============================================================
# CATATAN: Letakkan file Excel di direktori yang sama dengan
# file Rmd ini, lalu set working directory ke sana.
# Dataset ini adalah hasil penggabungan seluruh data SPKU
# 5 stasiun (2015–2024) + data tahun 2022, yang sudah
# dibersihkan dan diseragamkan formatnya.
# ============================================================

# --- Baca data gabungan dari Excel ---
# PENTING: guess_max diset besar (melebihi jumlah baris) agar readxl
# membaca SELURUH baris sebelum menebak tipe kolom. Tanpa ini, kolom
# yang nilainya jarang muncul di awal (mis. pm25, baru terisi mulai
# baris ~17.700 karena data 2021+) akan salah dideteksi sebagai
# kolom logical (TRUE/NA) alih-alih numeric, dan nilai aslinya hilang.
data_raw <- read_excel("data_ispu_dki_2010_2024.xlsx", sheet = "Sheet 1",
                       guess_max = 100000)

# --- Normalisasi tipe data & kolom turunan ---
# Kolom tahun/bulan_n/musim sudah tersedia di file sumber, tetapi
# kita hitung ulang dari `tanggal` agar konsisten & tidak bergantung
# pada penamaan kolom yang mungkin berbeda di file lain di kemudian hari.
data_raw <- data_raw %>%
  mutate(
    tanggal  = as.Date(tanggal),
    tahun    = year(tanggal),
    bulan    = month(tanggal, label = TRUE, abbr = FALSE),
    bulan_n  = month(tanggal),
    musim    = ifelse(bulan_n %in% 5:10, "Kemarau (Mei–Okt)", "Hujan (Nov–Apr)"),
    critical = recode(critical, "PM2,5" = "PM25")   # samakan alias penulisan
  )

# --- Bersihkan: buang data di luar rentang & nilai tidak valid ---
# REVISI: rentang dipersempit ke 2015-2024 (bukan 2010-2024) karena
# 2010-2011 cakupan stasiun belum lengkap (4/5 SPKU, hari terekam jauh
# di bawah baseline ~1.780-1.820 hari/tahun yang konsisten sejak 2015),
# dan agar analisis lebih relevan dengan konteks kebijakan 10 tahun
# terakhir. Tahun 2022 tetap disertakan walau juga tidak lengkap secara
# cakupan (lihat chunk `cek-cakupan-2022` di bawah untuk detail & alasan
# tetap mempertahankannya alih-alih membuang.
data_clean <- data_raw %>%
  filter(tahun >= 2015, tahun <= 2024, max > 0, !is.na(max), !is.na(categori))

# --- Palette warna global ---
warna_kategori <- c(
  "BAIK"               = "#27ae60",
  "SEDANG"             = "#f1c40f",
  "TIDAK SEHAT"        = "#e67e22",
  "SANGAT TIDAK SEHAT" = "#e74c3c",
  "BERBAHAYA"          = "#8e44ad"
)
kategori_order <- c("BAIK", "SEDANG", "TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA")

warna_polutan <- c(
  "O3"    = "#9b59b6",
  "PM10"  = "#e74c3c",
  "PM25"  = "#e67e22",
  "PM2,5" = "#e67e22",   # alias dari data 2022
  "CO"    = "#3498db",
  "SO2"   = "#1abc9c",
  "NO2"   = "#2c3e50"
)

cat("Data siap:", nrow(data_clean), "baris | Periode:",
    min(data_clean$tahun), "–", max(data_clean$tahun), "\n")
## Data siap: 16543 baris | Periode: 2015 – 2024
# --- Angka-angka kunci untuk referensi di seluruh dokumen ---
total_hari      <- nrow(data_clean)
hari_baik       <- sum(data_clean$categori == "BAIK",             na.rm = TRUE)
hari_sedang     <- sum(data_clean$categori == "SEDANG",           na.rm = TRUE)
hari_tdk_sehat  <- sum(data_clean$categori %in%
                       c("TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA"), na.rm = TRUE)
hari_berbahaya  <- sum(data_clean$categori == "BERBAHAYA",        na.rm = TRUE)

persen_baik     <- hari_baik      / total_hari * 100
persen_sedang   <- hari_sedang    / total_hari * 100
persen_tdk_baik <- (total_hari - hari_baik) / total_hari * 100
persen_buruk    <- hari_tdk_sehat / total_hari * 100

# Polutan dominan terbanyak
polutan_dom <- data_clean %>%
  filter(!is.na(critical)) %>%
  count(critical, sort = TRUE) %>%
  slice(1)

# Tahun terburuk & terbaik
year_stats <- data_clean %>%
  group_by(tahun) %>%
  summarise(rata = mean(max, na.rm = TRUE), .groups = "drop")
tahun_terburuk   <- year_stats$tahun[which.max(year_stats$rata)]
ispu_terburuk    <- round(max(year_stats$rata), 1)
tahun_terbaik    <- year_stats$tahun[which.min(year_stats$rata)]
ispu_terbaik     <- round(min(year_stats$rata), 1)

# Stasiun terburuk & terbersih
stasiun_stats <- data_clean %>%
  group_by(stasiun) %>%
  summarise(rata = mean(max, na.rm = TRUE), .groups = "drop") %>%
  arrange(desc(rata))
stasiun_terburuk <- stasiun_stats$stasiun[1]
ispu_st_terburuk <- round(stasiun_stats$rata[1], 1)
stasiun_terbaik  <- stasiun_stats$stasiun[nrow(stasiun_stats)]
ispu_st_terbaik  <- round(stasiun_stats$rata[nrow(stasiun_stats)], 1)

# Bulan terburuk
bulan_stats <- data_clean %>%
  group_by(bulan_n) %>%
  summarise(rata = mean(max, na.rm = TRUE), .groups = "drop")
bulan_terburuk_n <- bulan_stats$bulan_n[which.max(bulan_stats$rata)]
bulan_terburuk   <- format(as.Date(paste0("2024-", bulan_terburuk_n, "-01")), "%B")
# Cek kelengkapan cakupan data per tahun. Baseline normal ~1.780-1.820
# hari/tahun (5 stasiun x ~360 hari). Tahun 2022 jauh di bawah itu dan
# didominasi satu stasiun, sehingga dikecualikan dari perhitungan
# "tahun terburuk" agar perbandingan antar tahun tetap adil.
cakupan_tahun <- data_clean %>%
  group_by(tahun) %>%
  summarise(jumlah_hari = n(), jumlah_stasiun = n_distinct(stasiun), .groups = "drop")

baseline_cakupan   <- median(cakupan_tahun$jumlah_hari[cakupan_tahun$tahun != 2022])
tahun_tdk_lengkap  <- cakupan_tahun %>%
  filter(jumlah_hari < 0.7 * baseline_cakupan) %>% pull(tahun)

detail_stasiun_2022 <- data_clean %>%
  filter(tahun == 2022) %>%
  count(stasiun, sort = TRUE)
stasiun_dom_2022    <- detail_stasiun_2022$stasiun[1]
hari_dom_2022       <- detail_stasiun_2022$n[1]
hari_2022_total     <- sum(detail_stasiun_2022$n)

year_stats_lengkap <- data_clean %>%
  filter(!tahun %in% tahun_tdk_lengkap) %>%
  group_by(tahun) %>%
  summarise(rata = mean(max, na.rm = TRUE), .groups = "drop")
tahun_terburuk_lengkap <- year_stats_lengkap$tahun[which.max(year_stats_lengkap$rata)]
ispu_terburuk_lengkap  <- round(max(year_stats_lengkap$rata), 1)

Catatan: cakupan observasi 2022 tidak selengkap tahun lain (335 hari, dominan dari stasiun Lubang Buaya), sehingga klaim “tahun terburuk” pada dokumen ini dihitung dari tahun-tahun dengan cakupan lengkap.


3 Ringkasan Eksekutif

16.543
Total Hari Observasi
(5 Stasiun, 2015–2024)
17%
Hari dengan
Udara BAIK
12.9%
Hari Tidak Sehat
(atau lebih buruk)
2019
Tahun Terburuk*
(rata-rata ISPU 80.9)
O3
Polutan Dominan
Paling Sering Tercatat

*Cakupan 2022 tidak lengkap, sehingga dikecualikan dari perhitungan ini.

Temuan Utama: Selama 10 tahun (2015–2024), udara Jakarta tidak pernah secara konsisten berada di zona BAIK. Lebih dari tiga perempat hari, warga Jakarta menghirup udara berkategori SEDANG hingga berbahaya. Pandemi COVID-19 tahun 2020 menjadi satu-satunya “eksperimen alam” yang membuktikan bahwa pembatasan aktivitas manusia dapat menurunkan polusi secara dramatis — namun efeknya tidak bertahan lama.


4 Distribusi Kualitas Udara (2015–2024)

distribusi_kat <- data_clean %>%
  count(categori) %>%
  mutate(
    persen   = n / sum(n) * 100,
    categori = factor(categori, levels = kategori_order),
    label    = ifelse(persen > 2, paste0(round(persen, 1), "%"), "")
  ) %>%
  filter(!is.na(categori))   # buang "TIDAK ADA DATA" jika masih tersisa

ggplot(distribusi_kat, aes(x = 2, y = persen, fill = categori)) +
  geom_col(width = 1, color = "white", linewidth = 1) +
  coord_polar("y", start = 0) +
  xlim(0.5, 2.5) +
  scale_fill_manual(values = warna_kategori, name = "Kategori ISPU") +
  geom_text(aes(label = label),
            position = position_stack(vjust = 0.5),
            color = "white", fontface = "bold", size = 4.5) +
  annotate("text", x = 0.5, y = 0,
           label = paste0(round(persen_tdk_baik, 0), "%\nhari tidak\noptimal"),
           size = 6, fontface = "bold", color = "#2c3e50") +
  theme_void(base_size = 13) +
  theme(legend.position = "right") +
  labs(
    title    = "Distribusi Kualitas Udara Jakarta (2015–2024)",
    subtitle = paste0("Hanya ", round(persen_baik, 0),
                      "% hari benar-benar BAIK — lebih dari 3/4 hari di zona kuning atau lebih buruk"),
    caption  = "Sumber: DLH DKI Jakarta"
  )

Interpretasi: Donut chart ini mengungkap realita mendasar kualitas udara Jakarta. Kategori SEDANG mendominasi dengan proporsi terbesar, menandakan bahwa kondisi “lumayan aman” justru menjadi norma harian. Ini berbahaya karena paparan jangka panjang terhadap udara berkategori SEDANG tetap berdampak pada kelompok sensitif — anak-anak, lansia, ibu hamil, dan penderita penyakit pernapasan. Hanya 17% hari yang benar-benar memenuhi standar BAIK, berarti hampir 83% hari warga Jakarta terpapar kualitas udara yang tidak ideal. Meski hari BERBAHAYA sangat jarang, keberadaannya (0 hari) menunjukkan Jakarta pernah mencapai kondisi darurat udara.


5 Tren ISPU 10 Tahun (2015–2024)

trend_tahun <- data_clean %>%
  group_by(tahun) %>%
  summarise(
    rata_ispu    = mean(max, na.rm = TRUE),
    median_ispu  = median(max, na.rm = TRUE),
    persen_buruk = mean(categori %in%
                        c("TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA"),
                        na.rm = TRUE) * 100,
    persen_baik  = mean(categori == "BAIK", na.rm = TRUE) * 100,
    jumlah_hari  = n(),
    .groups      = "drop"
  )

# Variabel untuk inline reference
ispu_2019      <- filter(trend_tahun, tahun == 2019)$rata_ispu
ispu_2020      <- filter(trend_tahun, tahun == 2020)$rata_ispu
ispu_2021      <- filter(trend_tahun, tahun == 2021)$rata_ispu
penurunan_pct  <- (ispu_2019 - ispu_2020) / ispu_2019 * 100
rebound_pct    <- (ispu_2021 - ispu_2020) / ispu_2020 * 100
p_tren <- ggplot(trend_tahun, aes(x = tahun, y = rata_ispu)) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 100, ymax = Inf,
           fill = "#e74c3c", alpha = 0.08) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 50, ymax = 100,
           fill = "#f39c12", alpha = 0.08) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 50,
           fill = "#27ae60", alpha = 0.08) +
  geom_hline(yintercept = c(50, 100),
             linetype = "dashed",
             color    = c("#27ae60", "#e74c3c"),
             linewidth = 0.6) +
  annotate("text", x = 2015.2, y = 103,
           label = "TIDAK SEHAT (>100)", color = "#e74c3c",
           size = 3, hjust = 0, fontface = "italic") +
  annotate("text", x = 2015.2, y = 75,
           label = "SEDANG (51–100)", color = "#e67e22",
           size = 3, hjust = 0, fontface = "italic") +
  annotate("text", x = 2015.2, y = 46,
           label = "BAIK (≤50)", color = "#27ae60",
           size = 3, hjust = 0, fontface = "italic") +
  geom_line(color = "#2c3e50", linewidth = 1.1) +
  geom_point(color = "#2c3e50", size = 3, fill = "white", shape = 21, stroke = 1.2) +
  # Sorot 2020 (COVID)
  geom_point(data = filter(trend_tahun, tahun == 2020),
             color = "#27ae60", size = 6, shape = 19) +
  geom_text(data = filter(trend_tahun, tahun == 2020),
            aes(label = paste0("COVID-19\n↓", round(penurunan_pct, 0), "%")),
            vjust = 2.2, size = 3.5, color = "#27ae60", fontface = "bold") +
  # Sorot tahun terburuk versi cakupan-lengkap (dinamis, bukan tahun tetap)
  geom_point(data = filter(trend_tahun, tahun == tahun_terburuk_lengkap),
             color = "#e74c3c", size = 6, shape = 19) +
  geom_text(data = filter(trend_tahun, tahun == tahun_terburuk_lengkap),
            aes(label = paste0("Terburuk*\n", round(rata_ispu, 1))),
            vjust = -1.5, size = 3.5, color = "#e74c3c", fontface = "bold") +
  # Tandai 2022 secara eksplisit: lingkaran putus-putus abu-abu + label,
  # supaya jelas kenapa titik ini TIDAK diberi label "Terburuk" walau
  # nilainya lebih tinggi -- datanya tidak lengkap (lihat catatan di atas).
  geom_point(data = filter(trend_tahun, tahun == 2022),
             color = "#7f8c8d", size = 7, shape = 21, stroke = 1.3,
             fill = NA, linetype = "dashed") +
  geom_text(data = filter(trend_tahun, tahun == 2022),
            aes(label = paste0(round(rata_ispu, 1), "\n(data tidak lengkap)")),
            vjust = -1.3, size = 3, color = "#7f8c8d", fontface = "italic") +
  scale_x_continuous(breaks = seq(2015, 2024, 1)) +
  scale_y_continuous(limits = c(42, max(trend_tahun$rata_ispu) + 10)) +
  labs(x = "Tahun", y = "Rata-rata ISPU (maks. harian)",
       title   = "Tren ISPU DKI Jakarta (2015–2024)",
       subtitle = paste0("Fluktuatif tanpa perbaikan struktural; 2020 titik terendah akibat PSBB, ",
                         tahun_terburuk_lengkap, " terburuk dalam 10 tahun (data lengkap)"),
       caption = "Sumber: DLH DKI Jakarta") +
  theme_minimal(base_size = 12) +
  theme(panel.grid.minor   = element_blank(),
        plot.title         = element_text(face = "bold", size = 14),
        plot.subtitle      = element_text(color = "#555555", size = 11))

p_bar <- ggplot(trend_tahun, aes(x = factor(tahun), y = persen_buruk)) +
  geom_col(aes(fill = persen_buruk), width = 0.65, show.legend = FALSE) +
  geom_text(aes(label = paste0(round(persen_buruk, 1), "%")),
            vjust = -0.4, size = 3, fontface = "bold") +
  scale_fill_gradient(low = "#f9e79f", high = "#c0392b") +
  scale_y_continuous(labels = function(x) paste0(x, "%"),
                     limits = c(0, max(trend_tahun$persen_buruk) + 6)) +
  labs(x = "Tahun", y = "% Hari Tidak Sehat",
       title = "Persentase Hari dengan Udara Tidak Sehat per Tahun") +
  theme_minimal(base_size = 11) +
  theme(axis.text.x      = element_text(angle = 45, hjust = 1, size = 8),
        panel.grid.minor = element_blank())

p_tren / p_bar + plot_layout(heights = c(1.6, 1))

Interpretasi: Tren 10 tahun menunjukkan pola fluktuatif tanpa perbaikan struktural yang konsisten. Tiga fase utama dapat diidentifikasi:

  1. 2015–2019 (Fluktuatif Pra-Pandemi): ISPU bergerak naik-turun di kisaran 65–81 tanpa arah tren yang jelas, mencerminkan absennya kebijakan lingkungan jangka panjang yang benar-benar menekan polusi secara struktural. Fase ini mencapai titik tertinggi pra-pandemi pada 2019 (rata-rata ISPU 80.9).

  2. 2020 (Anomali PSBB): Titik terendah sepanjang periode 2015–2024 terjadi di 2020 saat PSBB COVID-19 membatasi mobilitas, terbukti menurunkan ISPU 22% dibandingkan 2019.

  3. 2021–2024 (Rebound Pasca-Pandemi): Begitu pembatasan dicabut, ISPU kembali melonjak melampaui level pra-pandemi. Tahun 2019 tercatat sebagai yang tertinggi (rata-rata ISPU 80.9), mengonfirmasi bahwa penurunan polusi selama pandemi bersifat sementara, bukan hasil perbaikan struktural — ISPU tahun-tahun setelahnya konsisten kembali ke level pra-pandemi atau lebih tinggi.

Catatan Penting: Penurunan ISPU saat COVID bukan karena kebijakan lingkungan, melainkan karena force majeure. Rebound cepat setelah pembatasan dicabut membuktikan bahwa tanpa transformasi sistem transportasi dan energi, kualitas udara Jakarta tidak akan membaik secara berkelanjutan. Selama satu dekade penuh (2015–2024), tidak ada satu tahun pun — di luar anomali 2020 — yang rata-rata ISPU-nya turun mendekati zona BAIK (≤50), menandakan bahwa krisis kualitas udara Jakarta adalah persoalan struktural yang berulang, bukan fluktuasi acak.


6 Analisis Spasial: Perbandingan Antar Stasiun

ranking_stasiun <- data_clean %>%
  group_by(stasiun) %>%
  summarise(
    rata_ispu    = mean(max, na.rm = TRUE),
    persen_buruk = mean(categori %in%
                        c("TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA"),
                        na.rm = TRUE) * 100,
    ispu_maks    = max(max, na.rm = TRUE),
    .groups      = "drop"
  ) %>%
  arrange(desc(rata_ispu))

stasiun_terburuk2 <- ranking_stasiun$stasiun[2]   # peringkat ke-2 terburuk
ispu_st_terburuk2 <- round(ranking_stasiun$rata_ispu[2], 1)

heatmap_data <- data_clean %>%
  group_by(tahun, stasiun) %>%
  summarise(rata_ispu = mean(max, na.rm = TRUE), .groups = "drop")
p_stasiun <- ggplot(ranking_stasiun,
                    aes(reorder(stasiun, rata_ispu), rata_ispu)) +
  geom_col(aes(fill = rata_ispu), width = 0.7, show.legend = FALSE) +
  geom_text(aes(label = paste0(round(rata_ispu, 1),
                               " | maks: ", round(ispu_maks, 0))),
            hjust = -0.08, fontface = "bold", size = 4) +
  scale_fill_gradient(low = "#f9e79f", high = "#c0392b") +
  coord_flip(ylim = c(0, 115)) +
  labs(x = NULL, y = "Rata-rata ISPU",
       title    = "Rata-rata ISPU per Stasiun (2015–2024)",
       subtitle = paste0(stasiun_terburuk, " secara konsisten mencatat rata-rata ISPU tertinggi")) +
  theme_minimal(base_size = 12) +
  theme(panel.grid.major.y = element_blank())

p_heatmap <- ggplot(heatmap_data,
                    aes(factor(tahun), reorder(stasiun, rata_ispu),
                        fill = rata_ispu)) +
  geom_tile(color = "white", linewidth = 0.5) +
  geom_text(aes(label = round(rata_ispu, 0)),
            size = 2.8, color = "white", fontface = "bold") +
  scale_fill_gradient2(low = "#27ae60", mid = "#f39c12", high = "#c0392b",
                       midpoint = 73, name = "ISPU") +
  labs(x = "Tahun", y = NULL,
       title    = "Heatmap ISPU per Stasiun per Tahun",
       subtitle = "Warna merah = ISPU tinggi; pola intensitas berbeda di tiap stasiun dapat ditelusuri per tahun") +
  theme_minimal(base_size = 11) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid  = element_blank())

p_stasiun / p_heatmap + plot_layout(heights = c(1, 1.3))

Interpretasi: Terdapat kesenjangan kualitas udara yang signifikan antar wilayah Jakarta:

  • Lubang Buaya mencatat rata-rata ISPU tertinggi (76.4), diikuti oleh Kebon Jeruk (76.1). Pola ini umumnya berkaitan dengan kepadatan arus kendaraan, kedekatan dengan kawasan industri atau permukiman padat, serta sirkulasi udara yang lebih terbatas dibanding wilayah dengan ruang terbuka yang lebih luas.

  • Bundaran HI justru mencatat rata-rata ISPU terendah (62.5) meski terletak di kawasan padat aktivitas. Hal ini kemungkinan besar disebabkan oleh efek dispersi angin yang lebih efektif di area terbuka, lebar jalan yang memadai, dan keberadaan ruang terbuka hijau di sekitarnya.

  • Heatmap mengungkap pola temporal yang dapat ditelusuri per stasiun dan per tahun: intensitas warna merah tidak seragam dari tahun ke tahun, menandakan bahwa penyebab pencemaran di tiap wilayah memiliki dinamika dan sensitivitas musiman yang berbeda-beda, bukan semata mengikuti tren kota secara keseluruhan.

Implikasi Kebijakan: Strategi pengendalian pencemaran udara tidak bisa seragam untuk seluruh Jakarta. Lubang Buaya dan Kebon Jeruk membutuhkan intervensi lebih agresif, seperti pembatasan kendaraan berat, penghijauan koridor jalan, dan pemantauan real-time yang lebih ketat.


7 Distribusi ISPU per Stasiun

ggplot(data_clean, aes(reorder(stasiun, max, FUN = median), max, fill = stasiun)) +
  geom_boxplot(show.legend = FALSE, outlier.alpha = 0.3,
               outlier.size = 1.2, width = 0.6) +
  geom_hline(yintercept = 100, linetype = "dashed",
             color = "#e74c3c", linewidth = 0.8) +
  geom_hline(yintercept = 50,  linetype = "dashed",
             color = "#27ae60", linewidth = 0.8) +
  annotate("text", x = 0.7, y = 102,
           label = "Batas TIDAK SEHAT", color = "#e74c3c",
           size = 3, hjust = 0, fontface = "italic") +
  annotate("text", x = 0.7, y = 52,
           label = "Batas BAIK", color = "#27ae60",
           size = 3, hjust = 0, fontface = "italic") +
  scale_fill_brewer(palette = "Set2") +
  labs(
    title    = "Distribusi ISPU per Stasiun (2015–2024)",
    subtitle = "Boxplot memperlihatkan sebaran, median, dan nilai ekstrem tiap stasiun",
    x        = NULL,
    y        = "Nilai ISPU Harian (Maks)"
  ) +
  theme_minimal(base_size = 12) +
  theme(panel.grid.major.x = element_blank())

Interpretasi: Boxplot memperjelas tidak hanya rata-rata, tetapi juga variabilitas dan nilai ekstrem tiap stasiun.

Lubang Buaya mencatat median ISPU tertinggi (74.5) sekaligus nilai ekstrem tertinggi yang pernah terukur di antara kelima stasiun (ISPU 287). Kombinasi ini menandakan stasiun tersebut bukan hanya secara konsisten berada di level lebih tinggi pada hari biasa, tetapi juga paling rentan terhadap episoda pencemaran ekstrem sesekali — menjadikannya prioritas utama untuk intervensi struktural sekaligus sistem peringatan dini.

Nilai ekstrem sesekali tetap berdampak kesehatan serius, bahkan jika rata-rata harian terlihat relatif aman.


8 Polutan Dominan Penyebab Pencemaran

# Normalisasi nama polutan (PM2,5 dari data 2022 → PM25)
polutan_clean <- data_clean %>%
  filter(!is.na(critical)) %>%
  mutate(critical = recode(critical, "PM2,5" = "PM25"))

polutan_summary <- polutan_clean %>%
  count(critical) %>%
  mutate(persen = n / sum(n) * 100) %>%
  arrange(desc(persen))

tren_polutan <- polutan_clean %>%
  count(tahun, critical) %>%
  group_by(tahun) %>%
  mutate(persen = n / sum(n) * 100) %>%
  ungroup()

pol_dom_name   <- polutan_summary$critical[1]
pol_dom_persen <- round(polutan_summary$persen[1], 1)
p_polutan <- ggplot(polutan_summary,
                    aes(reorder(critical, persen), persen, fill = critical)) +
  geom_col(show.legend = FALSE, width = 0.7) +
  geom_text(aes(label = paste0(round(persen, 1), "%")),
            hjust = -0.1, fontface = "bold", size = 4) +
  scale_fill_manual(values = warna_polutan) +
  coord_flip(ylim = c(0, max(polutan_summary$persen) + 8)) +
  labs(x = NULL, y = "% Hari sebagai Polutan Dominan",
       title    = "Polutan Dominan Penyebab ISPU Tertinggi (2015–2024)",
       subtitle = paste0(pol_dom_name, " menjadi polutan dominan terbanyak (",
                         pol_dom_persen, "% hari)")) +
  theme_minimal(base_size = 12) +
  theme(panel.grid.major.y = element_blank())

p_area <- ggplot(tren_polutan, aes(tahun, persen, fill = critical)) +
  geom_area(alpha = 0.85, position = "fill") +
  scale_fill_manual(values = warna_polutan, name = "Polutan") +
  scale_x_continuous(breaks = seq(2015, 2024, 1)) +
  scale_y_continuous(labels = percent_format()) +
  labs(x = "Tahun", y = "Proporsi Hari (%)",
       title    = "Pergeseran Komposisi Polutan Dominan per Tahun",
       subtitle = "Pergeseran dari CO/PM10 menuju O3 dan PM2.5 mencerminkan perubahan pola emisi") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "bottom",
        panel.grid.minor = element_blank())

p_polutan / p_area + plot_layout(heights = c(1, 1.3))

Interpretasi: Ozon (O₃) mendominasi sebagai polutan penyebab ISPU tertinggi (38.2% hari). Ini adalah temuan krusial: O₃ bukan polutan yang langsung diemisikan (polutan primer), melainkan polutan sekunder yang terbentuk melalui reaksi fotokimia antara NOₓ dan Volatile Organic Compounds (VOC) di bawah sinar matahari.

Dominansi O₃ di Jakarta mengindikasikan bahwa: 1. Emisi NOₓ dari kendaraan bermotor dan industri sangat tinggi sebagai bahan baku pembentuk ozon. 2. Intensitas sinar matahari Jakarta yang tinggi sepanjang tahun mempercepat reaksi fotokimia. 3. Pengendalian harus difokuskan pada pengurangan emisi NOₓ dan VOC, bukan hanya partikel.

Area chart menunjukkan bahwa O₃ sudah menjadi polutan dominan sejak awal periode 2015–2024 (bukan baru muncul belakangan) — pada 2015–2017 saja O₃ sudah tercatat sebagai penyebab ISPU tertinggi di sekitar separuh hari, mengungguli PM10. Pergeseran yang lebih mencolok justru terlihat menjelang 2022–2024, ketika PM2.5 melonjak menjadi polutan dominan di sebagian besar hari. Pergeseran ini perlu dibaca hati-hati: seperti dicatat pada bagian “Fokus PM2.5” di bawah, parameter PM2.5 baru dipantau secara konsisten di seluruh stasiun sejak 2021. Artinya, sebagian dari lonjakan dominansi PM2.5 di akhir periode kemungkinan mencerminkan bertambahnya cakupan sensor, bukan murni perubahan komposisi polutan di udara. Yang tetap dapat disimpulkan dengan yakin adalah: karakter pencemaran Jakarta di dekade ini didominasi polutan sekunder/halus (O₃ dan PM2.5), bukan polutan kasar (PM10) atau gas primer (CO), sehingga strategi pengendalian perlu menyasar prekursor fotokimia (NOₓ, VOC) dan partikel halus sekaligus.

Perhatian Khusus PM2.5: Meski PM2.5 “hanya” berada di posisi ketiga, partikel berdiameter <2,5 mikron ini jauh lebih berbahaya karena mampu menembus jauh ke dalam alveolus paru-paru dan masuk ke aliran darah, berkaitan dengan penyakit kardiovaskular dan kanker paru.


9 Fokus PM2.5: Kontribusi Sub-Indeks Terhadap ISPU

pm25_tren <- data_clean %>%
  filter(!is.na(pm25), pm25 > 0) %>%
  group_by(tahun, stasiun) %>%
  summarise(rata_pm25 = mean(pm25, na.rm = TRUE), .groups = "drop")

ggplot(pm25_tren, aes(factor(tahun), rata_pm25,
                       color = stasiun, group = stasiun)) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 100, ymax = Inf,
           fill = "#e74c3c", alpha = 0.07) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 50, ymax = 100,
           fill = "#f39c12", alpha = 0.07) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 50,
           fill = "#27ae60", alpha = 0.07) +
  geom_hline(yintercept = 100, linetype = "dashed",
             color = "#e74c3c", linewidth = 0.7) +
  geom_hline(yintercept = 50, linetype = "dashed",
             color = "#27ae60", linewidth = 0.7) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 3) +
  scale_color_brewer(palette = "Dark2", name = "Stasiun") +
  labs(x = "Tahun", y = "Rata-rata Sub-Indeks ISPU PM2.5",
       title    = "Tren Sub-Indeks ISPU PM2.5 per Stasiun (2021-2024)",
       subtitle = "Garis hijau = batas BAIK (50) | Garis merah = batas TIDAK SEHAT (100)",
       caption  = "Catatan: data PM2.5 baru dipantau konsisten sejak 2021; nilai adalah sub-indeks ISPU, bukan konsentrasi µg/m3") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

pm25_data   <- data_clean %>% filter(!is.na(pm25), pm25 > 0)
pct_baik    <- mean(pm25_data$pm25 <= 50, na.rm = TRUE) * 100
pct_sedang  <- mean(pm25_data$pm25 > 50 & pm25_data$pm25 <= 100, na.rm = TRUE) * 100
pct_buruk   <- mean(pm25_data$pm25 > 100, na.rm = TRUE) * 100
pm25_dom    <- mean(pm25_data$critical == "PM25", na.rm = TRUE) * 100

tibble(
  `Kategori Sub-Indeks PM2.5` = c("BAIK (≤50)", "SEDANG (51-100)", "TIDAK SEHAT ke atas (>100)"),
  `% Hari`                    = c(paste0(round(pct_baik, 1), "%"),
                                  paste0(round(pct_sedang, 1), "%"),
                                  paste0(round(pct_buruk, 1), "%"))
) %>%
  kable(caption = "Distribusi Sub-Indeks ISPU PM2.5 (2021-2024)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered"),
                full_width = FALSE) %>%
  row_spec(3, bold = TRUE, color = "white", background = "#e67e22")
Distribusi Sub-Indeks ISPU PM2.5 (2021-2024)
Kategori Sub-Indeks PM2.5 % Hari
BAIK (≤50) 12.1%
SEDANG (51-100) 73.5%
TIDAK SEHAT ke atas (>100) 14.4%

Catatan satuan data: Nilai pm25 (dan polutan lain) pada dataset ini adalah sub-indeks ISPU hasil konversi konsentrasi ke skala 0-500 sesuai pedoman ISPU KLHK, bukan konsentrasi mentah dalam µg/m³. Karena itu nilai sub-indeks dibandingkan terhadap ambang batas kategori ISPU (50 dan 100), bukan terhadap baku mutu konsentrasi WHO atau PP No. 22 Tahun 2021.

Interpretasi: Data PM2.5 hanya tersedia secara konsisten sejak 2021 (parameter ini baru dipantau lebih luas di stasiun-stasiun SPKU pada periode tersebut), sehingga grafik di atas hanya mencerminkan kondisi 2021–2024. Pada periode ini, sub-indeks PM2.5 berada di 89.1% hari sebagai polutan kritis (penyumbang ISPU tertinggi) — menjadikannya kontributor penting meski bukan yang paling dominan dibanding O₃.

Tabel distribusi menunjukkan 14.4% hari sub-indeks PM2.5 saja sudah berada di kategori TIDAK SEHAT ke atas, sementara hanya 12.1% hari berada di kategori BAIK. Ini menegaskan bahwa PM2.5 tetap menjadi pencemar serius di Jakarta meski O₃ lebih sering menjadi penyebab nilai ISPU harian tertinggi.

Perlu dicatat bahwa keterbatasan data PM2.5 sebelum 2021 (banyak nilai NA) kemungkinan besar mencerminkan keterbatasan jangkauan alat pemantauan saat itu, bukan absennya polutan. Investasi pada sensor PM2.5 yang lebih luas dan konsisten sejak awal periode akan memberikan gambaran risiko jangka panjang yang lebih akurat.


10 Pola Musiman ISPU Bulanan

bulanan <- data_clean %>%
  group_by(bulan_n) %>%
  summarise(
    rata_ispu    = mean(max, na.rm = TRUE),
    median_ispu  = median(max, na.rm = TRUE),
    persen_buruk = mean(categori %in%
                        c("TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA"),
                        na.rm = TRUE) * 100,
    .groups      = "drop"
  ) %>%
  mutate(bulan = month(bulan_n, label = TRUE, abbr = FALSE))

musim_summary <- data_clean %>%
  group_by(musim) %>%
  summarise(
    rata_ispu    = mean(max, na.rm = TRUE),
    persen_buruk = mean(categori %in%
                        c("TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA"),
                        na.rm = TRUE) * 100,
    jumlah_hari  = n(),
    .groups      = "drop"
  )

ispu_kemarau <- filter(musim_summary, grepl("Kemarau", musim))$rata_ispu
ispu_hujan   <- filter(musim_summary, grepl("Hujan", musim))$rata_ispu
selisih_musim <- round(ispu_kemarau - ispu_hujan, 1)
nama_bulan_id <- c("Jan","Feb","Mar","Apr","Mei","Jun",
                   "Jul","Agt","Sep","Okt","Nov","Des")

ggplot(bulanan, aes(bulan_n, rata_ispu, group = 1)) +
  # Latar musim kemarau
  annotate("rect", xmin = 4.5, xmax = 10.5, ymin = -Inf, ymax = Inf,
           fill = "#f39c12", alpha = 0.12) +
  # Label musim
  annotate("text", x = 7.5, y = max(bulanan$rata_ispu) + 3,
           label = "MUSIM KEMARAU (Mei–Okt)", fontface = "bold",
           size = 3.8, color = "#e67e22") +
  annotate("text", x = 2.3, y = max(bulanan$rata_ispu) + 3,
           label = "MUSIM HUJAN", fontface = "bold",
           size = 3.5, color = "#2980b9") +
  annotate("text", x = 11.7, y = max(bulanan$rata_ispu) + 3,
           label = "HUJAN", fontface = "bold",
           size = 3.2, color = "#2980b9") +
  # Ribbon selisih mean–median
  geom_ribbon(aes(ymin = median_ispu, ymax = rata_ispu),
              fill = "#e74c3c", alpha = 0.18) +
  geom_line(color = "#c0392b", linewidth = 1.5) +
  # Titik: ukuran = % hari tidak sehat
  geom_point(aes(size = persen_buruk), color = "#c0392b", show.legend = FALSE) +
  geom_text(aes(label = round(rata_ispu, 1)), vjust = -1.6, size = 3.5, fontface = "bold") +
  scale_x_continuous(breaks = 1:12, labels = nama_bulan_id) +
  scale_y_continuous(limits = c(45, max(bulanan$rata_ispu) + 6)) +
  scale_size(range = c(3, 10)) +
  labs(x = NULL, y = "Rata-rata ISPU",
       title    = "Pola ISPU Bulanan (2015–2024)",
       subtitle = "Ukuran titik = % hari tidak sehat | Warna area = selisih rata-rata & median",
       caption  = "Sumber: DLH DKI Jakarta") +
  theme_minimal(base_size = 13) +
  theme(panel.grid.minor = element_blank())

Interpretasi: Pola musiman ISPU Jakarta sangat jelas dan dapat diprediksi. Bulan August tercatat sebagai bulan dengan rata-rata ISPU tertinggi, sementara Januari adalah yang terendah. Terdapat selisih rata-rata sekitar 18 poin ISPU antara musim kemarau dan musim hujan.

Mekanisme fisik di balik pola ini: - Musim kemarau (Mei–Oktober): Tidak ada hujan yang berfungsi sebagai “pembersih” alami polutan di udara (wet deposition). Udara panas dan kering mempercepat reaksi fotokimia pembentukan O₃. Angin muson timur membawa partikel dari wilayah timur Indonesia. - Musim hujan (November–April): Hujan secara aktif mencuci partikel dan polutan dari atmosfer (washout effect), mengurangi konsentrasi PM10 dan PM2.5 secara signifikan.

Ukuran titik yang membesar di musim kemarau mengkonfirmasi bahwa risiko kesehatan meningkat di bulan-bulan kering. Informasi ini dapat dimanfaatkan untuk sistem peringatan dini berbasis kalender musim.

Peluang Intervensi: Pola musiman yang dapat diprediksi ini memberikan window of opportunity untuk kebijakan proaktif: pengetatan emisi kendaraan dan industri mulai April–Mei sebelum musim kemarau, penambahan penyiraman jalan di musim kemarau, dan kampanye kesehatan masyarakat yang terjadwal.


11 Dampak COVID-19: Eksperimen Alam Terbesar

covid_years <- data_clean %>%
  filter(tahun %in% c(2018, 2019, 2020, 2021)) %>%
  group_by(tahun) %>%
  summarise(
    rata_ispu    = mean(max, na.rm = TRUE),
    persen_buruk = mean(categori %in%
                        c("TIDAK SEHAT", "SANGAT TIDAK SEHAT", "BERBAHAYA"),
                        na.rm = TRUE) * 100,
    persen_baik  = mean(categori == "BAIK", na.rm = TRUE) * 100,
    .groups      = "drop"
  )

ispu_2018     <- filter(covid_years, tahun == 2018)$rata_ispu
ispu_2019     <- filter(covid_years, tahun == 2019)$rata_ispu
ispu_2020     <- filter(covid_years, tahun == 2020)$rata_ispu
ispu_2021     <- filter(covid_years, tahun == 2021)$rata_ispu
penurunan_pct <- (ispu_2019 - ispu_2020) / ispu_2019 * 100
rebound_pct   <- (ispu_2021 - ispu_2020) / ispu_2020 * 100
p_covid_bar <- ggplot(covid_years, aes(factor(tahun), rata_ispu, fill = factor(tahun))) +
  geom_col(show.legend = FALSE, width = 0.65) +
  geom_text(aes(label = round(rata_ispu, 1)),
            vjust = -0.5, fontface = "bold", size = 5.5) +
  # Panah & label penurunan 2019→2020
  annotate("segment",
           x = 2.1, xend = 2.9,
           y = ispu_2019 + 1.5, yend = ispu_2020 + 1.5,
           arrow     = arrow(ends = "last", type = "closed",
                             length = unit(0.25, "cm")),
           color     = "#27ae60", linewidth = 1.5) +
  annotate("text", x = 2.5, y = (ispu_2019 + ispu_2020) / 2 + 6,
           label = paste0("↓ ", round(penurunan_pct, 0), "%\nPSBB"),
           color = "#27ae60", fontface = "bold", size = 4) +
  # Panah & label kenaikan 2020→2021
  annotate("segment",
           x = 3.1, xend = 3.9,
           y = ispu_2020 + 1.5, yend = ispu_2021 + 1.5,
           arrow     = arrow(ends = "last", type = "closed",
                             length = unit(0.25, "cm")),
           color     = "#e74c3c", linewidth = 1.5) +
  annotate("text", x = 3.5, y = (ispu_2020 + ispu_2021) / 2 + 6,
           label = paste0("↑ ", round(rebound_pct, 0), "%\nRebound"),
           color = "#e74c3c", fontface = "bold", size = 4) +
  scale_fill_manual(values = c("2018" = "#e67e22", "2019" = "#c0392b",
                               "2020" = "#27ae60", "2021" = "#f39c12")) +
  scale_y_continuous(limits = c(0, max(covid_years$rata_ispu) + 12)) +
  labs(x = "Tahun", y = "Rata-rata ISPU",
       title    = "Dampak COVID-19 pada ISPU Jakarta (2018–2021)",
       subtitle = paste0("Penurunan ", round(penurunan_pct, 0),
                         "% saat PSBB 2020 — rebound cepat sebesar ",
                         round(rebound_pct, 0), "% di 2021"),
       caption  = "Sumber: DLH DKI Jakarta") +
  theme_minimal(base_size = 13) +
  theme(panel.grid.minor = element_blank())

# Perbandingan bulanan 2019 vs 2020
covid_bulanan <- data_clean %>%
  filter(tahun %in% c(2019, 2020)) %>%
  group_by(tahun, bulan, bulan_n) %>%
  summarise(rata_ispu = mean(max, na.rm = TRUE), .groups = "drop")

covid_wide <- covid_bulanan %>%
  pivot_wider(names_from = tahun, values_from = rata_ispu, names_prefix = "ispu_") %>%
  mutate(beda = ispu_2019 - ispu_2020)

p_covid_bulan <- ggplot(covid_bulanan,
                        aes(bulan, rata_ispu, color = factor(tahun), group = tahun)) +
  geom_ribbon(data      = covid_wide,
              aes(x = bulan,
                  ymin = pmin(ispu_2019, ispu_2020),
                  ymax = pmax(ispu_2019, ispu_2020),
                  group = 1),
              inherit.aes = FALSE, fill = "#27ae60", alpha = 0.22) +
  geom_line(linewidth = 1.3) +
  geom_point(size = 3.5) +
  geom_text(data = filter(covid_wide, bulan_n %in% c(3, 5, 7, 9)),
            aes(x     = bulan,
                y     = (ispu_2019 + ispu_2020) / 2,
                label = paste0("-", round(beda, 0), " poin")),
            inherit.aes = FALSE, color = "#27ae60",
            fontface = "bold", size = 3.5, vjust = -0.9) +
  scale_color_manual(values = c("2019" = "#c0392b", "2020" = "#27ae60"),
                     name   = "Tahun") +
  labs(x = NULL, y = "Rata-rata ISPU",
       title    = "ISPU Bulanan: 2019 vs 2020",
       subtitle = "Area hijau = selisih penurunan ISPU akibat PSBB") +
  theme_minimal(base_size = 13) +
  theme(legend.position = "bottom")

p_covid_bar / p_covid_bulan

Interpretasi: Analisis periode COVID-19 memberikan bukti kausalitas terkuat yang pernah ada untuk hubungan antara aktivitas manusia dan kualitas udara Jakarta:

  • 2020 vs 2019: PSBB (Pembatasan Sosial Berskala Besar) yang membatasi mobilitas kendaraan dan aktivitas industri menurunkan rata-rata ISPU sebesar 22% — dari 80.9 menjadi 63.2.
  • Penurunan terbesar terjadi pada bulan-bulan PSBB paling ketat (Maret–Mei 2020), dengan perbedaan yang terbesar tercatat di beberapa bulan musim kemarau.
  • Rebound 2021: Begitu pembatasan dilonggarkan, ISPU langsung melonjak 23% — membuktikan bahwa tanpa perubahan struktural, kualitas udara akan kembali ke kondisi buruk.
covid_kat <- data_clean %>%
  filter(tahun %in% c(2019, 2020)) %>%
  count(tahun, categori) %>%
  group_by(tahun) %>%
  mutate(persen   = n / sum(n) * 100,
         categori = factor(categori, levels = kategori_order)) %>%
  filter(!is.na(categori))

ggplot(covid_kat, aes(factor(tahun), persen, fill = categori)) +
  geom_col(position = "stack", width = 0.5) +
  scale_fill_manual(values = warna_kategori, name = "Kategori ISPU") +
  scale_y_continuous(labels = function(x) paste0(x, "%")) +
  geom_text(aes(label = ifelse(persen > 3, paste0(round(persen, 0), "%"), "")),
            position = position_stack(vjust = 0.5),
            color = "white", fontface = "bold", size = 4) +
  labs(x = NULL, y = "Persentase Hari (%)",
       title    = "Distribusi Kategori ISPU: 2019 vs 2020",
       subtitle = "Proporsi hari BAIK meningkat & hari TIDAK SEHAT berkurang signifikan di 2020") +
  theme_minimal(base_size = 13)

Interpretasi: Stacked bar chart mempertegas dampak PSBB dari perspektif distribusi kategori. Di tahun 2020, proporsi hari dengan udara BAIK meningkat dan hari TIDAK SEHAT berkurang dibandingkan 2019. Ini mengkonfirmasi bahwa bukan hanya rata-rata ISPU yang turun, tetapi secara nyata lebih banyak hari yang dapat dinikmati warga Jakarta dengan udara yang lebih sehat. Temuan ini seharusnya menjadi landasan kebijakan: investasi pada transportasi publik, elektrifikasi kendaraan, dan pengurangan emisi industri dapat memberikan dampak yang sebanding dengan PSBB, tetapi secara permanen dan tanpa mengorbankan perekonomian.


12 Tren ISPU per Stasiun (Faceted)

data_clean %>%
  group_by(tahun, stasiun) %>%
  summarise(rata = mean(max, na.rm = TRUE), .groups = "drop") %>%
  ggplot(aes(tahun, rata, color = stasiun, group = stasiun)) +
  geom_line(linewidth = 1.1) +
  geom_point(size = 2.5) +
  geom_hline(yintercept = 100, linetype = "dashed",
             color = "#e74c3c", linewidth = 0.6, alpha = 0.7) +
  geom_hline(yintercept = 50, linetype = "dashed",
             color = "#27ae60", linewidth = 0.6, alpha = 0.7) +
  facet_wrap(~stasiun, ncol = 3) +
  scale_x_continuous(breaks = seq(2015, 2024, 2)) +
  scale_color_brewer(palette = "Dark2") +
  labs(
    title    = "Tren ISPU Tahunan per Stasiun (2015–2024)",
    subtitle = "Garis merah putus = batas TIDAK SEHAT (100) | Garis hijau putus = batas BAIK (50)",
    x        = "Tahun",
    y        = "Rata-rata ISPU",
    caption  = "Sumber: DLH DKI Jakarta"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    legend.position    = "none",
    panel.grid.minor   = element_blank(),
    strip.text         = element_text(face = "bold", size = 10),
    axis.text.x        = element_text(angle = 45, hjust = 1, size = 8)
  )

Interpretasi: Visualisasi faceted memungkinkan perbandingan tren antar stasiun secara bersamaan. Beberapa pola menonjol:

  • Lubang Buaya memperlihatkan tren yang secara konsisten berada di atas stasiun lain sepanjang sebagian besar periode, mendekati garis merah (batas TIDAK SEHAT, ISPU 100) di tahun-tahun terburuknya tanpa benar-benar melampauinya. Kebon Jeruk justru yang benar-benar melampaui garis TIDAK SEHAT pada beberapa titik, menandakan episoda pencemaran akutnya lebih ekstrem meski rata-rata tahunannya sedikit lebih rendah.
  • Bundaran HI menunjukkan variabilitas tinggi: ada tahun-tahun di mana ISPU-nya mendekati zona BAIK, tetapi juga ada lonjakan tajam di tahun-tahun tertentu. Hal ini berkaitan dengan dinamika lalu lintas musiman dan pola angin di kawasannya.
  • Pola umum di semua stasiun: penurunan serempak di 2020 dan kenaikan kembali di 2021–2022 terlihat pada kelima panel. Ini memperkuat argumen bahwa penyebab pencemaran sebagian besar bersifat kota-wide (mobilitas, industri, kondisi cuaca regional), bukan murni faktor lokal per wilayah.
  • Variasi antar stasiun membuktikan pentingnya pemantauan terdesentralisasi: hanya mengandalkan satu stasiun akan memberikan gambaran yang menyesatkan tentang kondisi udara Jakarta secara keseluruhan, karena setiap wilayah memiliki karakteristik sumber emisi dan dispersi udara yang berbeda.

12.1 Tren ISPU per Stasiun (Gabungan)

Versi gabungan (bukan faceted) berikut menumpuk kelima stasiun dalam satu panel dengan zona warna latar belakang, agar pembaca infografis bisa langsung melihat posisi tiap stasiun relatif terhadap ambang BAIK dan TIDAK SEHAT sekaligus membandingkannya satu sama lain.

tren_stasiun_gab <- data_clean %>%
  group_by(tahun, stasiun) %>%
  summarise(rata = mean(max, na.rm = TRUE), .groups = "drop")

warna_stasiun_gab <- c(
  "Lubang Buaya"  = "#e63946",
  "Jagakarsa"     = "#b5651d",
  "Kebon Jeruk"   = "#f4a300",
  "Kelapa Gading" = "#f7c948",
  "Bundaran HI"   = "#2b7cd3"
)

batas_atas_y <- ceiling(max(tren_stasiun_gab$rata) / 10) * 10 + 20

ggplot(tren_stasiun_gab, aes(x = tahun, y = rata, color = stasiun, group = stasiun)) +
  # Zona latar belakang: merah muda (>100, TIDAK SEHAT), hijau muda (<=50, BAIK)
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 100, ymax = batas_atas_y,
           fill = "#e74c3c", alpha = 0.10) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = 0, ymax = 50,
           fill = "#27ae60", alpha = 0.12) +
  geom_hline(yintercept = 100, color = "#e74c3c", linewidth = 0.5, alpha = 0.6) +
  geom_hline(yintercept = 50,  color = "#27ae60", linewidth = 0.5, alpha = 0.6) +
  geom_line(linewidth = 1, alpha = 0.9) +
  geom_point(size = 2.6) +
  scale_color_manual(values = warna_stasiun_gab, name = NULL) +
  scale_x_continuous(breaks = seq(2015, 2024, 1)) +
  scale_y_continuous(limits = c(0, batas_atas_y), breaks = seq(0, batas_atas_y, 50),
                      expand = expansion(mult = c(0, 0))) +
  labs(title = "Tren ISPU per Stasiun (2015–2024)", x = NULL, y = NULL) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title       = element_text(face = "bold", size = 14, hjust = 0.5),
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_blank(),
    legend.position  = "bottom",
    legend.text      = element_text(size = 10)
  ) +
  guides(color = guide_legend(nrow = 2, byrow = TRUE))

Interpretasi: Kelima stasiun bergerak dengan pola musiman yang serempak — naik di 2018–2019, jatuh bersama di 2020 (PSBB), lalu naik lagi di 2021–2022 — mengonfirmasi bahwa pencemaran udara Jakarta dominan dipengaruhi faktor kota-wide (mobilitas dan cuaca regional), bukan semata kondisi lokal tiap wilayah. Namun ada jarak (gap) yang konsisten antar stasiun: Lubang Buaya dan Kebon Jeruk secara sistematis berada di pita atas mendekati atau melewati zona TIDAK SEHAT, sementara Bundaran HI konsisten di pita bawah, kadang mendekati zona BAIK. Jarak yang stabil ini menunjukkan perbedaan karakteristik struktural antar wilayah (kepadatan lalu lintas, tata ruang, tutupan vegetasi) yang tidak hilang meski tren tahunan naik-turun bersamaan.


13 Analisis Korelasi Antar Polutan

cor_polutan <- data_clean %>%
  select(pm25, pm10, o3, no2, so2, co) %>%
  cor(use = "pairwise.complete.obs")

corrplot(cor_polutan,
         method   = "color",
         type     = "upper",
         tl.col   = "black",
         tl.srt   = 45,
         addCoef.col = "white",
         number.cex  = 0.85,
         col      = colorRampPalette(c("#3498db", "white", "#e74c3c"))(200),
         title    = "Matriks Korelasi Antar Polutan Udara (2015–2024)",
         mar      = c(0, 0, 2, 0))

Catatan Metodologi: Korelasi yang melibatkan PM2.5 dihitung dari jumlah observasi yang jauh lebih sedikit (data tersedia konsisten sejak 2021) dibanding korelasi antar polutan lain yang memakai data penuh 2015–2024. Ini wajar secara statistik (pairwise.complete.obs), tetapi berarti angka korelasi PM2.5 secara implisit hanya mencerminkan kondisi 2021–2024, bukan seluruh dekade.

Interpretasi: Matriks korelasi mengungkap hubungan struktural antar polutan yang penting untuk memahami sumber emisi dan proses atmosferik:

  • PM2.5 & PM10 berkorelasi kuat positif (r ≈ 0,8): Keduanya berbagi sumber yang sama — emisi kendaraan bermotor, debu jalanan, dan partikel konstruksi. Pengendalian satu polutan secara otomatis akan mereduksi yang lain.

  • CO berkorelasi moderat dengan PM2.5, tetapi lemah dengan PM10: Karbon monoksida (CO) yang berasal dari pembakaran tidak sempurna kendaraan bermotor tampak lebih terkait dengan partikel halus (PM2.5) dibanding partikel kasar (PM10) — mengindikasikan bahwa PM10 di Jakarta juga banyak berasal dari sumber non-pembakaran langsung (debu jalan, konstruksi), bukan semata emisi kendaraan.

  • O₃ memiliki pola korelasi berbeda dari gas hasil pembakaran langsung (NO₂, SO₂): O₃ berkorelasi negatif moderat dengan NO₂ dan SO₂ — konsisten dengan sifatnya sebagai polutan sekunder yang justru terbentuk dari reaksi NOₓ dan VOC di bawah sinar matahari, dan dapat terdegradasi oleh NO segar dari knalpot kendaraan (efek titrasi). Menariknya, O₃ justru berkorelasi lemah positif dengan PM dan CO — kemungkinan karena hari-hari cerah dengan sinar matahari kuat (pemicu O₃) juga cenderung menjadi hari dengan aktivitas kota yang tinggi, sehingga emisi primer ikut naik bersamaan.

  • SO₂ & NO₂ berkorelasi moderat positif (r ≈ 0,4): Mencerminkan emisi dari pembakaran bahan bakar fosil yang sama (terutama industri dan pembangkit listrik).

Implikasi Analisis: Temuan korelasi ini menyarankan dua strategi berbeda: (1) Pengendalian PM2.5, PM10, dan CO dapat dilakukan bersamaan melalui pengendalian emisi kendaraan, debu jalan, dan konstruksi; (2) Pengendalian O₃ memerlukan pendekatan berbeda — fokus pada prekursor NOₓ dan VOC, bukan sekadar menekan polutan partikel atau CO secara langsung.


14 Hari Terburuk dalam 10 Tahun

top15 <- data_clean %>%
  arrange(desc(max)) %>%
  head(15)

top15 %>%
  select(Tanggal     = tanggal,
         Stasiun     = stasiun,
         `ISPU Maks` = max,
         Polutan     = critical,
         Kategori    = categori) %>%
  kable(caption = "15 Hari dengan ISPU Tertinggi yang Tercatat (2015-2024)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered")) %>%
  row_spec(which(top15$max >= 150),
           bold = TRUE, color = "white", background = "#c0392b")
15 Hari dengan ISPU Tertinggi yang Tercatat (2015-2024)
Tanggal Stasiun ISPU Maks Polutan Kategori
2023-09-28 Lubang Buaya 287 PM25 SANGAT TIDAK SEHAT
2023-09-26 Lubang Buaya 249 PM25 SANGAT TIDAK SEHAT
2019-04-07 Kebon Jeruk 243 O3 SANGAT TIDAK SEHAT
2019-04-08 Kebon Jeruk 235 O3 SANGAT TIDAK SEHAT
2018-07-28 Kebon Jeruk 234 O3 SANGAT TIDAK SEHAT
2018-11-16 Kelapa Gading 234 O3 SANGAT TIDAK SEHAT
2018-09-14 Kelapa Gading 233 O3 SANGAT TIDAK SEHAT
2018-07-16 Kebon Jeruk 222 O3 SANGAT TIDAK SEHAT
2019-11-09 Kebon Jeruk 220 O3 SANGAT TIDAK SEHAT
2018-09-04 Kebon Jeruk 217 O3 SANGAT TIDAK SEHAT
2018-10-17 Kebon Jeruk 217 O3 SANGAT TIDAK SEHAT
2023-09-27 Lubang Buaya 217 PM25 SANGAT TIDAK SEHAT
2019-03-09 Kebon Jeruk 215 O3 SANGAT TIDAK SEHAT
2018-12-12 Kelapa Gading 214 O3 SANGAT TIDAK SEHAT
2018-06-29 Kebon Jeruk 213 O3 SANGAT TIDAK SEHAT

Interpretasi: Tabel ini menampilkan episoda pencemaran terparah yang pernah tercatat selama 10 tahun. Rekor tertinggi terjadi pada 28 September 2023 di stasiun Lubang Buaya dengan ISPU mencapai 287 (kategori SANGAT TIDAK SEHAT), dipicu oleh polutan PM25.

Beberapa pola penting dari 15 episoda terburuk ini:

  • O3 mendominasi sebagai pemicu utama (12 dari 15 episoda terburuk), mengonfirmasi bahwa kondisi pencemaran paling ekstrem di Jakarta umumnya terjadi akibat reaksi fotokimia yang intens di bawah sinar matahari terik dan udara yang relatif minim hujan pembersih.

  • Kebon Jeruk menyumbang 9 dari 15 episoda terburuk, menandakan bahwa kondisi atmosferik dan geografis lokal stasiun ini (sirkulasi udara terbatas, kepadatan emisi) membuatnya rentan terhadap penumpukan polutan pada kondisi cuaca tertentu.

  • Sebanyak 0 dari 15 episoda terburuk mencapai kategori BERBAHAYA (ISPU ≥ 300) — kondisi darurat udara di mana seluruh populasi berisiko, bukan hanya kelompok sensitif. Episoda seperti ini, meski jarang, perlu menjadi pemicu protokol darurat kualitas udara (misalnya penutupan sekolah atau pembatasan aktivitas luar ruang).


15 Simulasi Dampak Kebijakan Mobilitas

ispu_2024_val <- filter(trend_tahun, tahun == 2024)$rata_ispu

skenario <- tibble(
  skenario  = c("Kondisi Aktual 2024",
                "Skenario: Pembatasan Mobilitas\n(setara efek COVID-2020)",
                "Skenario: Target Kualitas BAIK"),
  rata_ispu = c(
    ispu_2024_val,
    ispu_2024_val * (1 - penurunan_pct / 100),
    50
  ),
  warna     = c("#c0392b", "#f39c12", "#27ae60")
)

ggplot(skenario, aes(reorder(skenario, -rata_ispu), rata_ispu, fill = warna)) +
  geom_col(width = 0.55, show.legend = FALSE) +
  geom_text(aes(label = round(rata_ispu, 1)),
            vjust = -0.5, fontface = "bold", size = 6) +
  geom_hline(yintercept = 50, linetype = "dashed",
             color = "#27ae60", linewidth = 1.1) +
  geom_hline(yintercept = 100, linetype = "dashed",
             color = "#e74c3c", linewidth = 0.8, alpha = 0.6) +
  annotate("text", x = 0.55, y = 52,
           label = "Batas Target BAIK (≤50)",
           color = "#27ae60", fontface = "italic", size = 3.8, hjust = 0) +
  scale_fill_identity() +
  scale_y_continuous(limits = c(0, ispu_2024_val + 15)) +
  labs(x = NULL, y = "Rata-rata ISPU",
       title    = "Simulasi Dampak Kebijakan terhadap ISPU Jakarta",
       subtitle = paste0("Basis: kondisi 2024 (ISPU ", round(ispu_2024_val, 1),
                         ") | Potensi penurunan ", round(penurunan_pct, 0),
                         "% dari pembatasan mobilitas serius")) +
  theme_minimal(base_size = 13) +
  theme(panel.grid.minor  = element_blank(),
        axis.text.x       = element_text(size = 10))

Interpretasi: Simulasi ini menggunakan besaran penurunan ISPU yang teramati saat PSBB 2020 sebagai proksi efek kebijakan pembatasan mobilitas. Hasilnya menunjukkan bahwa bahkan dengan skenario “pembatasan mobilitas setara PSBB”, ISPU Jakarta masih belum mencapai target BAIK (≤50) — menandakan bahwa pembatasan kendaraan saja tidak cukup.

Untuk mencapai kategori BAIK secara konsisten, Jakarta membutuhkan: 1. Transformasi sistem transportasi: Ekspansi masif transportasi publik, elektrifikasi kendaraan, dan pengendalian emisi ketat. 2. Penghijauan kota: Peningkatan ruang terbuka hijau sebagai filter alami polutan. 3. Transisi energi industri: Pengurangan emisi dari sektor non-transportasi (pembangkit listrik, industri, konstruksi). 4. Regulasi berkelanjutan: Standar emisi Euro 5/6 untuk kendaraan baru dan pensiun kendaraan tua berpolusi.

Catatan Metodologi: Simulasi ini bersifat ilustratif dan mengasumsikan hubungan linear antara pembatasan mobilitas dan penurunan ISPU. Kondisi aktual lebih kompleks karena melibatkan faktor meteorologis, komposisi armada kendaraan, dan sumber emisi non-kendaraan yang sulit dikontrol.


16 Visualisasi untuk Infografis

Dua grafik berikut dirancang khusus dengan gaya flat dan warna semantik (bukan gaya laporan analitis di atas) agar siap dipakai langsung sebagai aset visual infografis.

16.1 Bar Chart: Perbandingan Musim

musim_infografis <- data_clean %>%
  mutate(musim_label = ifelse(bulan_n %in% 5:10, "Kemarau", "Hujan")) %>%
  group_by(musim_label) %>%
  summarise(rata_ispu = round(mean(max, na.rm = TRUE), 1), .groups = "drop") %>%
  mutate(musim_label = factor(musim_label, levels = c("Kemarau", "Hujan")))

warna_musim <- c("Kemarau" = "#EF9F27", "Hujan" = "#639922")

p_musim_info <- ggplot(musim_infografis, aes(x = musim_label, y = rata_ispu, fill = musim_label)) +
  geom_col(width = 0.55, show.legend = FALSE) +
  geom_text(aes(label = rata_ispu), vjust = -0.6, size = 6, fontface = "bold",
            color = "#2C2C2A") +
  scale_fill_manual(values = warna_musim) +
  scale_y_continuous(limits = c(0, max(musim_infografis$rata_ispu) * 1.25),
                      expand = expansion(mult = c(0, 0))) +
  labs(x = NULL, y = NULL, title = NULL) +
  theme_minimal(base_size = 13) +
  theme(
    panel.grid          = element_blank(),
    axis.text.y         = element_blank(),
    axis.text.x         = element_text(size = 13, face = "bold", color = "#2C2C2A"),
    axis.ticks          = element_blank(),
    plot.margin         = margin(10, 10, 10, 10)
  )

p_musim_info

Interpretasi: musim kemarau (Mei–Oktober) rata-rata ISPU-nya jauh lebih tinggi dibanding musim hujan — selisih sekitar 18 poin, mengindikasikan kondisi atmosfer kering dan minim hujan mempercepat akumulasi polutan serta memicu pembentukan O₃ fotokimia.

16.2 Donut Chart: Polutan Dominan

polutan_infografis <- data_clean %>%
  mutate(critical = case_when(
    critical %in% c("PM2,5", "PM25", "PM 2.5") ~ "PM2.5",
    TRUE ~ critical
  )) %>%
  count(critical) %>%
  mutate(persen = round(n / sum(n) * 100, 1)) %>%
  arrange(desc(persen)) %>%
  mutate(critical = factor(critical, levels = critical))

warna_polutan <- c(
  "O3"   = "#EF9F27",
  "PM2.5" = "#E24B4A",
  "PM10" = "#888780",
  "SO2"  = "#B4B2A9",
  "CO"   = "#D3D1C7",
  "NO2"  = "#F1EFE8"
)

p_donut_info <- ggplot(polutan_infografis, aes(x = 2, y = persen, fill = critical)) +
  geom_col(width = 1, color = "white", linewidth = 1.5) +
  coord_polar(theta = "y") +
  xlim(0.5, 2.5) +
  scale_fill_manual(values = warna_polutan) +
  theme_void(base_size = 13) +
  theme(legend.position = "none") +
  # Label persen untuk 3 polutan teratas saja (batasi noise visual)
  geom_text(
    data = filter(polutan_infografis, row_number() <= 3),
    aes(label = paste0(critical, "\n", persen, "%"), x = 2.5),
    position = position_stack(vjust = 0.5),
    size = 4, fontface = "bold", color = "#2C2C2A"
  )

p_donut_info

Interpretasi: O₃ (38.2%) dan PM2.5 (30.7%) bersama-sama menyumbang hampir 70% dari seluruh episoda ISPU tertinggi — keduanya polutan sekunder/halus yang lebih sulit dikendalikan hanya dengan mengurangi sumber emisi langsung, berbeda dari PM10 (24.7%) yang lebih terkait debu dan partikel kasar.

16.3 Perbandingan: Sebelum vs Sesudah PM2.5 Dipantau

# Normalisasi penulisan PM2.5 yang tidak konsisten di data mentah
data_pm25norm <- data_clean %>%
  mutate(critical = case_when(
    critical %in% c("PM25", "PM2,5") ~ "PM2.5",
    TRUE ~ critical
  ))

buat_ringkasan_polutan <- function(df, label_kategori_lain = "Lainnya") {
  df %>%
    count(critical) %>%
    mutate(persen = n / sum(n) * 100) %>%
    arrange(desc(persen)) %>%
    mutate(
      kelompok = ifelse(row_number() <= 3, critical, label_kategori_lain)
    ) %>%
    group_by(kelompok) %>%
    summarise(persen = sum(persen), .groups = "drop") %>%
    arrange(desc(persen)) %>%
    mutate(kelompok = factor(kelompok, levels = kelompok))
}

polutan_pra  <- data_pm25norm %>% filter(tahun >= 2015, tahun <= 2020) %>% buat_ringkasan_polutan()
polutan_pasca <- data_pm25norm %>% filter(tahun >= 2021, tahun <= 2024) %>% buat_ringkasan_polutan()

persen_pm25_pasca <- round(polutan_pasca$persen[polutan_pasca$kelompok == "PM2.5"], 1)
warna_banding <- c(
  "O3"      = "#6c5ce7", "PM2.5" = "#6c5ce7",
  "PM10"    = "#2980b9",
  "CO"      = "#e67e22", "SO2" = "#e67e22", "CO+SO2" = "#e67e22",
  "Lainnya" = "#bdc3c7"
)

buat_donut <- function(df, judul) {
  ggplot(df, aes(x = 2, y = persen, fill = kelompok)) +
    geom_col(width = 1, color = "white", linewidth = 1.2) +
    coord_polar(theta = "y") +
    xlim(0.5, 2.5) +
    scale_fill_manual(values = warna_banding) +
    labs(title = judul) +
    theme_void(base_size = 12) +
    theme(
      legend.position = "top",
      legend.title    = element_blank(),
      legend.text     = element_text(size = 10),
      plot.title      = element_text(size = 11, face = "bold", hjust = 0, margin = margin(b = 8))
    ) +
    guides(fill = guide_legend(nrow = 1)) +
    geom_text(aes(label = paste0(kelompok, " ", round(persen, 1), "%"), x = 2.5),
              position = position_stack(vjust = 0.5), size = 3.3, color = "#2c3e50")
}

p_donut_pra  <- buat_donut(polutan_pra,  "Polutan kritis 2015–2020 (PM2.5 belum konsisten dipantau)")
p_donut_pasca <- buat_donut(polutan_pasca, "Polutan kritis 2021–2024 (PM2.5 mulai dipantau)")

p_donut_pra + p_donut_pasca

O₃ dominan sebagian karena PM2.5 belum diukur konsisten — bukan karena PM2.5 tidak ada. Ini buta data, bukan buta polutan.

Begitu PM2.5 mulai diukur luas sejak 2021, ia langsung mendominasi 83.4% hari — polutan yang selama ini nyaris tidak terlihat di data ternyata yang paling sering jadi penyebab ISPU tertinggi.

Catatan Metodologi: Perbandingan ini memakai batas 2021 karena itu titik saat cakupan sensor PM2.5 meluas ke seluruh stasiun (lihat catatan keterbatasan data PM2.5 di bagian “Fokus PM2.5” sebelumnya). Perbedaan persentase antar periode karena itu mencerminkan gabungan dua hal: perluasan cakupan sensor DAN kemungkinan perubahan pola polusi aktual — keduanya tidak bisa dipisahkan sepenuhnya dari data ini.


17 Kesimpulan dan Rekomendasi

17.1 Kesimpulan Utama

1. Tidak ada perbaikan struktural dalam 10 tahun. Meski terjadi fluktuasi, rata-rata ISPU Jakarta tidak menunjukkan tren penurunan yang konsisten dari 2015 hingga 2024. Puncak tertinggi tercatat pada 2019 (rata-rata ISPU 80.9) — jauh setelah anomali penurunan akibat pandemi di 2020 — menandakan pola pencemaran yang berulang tanpa solusi permanen.

2. O₃ adalah polutan utama — bukan PM. Dominasi ozon menandakan perubahan karakter pencemaran udara Jakarta dari berbasis partikel ke berbasis kimia-fotokimia. Strategi pengendalian harus beradaptasi dengan tantangan ini.

3. COVID-19 membuktikan kausalitas. Penurunan 22% ISPU saat PSBB adalah bukti paling kuat bahwa aktivitas manusia — terutama transportasi — adalah penyebab dominan pencemaran udara Jakarta.

4. Musim kemarau adalah periode kritis. Perbedaan 18 poin ISPU antara musim kemarau dan hujan memberikan baseline untuk sistem peringatan dini dan intensifikasi kebijakan musiman.

5. Lubang Buaya membutuhkan perhatian khusus. Dengan rata-rata ISPU tertinggi (76.4), diikuti oleh Kebon Jeruk (76.1), kedua wilayah ini memerlukan intervensi yang lebih intensif dibanding kawasan lain.

17.2 Rekomendasi Kebijakan

Rekomendasi Kebijakan Berdasarkan Temuan Analisis
Prioritas Rekomendasi Dampak Perkiraan
Tinggi Percepat elektrifikasi angkutan umum & kendaraan pribadi Penurunan CO, PM, NOₓ signifikan
Tinggi Terapkan standar emisi Euro 5/6 dan pensiun kendaraan berpolusi tinggi Penurunan emisi partikel & SO₂
Menengah Perkuat sistem pemantauan real-time PM2.5 di seluruh stasiun SPKU Data lebih akurat untuk kebijakan berbasis bukti
Menengah Kampanye peringatan udara berbasis musim (intensif Mei–Oktober) Pengurangan paparan kelompok sensitif
Jangka Panjang Integrasi kebijakan tata ruang: perlindungan RTH dan sabuk hijau kota Filter alami & penyerapan polutan jangka panjang

Penutup: Data tidak berbohong. Selama 10 tahun, Jakarta telah mengumpulkan ribuan hari bukti bahwa udara yang dihirup warganya tidak memenuhi standar kesehatan. Pandemi 2020 memberi kita sebentar napas — dalam arti literal. Tantangannya kini adalah mentransfer “efek pandemi” ke dalam “efek kebijakan” yang permanen, tanpa harus menunggu bencana lain untuk membuktikan bahwa perubahan itu mungkin dan perlu.


Sumber Data: Dinas Lingkungan Hidup (DLH) DKI Jakarta — Data ISPU dari 5 Stasiun Pemantauan Kualitas Udara (SPKU): Bundaran HI, Kelapa Gading, Jagakarsa, Lubang Buaya, dan Kebon Jeruk. Periode analisis: 1 Januari 2015 – 31 Desember 2024 (disaring dari file gabungan data_ispu_dki_2010_2024.xlsx yang aslinya mencakup 2010–2024), total 16.543 hari observasi valid.
Analisis oleh: Fadly Syabani | Dihasilkan pada: 08 September 2026