Rasyid Abdurrahman - 140610230007
Nabiel Alfallah Herdiana - 140610230018
Farhan Alkarimi - 140610230028
Human Immunodeficiency Virus (HIV) masih menjadi permasalahan kesehatan masyarakat global, termasuk di Indonesia. Provinsi Jawa Barat merupakan salah satu wilayah dengan jumlah kasus HIV tertinggi di Indonesia. Kepadatan penduduk, urbanisasi, serta dinamika sosial ekonomi menjadi faktor yang berpotensi memperluas penyebaran penyakit ini. Penelitian ini bertujuan untuk menganalisis distribusi spasial kasus HIV di Jawa Barat periode 2019β2023, menghitung prevalensi penyakit per kabupaten/kota, serta mengevaluasi perbedaan kasus berdasarkan jenis kelamin.
Penelitian ini menggunakan pendekatan studi epidemiologi deskriptif dengan rancangan cross-sectional. Data sekunder diperoleh dari portal open data resmi Pemerintah Provinsi Jawa Barat. Analisis dilakukan secara deskriptif dan spasial dengan menghitung prevalensi HIV per 100.000 penduduk, membuat peta tematik (choropleth map), serta menghitung Prevalence Odds Ratio (POR) untuk membandingkan prevalensi antara laki-laki dan perempuan.
Hasil penelitian menunjukkan bahwa prevalensi HIV di Jawa Barat mengalami tren peningkatan dari tahun 2019 hingga 2023, dengan beberapa wilayah seperti Kota Cirebon, Kota Bandung, dan Kota Sukabumi memiliki tingkat prevalensi tertinggi. Analisis berdasarkan gender menunjukkan bahwa laki-laki memiliki risiko prevalensi HIV sekitar 2,9 kali lebih tinggi dibandingkan perempuan (POR = 2,865; CI 95%: 2,737β2,999). Pola spasial memperlihatkan konsentrasi kasus di wilayah perkotaan padat penduduk, sejalan dengan teori epidemiologi yang menekankan pengaruh lingkungan sosial terhadap penyebaran penyakit.
Kesimpulannya, HIV di Jawa Barat memiliki karakteristik epidemiologi yang spasial, dinamis, dan dipengaruhi faktor demografi. Hasil ini menegaskan pentingnya pendekatan berbasis wilayah dalam upaya pengendalian HIV, termasuk peningkatan surveilans, edukasi masyarakat, serta program intervensi di daerah berisiko tinggi.
Kata kunci: HIV, epidemiologi, prevalensi, pemetaan penyakit, Jawa Barat
Human Immunodeficiency Virus (HIV) merupakan salah satu masalah kesehatan masyarakat yang hingga kini masih menjadi tantangan global. Virus ini menyerang sistem kekebalan tubuh manusia dan, apabila tidak ditangani, dapat berkembang menjadi Acquired Immune Deficiency Syndrome (AIDS) yang meningkatkan kerentanan terhadap infeksi oportunistik serta angka kematian (WHO, 2023). Meskipun perkembangan pengobatan antiretroviral (ARV) telah memperpanjang harapan hidup orang dengan HIV, angka kejadian baru masih tinggi di banyak negara, termasuk Indonesia.
Di Indonesia, epidemi HIV menunjukkan tren yang fluktuatif, dengan penyebaran kasus yang semakin meluas ke berbagai kelompok populasi dan wilayah. Berdasarkan laporan Kementerian Kesehatan RI (2023), jumlah kasus HIV yang dilaporkan hingga tahun 2022 mencapai lebih dari 600.000, dengan kecenderungan peningkatan di provinsi-provinsi berpenduduk besar dan padat. Jawa Barat merupakan salah satu provinsi dengan jumlah kasus HIV tertinggi di Indonesia, berkontribusi signifikan terhadap total kasus nasional.
Kondisi ini tidak terlepas dari karakteristik demografis dan sosial Jawa Barat. Sebagai provinsi dengan penduduk lebih dari 48 juta jiwa, Jawa Barat memiliki dinamika sosial ekonomi, urbanisasi, dan mobilitas penduduk yang sangat tinggi (BPS, 2023). Kota-kota besar seperti Bandung, Bekasi, dan Bogor menjadi pusat aktivitas ekonomi sekaligus lokasi dengan tingkat interaksi sosial yang padat. Faktor-faktor tersebut dapat meningkatkan peluang penularan HIV, terutama di kelompok berisiko tinggi. Selain itu, ketimpangan akses terhadap layanan kesehatan, stigma sosial, dan rendahnya tingkat kesadaran masyarakat juga memperburuk upaya pencegahan dan deteksi dini kasus HIV.
Dalam konteks epidemiologi, penting untuk memahami pola distribusi spasial penyakit sebagai dasar perencanaan intervensi kesehatan masyarakat. Pemetaan kasus HIV (disease mapping) memungkinkan identifikasi wilayah dengan konsentrasi kasus tinggi (hotspot) serta analisis pola penyebaran penyakit di antara kabupaten/kota (Lawson, 2018). Melalui pendekatan ini, pemerintah dapat menetapkan prioritas wilayah dan sumber daya untuk pencegahan dan pengendalian penyakit.
Selain aspek geografis, faktor demografi seperti jenis kelamin berperan penting dalam epidemiologi HIV. Kasus HIV di Indonesia sebagian besar ditemukan pada kelompok usia produktif 25β49 tahun, yang memiliki aktivitas sosial dan ekonomi tinggi (Kemenkes RI, 2023). Di sisi lain, perbedaan gender juga memengaruhi risiko penularan, baik karena faktor biologis maupun sosialβmisalnya, perbedaan dalam perilaku seksual, penggunaan kondom, serta akses terhadap layanan kesehatan (UNAIDS, 2022).
Berdasarkan latar belakang tersebut, analisis terhadap kasus HIV di Jawa Barat periode 2019β2020 menjadi penting untuk dilakukan. Penelitian ini bertujuan untuk menggambarkan distribusi spasial kasus HIV, menghitung prevalensi penyakit di tingkat kabupaten/kota, serta menganalisis hubungan antara karakteristik demografi (gender) dengan kejadian HIV. Hasilnya diharapkan dapat memberikan dasar ilmiah bagi pemerintah daerah dalam perencanaan program pencegahan HIV yang lebih tepat sasaran dan berbasis bukti (evidence-based policy).
Berdasarkan latar belakang tersebut, permasalahan yang dikaji dalam penelitian ini dapat dirumuskan sebagai berikut:
Bagaimana gambaran umum situasi epidemiologi HIV di Provinsi Jawa Barat selama periode 2019 - 2023, meliputi jumlah, jumlah penduduk, serta tingkat prevalensinya?
Bagaimana pola spasial penyebaran HIV di Jawa Barat berdasarkan hasil pemetaan kasus (disease mapping)?
Bagaimana perbandingan HIV menurut jenis kelamin (gender) pada tingkat provinsi maupun kabupaten/kota?
Mengetahui dan menganalisis distribusi spasial serta karakteristik epidemiologis HIV di Provinsi Jawa Barat selama periode 2019β2023 berdasarkan faktor wilayah dan jenis kelamin.
Menghitung prevalensi HIV pada masing-masing kabupaten/kota di Jawa Barat tahun 2019β2023.
Menganalisis pola distribusi spasial (disease mapping) untuk mengidentifikasi wilayah dengan prevalensi HIV tinggi.
Memberikan interpretasi epidemiologis dan rekomendasi kebijakan berbasis data untuk mendukung upaya pencegahan dan pengendalian HIV di tingkat daerah.
Epidemiologi adalah ilmu yang mempelajari penyebaran dan determinan dari kejagian penyakit atau masalah kesehatan dalam suatu populasi . Selain itu juga belajar mengenai penerapannya dalam mengendalikan masalah tersebut.
Dalam studi epidemiologi, ada konsep agentβhostβenvironment yang menggambarkan bahwa terjadinya suatu penyakit bergantung pada interaksi antara tiga komponen utama.
Faktor faktor ini saling berinteraksi dan menentukan tingkat kerentanan individu terhadap infeksi.
Ukuran asosiasi digunakan dalam epidemiologi untuk mengetahui hubungan antara faktor risiko (exposure) dengan kejadian penyakit (outcome). Dalam penelitian ini, ukuran asosiasi digunakan untuk menganalisis hubungan antara jenis kelamin dengan kejadian HIV. Adapun ukuran yang digunakan pada kasus ini adalah
Prevalensi
Proporsi individu yang memiliki penyakit tertentu (kasus lama + kasus baru) pada suatu titik waktu atau pertiode tertentu. Prevalensi menggambarkan beban penyakit pada populasi dengan rumus
\[ Prevaleni = \frac{Kasus(lama+baru)}{Total Populasi} \]
Sebagai contoh jika disebuah kota berpenduduk 10 ribu orang terdapat 200 penderita, maka prevalensinya adalah 2%.
Berdasarkan tujuan analisis dan sifat data, penelitian ini menggunakan desain studi observasional yang mana, peneliti disini tidak memberikan intervensi terhadap data, melainkan hanya mengamati hubungan antara paparan dan kejadian penyakit.
Pendekatan yang digunakan adalah pendekatan cross-sectional study dimana mengukur paparan dan outcome pada satu titik waktu. Tujuannya adalah mengestimasi prevalensi dan mengekplorasi asosiasi awal. Pada penelitian ini, dilakukan pemetaan terhadap prevalensi dan distribusi kasus di berbagai wilayah di Jawa Barat. Pendekatan ini menggunakan data yang dikumpulkan pada satu titik waktu tertentu, misalnya tahun 2019 dan 2023. Keuntungannya adalah efisien dalam waktu dan biaya, serta cocok untuk menggambarkan situasi epidemiologi terkini.
Disease mapping adalah epresentasi spasial (peta) dari distribusi suatu penyakit, biasanya menunjukkan tingkat prevalensi, insidensi atau resiko penyakit pada daerah tertentu. Tujuannya adalah untuk mengidentifikasi distribusi geografis penyakit, melihat pola sebaran penyakit, mendeteksi area resiko tinggi, membandingkan wilayah sehingga menjadi dasar perencanaan kebijakan kesehatan.
Disease mapping memberikan visualisasi kompleks menjadi sederhana, dapat memberikan intervensi yang lebih tepat sasaran.
Penelitian ini merupakan studi epidemiologi deskriptif dengan pendekatan cross-sectional. Pendekatan ini digunakan untuk menggambarkan situasi HIV di Provinsi Jawa Barat pada periode 2019β2023 berdasarkan data kasus dan gender. Analisis dilakukan untuk mengetahui distribusi spasial, menghitung prevalensi, serta menganalisis hubungan antara variabel demografi dengan kejadian HIV.
Penelitian ini menggunakan data sekunder yang diperoleh dari instansi resmi open data Jawa Barat
Tabel berikut merangkum variabel yang digunakan dalam penelitian ini :
| Jenis Variabel | Nama Variabel | Satuan / Kategori | Keterangan |
|---|---|---|---|
| Variabel utama | Jumlah kasus HIV | Kasus (orang) | Total kasus HIV yang dilaporkan per kabupaten/kota |
| Variabel pendukung | Jumlah penduduk | Jiwa | Jumlah total penduduk di wilayah yang sama |
| Variabel demografi | Jenis kelamin | Laki-laki / Perempuan | Berdasarkan data laporan kasus |
Analisis data dilakukan secara deskriptif untuk menggambarkan situasi HIV di Provinsi Jawa Barat periode 2019β2023 berdasarkan tahun, tempat dan variabel pembeda (gender). Analisis dilakukan dalam tiga tahapan utama, sesuai dengan rumusan masalah penelitian, yaitu:
Analisis ini bertujuan untuk menggambarkan beban penyakit HIV di
Provinsi Jawa Barat secara keseluruhan dan per kabupaten/kota.
Langkah-langkah analisis meliputi:
\[ Prevaleni = \frac{Kasus(lama+baru)}{Total Populasi} \]
Menyajikan hasil dalam bentuk tabel dan grafik tren untuk memperlihatkan perubahan prevalensi dari tahun 2019 hingga 2023.
Mengidentifikasi wilayah dengan prevalensi tertinggi dan terendah setiap tahun.
Analisis ini memberikan gambaran umum mengenai beban dan tren penyakit HIV di Provinsi Jawa Barat berdasarkan berbagai dimensi.
Analisis spasial dilakukan untuk menggambarkan pola geografis distribusi HIV antarwilayah di Provinsi Jawa Barat. Tahapan yang dilakukan adalah :
Menggabungkan data kasus HIV dan jumlah penduduk dengan peta batas administrasi kabupaten/kota (format shapefile)
Membuat peta tematik (choropleth map) untuk menampilkan variasi spasial prevalensi HIV antarwilayah dan antar tahun.
Melakukan analisis deskriptif spasial untuk mengidentifikasi klaster wilayah dengan prevalensi tinggi.
Hasil pemetaan ini membantu menunjukkan perbedaan beban penyakit antar daerah serta membantu proses kebijakan berbasis data.
Analisis ini bertujuan untuk menilai perbedaan proporsi penderita HIV antara laki-laki dan perempuan. Langkah-langkah yang dilakukan meliputi:
Menghitung prevalensi HIV menurut jenis kelamin dengan membandingkan jumlah kasus HIV laki-laki dan perempuan terhadap total penduduk laki-laki dan perempuan di setiap tahun pengamatan.
Menghitung prevalensi odds ratio untuk mengukur perbandingan peluang prevalensi HIV antar gender dengan formula
\[ POR = \frac{\dfrac{a/b}{c/d}}{} = \frac{ad}{bc} \]
Jika nilai dari POR lebih dari 1, maka menandakan bahwa kelompok laki laki memiliki peluang prevalensi lebih tinggi dibandingkan perempuan.
Hasil interpretasi dibandingkan dengan teori epidemiologi (konsep agentβhostβenvironment) serta laporan resmi seperti Profil Kesehatan Indonesia dan Profil Kesehatan Jawa Barat untuk memperkuat pembahasan. Untuk penyajian hasil, kami menggunakan R shiny yang nanti dikonversi menjadi dashboard interaktif yang dapat diakses.
Tahapan kegiatan penelitian digambarkan dalam alur berikut:
Tahapan ini memastikan proses analisis berjalan sistematis, mulai dari pengumpulan data sekunder hingga interpretasi hasil yang siap dijadikan dasar rekomendasi kebijakan kesehatan.
Hasil analisis ini menyajikan deskripsi umum mengenai perkembangan kasus HIV di Jawa Barat selama periode pengamatan. Berikut adalah ringkasan umum dan plot dasar untuk tahun 2023 di Provinsi Jawa Barat
Dari kedua output tersebut dapat dilihat bahwa terdapat satu daerah yang memiliki jumlah kasus yang sangat berbeda dengan wilayah lainnya. Dapat dibuktikan dari boxplot yang terdapat outlier. Selain itu dapat dilihat pula nilai rata-rata kasus di kabupaten/kota Jawa Barat.
Selanjutnya adalah menghitung prevalensi per kabupaten kota di Jawa Barat dengan formula pada bab sebelumnya.
library(googlesheets4)
## Warning: package 'googlesheets4' was built under R version 4.5.2
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
url <- "https://docs.google.com/spreadsheets/d/1I704mG49SZIipMUUyBvPQQedSMCz6Mz8wLqvlRIoqPk/edit?usp=sharing"
data <- read_sheet(url)
## ! Using an auto-discovered, cached token.
## To suppress this message, modify your code or options to clearly consent to
## the use of a cached token.
## See gargle's "Non-interactive auth" vignette for more details:
## <https://gargle.r-lib.org/articles/non-interactive-auth.html>
## βΉ The googlesheets4 package is using a cached token for
## 'farhanalkaribi@gmail.com'.
## β Reading from "HIV".
## β Range 'Sheet1'.
head(data)
## # A tibble: 6 Γ 3
## `Kabupaten/Kota` `Jumlah Kasus` `Jumlah Penduduk`
## <chr> <dbl> <dbl>
## 1 KABUPATEN BOGOR 830 56.3
## 2 KABUPATEN SUKABUMI 294 28.0
## 3 KABUPATEN CIANJUR 211 25.6
## 4 KABUPATEN BANDUNG 534 37.2
## 5 KABUPATEN GARUT 251 26.8
## 6 KABUPATEN TASIKMALAYA 89 19.1
hasil <- data %>%
mutate(Prevalensi = `Jumlah Kasus` / `Jumlah Penduduk`)
print(hasil)
## # A tibble: 27 Γ 4
## `Kabupaten/Kota` `Jumlah Kasus` `Jumlah Penduduk` Prevalensi
## <chr> <dbl> <dbl> <dbl>
## 1 KABUPATEN BOGOR 830 56.3 14.8
## 2 KABUPATEN SUKABUMI 294 28.0 10.5
## 3 KABUPATEN CIANJUR 211 25.6 8.25
## 4 KABUPATEN BANDUNG 534 37.2 14.4
## 5 KABUPATEN GARUT 251 26.8 9.35
## 6 KABUPATEN TASIKMALAYA 89 19.1 4.67
## 7 KABUPATEN CIAMIS 112 12.5 8.95
## 8 KABUPATEN KUNINGAN 165 12.0 13.7
## 9 KABUPATEN CIREBON 364 23.6 15.4
## 10 KABUPATEN MAJALENGKA 259 13.4 19.3
## # βΉ 17 more rows
Hasil tersebut dapat dilihat pada gambar dibawah. Gambar dibawha merupakan hasil analisis prevalensi untuk setiap kabupaten/kota. Berikut adalah contoh tabel hasil perhitungan prevalensi per kabupaten/kota di Jawa Barat.
Tabel menunjukkan hasil perhitungan prevalensi untuk setiap kabupaten atau kota yang ada di Jawa Barat. Sebagai contoh pada Kabupaten Bandung dengan prevalensi 14,24 dan Kota Cirebon yang 5 kali lebih tinggi dengan 88.5. Nilai ini mengartikan bahwa dari 100 ribu orang, terdapat 88,5 orang terpapar HIV di Kota Cirebon.
Dari analisis dapat dilihat tren prevalensi HIV di Provinsi Jawat Barat dalam periode waktu.
Pada plot tersebut terlihat bahwa prevalensi HIV mengalami tren meningkat selama periode 2019 - 2023. Pada tahun 2019 berada di sekitar 10 per 100.000 penduduk, sempat turun pada 2020 hingga 2021, namun naik tajam pada tahun 2022 dan 2023. Hal ini dapat dipartikan adanya peningkatan beban penyakit. Peningkatan ini bisa disebabkan oleh peningkatan penularan baru, meningkatnya penyebaran HIV dan lainnya.
Selain itu, kami juga menyediakan fitur untuk membandingkan prevalensi antar kabupaten/kota. Sebagai contoh disini kamu membandingkan antara Kabupaten Bandung dan Bekasi selama 5 tahun periode.
Dapat dilihat bahwa pada tahun 2019 - 2020, kedua wilayah relatif rendah dan sama, namun di tahun selanjutnya terjadi kenaikan tajam di Kabupaten Bekasi dari 8 menjadi 26 penduduk per 100.000. Sedikit Berbeda dengan Bandung yang meningkat juga namun dengan tren yang lebih moderat, yakni 14 per 100.000 penduduk.
Plot ini memberikan gambaran perbandingan prevalensi antar kabupaten kota. Pola perbedaan ini memperlihatkan pentingnya pendekatan wilayah dalam upaya penganggulangan HIV.
Kami juga melakukan perbandingan prevalensi berdasarkan gender. Sebagai contoh kami melakukan perbandingan pada tahun 2023 dengan hasil
Nilai dari Prevalensi Ratio adalah 2,865 dengan interval 2,737 - 2,999. Nilai ini menunjukkan bahwa kelompok paparan laki laki memliki prevalensi HIV sekitar 2,9 kali lebih tinggi daripada kelompok terpapar perempuan. Interval kepercayaan pun tidak mendekati 1 sehingga dapat disimpulkan juga bahwa terdapat perbedaan yang signifikan antara kasus HIV terhadap laki laki dengan perempuan.
Dari penelitian ini diperoleh peta tematik (choropleth map) prevalensi HIV per 100.000 penduduk di Jawa Barat.
Peta ini menggambarkan variasi spasial HIV di setiap kabupaten/kota. Berdasarkan peta dapat dilihat bahwa penyebaran HIV tidak merata. Konsentrasi kasus cenderung meningkat di kawasan perkotaan. Dapat dilihat bahwa terdapat beberapa kota dengan prevalensi tinggi seperti Kota Cirebon, Kota Bandung dan Kota Sukabumi. Hal ini konsisten dengan konsep epidemiologi spasial dimana distribusi penyakit sangat dipengaruhi lingkungan sosial.
Pemetaan ini memberikan hasil yang dapat menjadi dasar untuk menetapkan wilayah prioritas intervensi HIV, menyesuaikan alokasi layanan kesehatan dan mengembangkan program pencegahan.
Penelitian ini dilakukan dengan analisis spasial terhadap data HIV di Provinsi Jawa Barat periode 2019-2023 dan dapat dilihat beberapa hal dan ditarik kesimpulan sebagai berikut :
Situasi umum HIV di Jawa Barat
Prevalensi HIV di Jawa Barat menunjukkan tren meningkat dari tahun 2019 hingga 2023. Kenaikan ini mengindikasikan bawha beban penyakit HIV di Jawa Barat masih tinggi dan butuh perhatian serius terutama pada wilayah yang padat penduduk.
Tren Kasus HIV
Terdapat peningkatan yang signfikan dalam prevalensi HIV pada dua tahun terakhir pengamatan (2022-2023). Peningkatan ini dapat disebabkan oleh kombinasi antara peningkatan pelaporan kasus atau mungkin bertambahnya kasus penularan baru di masyarakat.
Perbedaan Berdasarkan Gender
Analisis ukuran asosisasi menunjukkan bahwa laki laki memiliki risiko prevalensi HIV lebih tinggi 2,9 kali daripada perempuan. Nilai ini cukup signifikan untuk menyatakan bahwa prevalensi laki laki berbeda dengan prevalensi perempuan. Hal ini menunjukkan bahwa gender merupaakn salah satu faktor yang berhubungan kuat dengan risiko HIV.
Disease Mapping
Hasil pemetaan menunjukkan bahwa distribusi HIV tidak merata di Jawa Barat. Wilayah dengan prevalensi tinggi biasanya terdeteksi pada wilayah padat penduduk seperti di wilayah perkotaan. Sesuai dengan konsep epidemiologi yang menyatakan bahwa lingkungan sosial memberikan pengaruh terhadap penyebaran penyakit.
Secara keseluruhan, hasil penelitian ini menegaskan bahwa HIV di Jawa Barat merupakan masalah kesehatan masyarakat yang bersifat dinamis, spasial, dan demografis, sehingga penanggulangannya perlu melibatkan pendekatan berbasis wilayah dan kelompok berisiko.
Berdasarkan hasil temuan dan interpretasi epidemiologis disarankan bagi pemerintah dan dinas kesehatan untuk menetapkan wilayah prioritas pada daerah dengan prevalensi tinggi seeprti Kota Cirebon, Kota Bandung dan Bekasi. Kemudian memperkuat program pencegahan seperti edukasi dan kegiatan positif lainnya. Lebih gencar terhadap surveilans berkelanjutan demi memantau tren HIV dan membantu meningkatkan kesadaran terhadap pentingnya menjaga diri terutama bagi individu dengna perilaku berisiko.
Hasil penelitian ini diharapkan dapat menjadi bahan rujukan bagi
perencanaan program penanggulangan HIV di Jawa Barat, serta memberikan
gambaran ilmiah mengenai pola, tren, dan determinan epidemiologi HIV di
tingkat wilayah.
Pendekatan berbasis data dan spasial seperti ini penting untuk
mewujudkan program kesehatan masyarakat yang lebih efektif, terarah, dan
berkeadilan.
Jaya, I. G. N. M. (2025). Epidemiologi.
Kementerian Kesehatan Republik Indonesia. (2023). Profil Kesehatan Indonesia 2022. Jakarta: Kemenkes RI.
Lawson, A. B. (2018). Bayesian Disease Mapping: Hierarchical Modeling in Spatial Epidemiology (3rd ed.). CRC Press.
Rothman, K. J., Greenland, S., & Lash, T. L. (2021). Modern Epidemiology (4th ed.). Wolters Kluwer.
World Health Organization (WHO). (2023). Global Health Observatory: HIV/AIDS Data. Geneva: WHO.
Badan Pusat Statistik (BPS). (2023). Provinsi Jawa Barat dalam Angka 2023. BPS Provinsi Jawa Barat.
UNAIDS. (2022). Global AIDS Update 2022: In Danger. Geneva: Joint United Nations Programme on HIV/AIDS.
Dalam pembuatan project analisis spasial HIV di Jawa Barat, kami dibantu dengan kecerdasan buatan ChatGPT dan Gemini AI. Peran utama AI dalam project ini antara lain : 1. Membantu penyusunan syntax dalam pembuatan dashboard 2. Membantu dalam perhitungan beberapa ukuran epidemiologi 3. Membantu menganalisis dan memecahkan masalah error pada syntax R 4. Memberikan panduan langkah demi langkah dalam adaptasi kode terhadap hal yang diinginkan
Penggunaan AI bersifat sebagai support terhadap kesulitan kami, bukan sebagai pengganti analisis data inti atau penulisan naratif. Semua keputusan metodologis, intepretasi data dan konten sepenuhnya dikerjakan dan merupakan tanggung jawab penulis
Tampilan kode lengkap dari file app.R yang digunakan untuk membuat dashboard interaktif.
## # ============================================================
## # Dashboard Interaktif HIV β Jawa Barat (2019β2023)
## # ============================================================
##
## # --- 0) Packages -------------------------------------------------------------
## # setwd() DAN install.packages() TELAH DIHAPUS.
## # Kita memanggil library() satu per satu agar shinyapps.io PASTI bisa membacanya.
##
## library(shiny)
## library(leaflet)
## library(dplyr)
## library(readxl)
## library(plotly)
## library(DT)
## library(sf)
## library(geodata)
## library(stringr)
## library(viridis)
## library(htmltools)
## library(webshot2)
## library(htmlwidgets)
## library(ggplot2)
## library(scales)
## # UI/UX
## library(bslib)
## library(thematic)
## library(shinyWidgets)
## library(shinycssloaders) # <--- INI YANG MENYEBABKAN ERROR
## library(forcats)
## library(glue)
## library(tibble)
## # util
## library(tidyr) # <--- INI JUGA GAGAL
## library(purrr) # <--- INI JUGA GAGAL
## library(igraph)
## library(readr) # <--- INI JUGA GAGAL
##
## thematic::thematic_on()
##
## lab_si <- scales::label_number(scale_cut = scales::cut_si(""))
##
## # --- 1) Helpers & Data (2 Excel mentah) -------------------------------------
## # PASTIKAN KEDUA FILE INI DI-UPLOAD (DICENTANG) SAAT DEPLOY
## path_hiv <- "Data HIV berdasarkan Gender dan Umur.xlsx"
## path_pop <- "Data Penduduk berdasarkan Gender.xlsx"
##
## std_cols <- function(x) {
## names(x) <- names(x) |>
## tolower() |>
## stringr::str_replace_all("\\s+", "_") |>
## stringr::str_replace_all("kabupaten/kota|kabupaten_kota|kab/kota|kab_kota|kabkot|nama_kabupaten_kota", "kabkota") |>
## stringr::str_replace_all("^jumlah_kasus$|kasus_total|total_kasus|jml_kasus", "kasus") |>
## stringr::str_replace_all("^jumlah_penduduk$|penduduk_total|populasi|jml_penduduk", "penduduk") |>
## stringr::str_replace_all("^year$|^tahun_*$", "tahun") |>
## stringr::str_replace_all("^jenis_kelamin$", "gender")
## x
## }
##
## clean_label <- function(s) {
## s <- toupper(trimws(as.character(s)))
## ifelse(
## grepl("^KABUPATEN\\s", s), s,
## ifelse(
## grepl("^KOTA\\s", s), s,
## s |>
## stringr::str_replace("^KAB\\.?\\s+", "KABUPATEN ") |>
## stringr::str_replace("^KOT\\.?\\s+", "KOTA ") |>
## stringr::str_replace("\\s+", " ")
## )
## )
## }
##
## make_key <- function(s) {
## s |>
## clean_label() |>
## stringr::str_replace_all("[^A-Z0-9 ]", "") |>
## stringr::str_replace_all("\\s+", "_")
## }
##
## # Baca excel (ambil sheet pertama jika tidak disebut)
## hiv_raw <- readxl::read_xlsx(path_hiv) |> std_cols()
## pop_raw <- readxl::read_xlsx(path_pop) |> std_cols()
##
## # Pastikan kolom kunci ada; toleran terhadap variasi nama
## stopifnot("tahun" %in% names(hiv_raw))
## stopifnot(any(c("kabkota", "kabupaten", "kota") %in% names(hiv_raw)))
## if (!"kabkota" %in% names(hiv_raw)) {
## nm <- intersect(c("kabupaten", "kota"), names(hiv_raw))[1]
## hiv_raw$kabkota <- hiv_raw[[nm]]
## }
## if (!"kasus" %in% names(hiv_raw)) stop("Kolom 'kasus' tidak ditemukan di data HIV.")
##
## # gender opsional
## if (!"gender" %in% names(hiv_raw)) hiv_raw$gender <- NA_character_
##
## # --- Standarisasi HIV ------------------------------------------
## hiv_clean <- hiv_raw |>
## transmute(
## tahun = as.integer(tahun),
## kabkota = clean_label(kabkota),
## kasus = suppressWarnings(as.integer(kasus)),
## gender = dplyr::case_when(
## tolower(as.character(gender)) %in% c("l", "laki-laki", "laki", "male", "m") ~ "LAKI-LAKI",
## tolower(as.character(gender)) %in% c("p", "perempuan", "female", "f", "wanita") ~ "PEREMPUAN",
## TRUE ~ NA_character_
## ),
## join_key = make_key(kabkota)
## ) |>
## # Periode 2019β2023
## dplyr::filter(dplyr::between(tahun, 2019, 2023))
##
## # --- Standarisasi Penduduk (by kabkota + gender) ----------------------------
## stopifnot("tahun" %in% names(pop_raw))
## if (!"kabkota" %in% names(pop_raw)) {
## nm <- intersect(c("kabupaten", "kota"), names(pop_raw))[1]
## if (length(nm)) pop_raw$kabkota <- pop_raw[[nm]]
## }
## if (!"penduduk" %in% names(pop_raw)) stop("Kolom 'penduduk' tidak ditemukan di data penduduk.")
##
## pop_clean <- pop_raw |>
## transmute(
## tahun = as.integer(tahun),
## kabkota = clean_label(kabkota),
## gender = dplyr::case_when(
## tolower(as.character(gender)) %in% c("l", "laki-laki", "laki", "male", "m") ~ "LAKI-LAKI",
## tolower(as.character(gender)) %in% c("p", "perempuan", "female", "f", "wanita") ~ "PEREMPUAN",
## TRUE ~ NA_character_
## ),
## # satuan bisa 'ribu'
## scale = if ("satuan" %in% names(pop_raw))
## ifelse(tolower(trimws(pop_raw$satuan)) %in% c("ribu", "ribu orang", "ribu jiwa"), 1000, 1) else 1,
## penduduk = suppressWarnings(as.numeric(penduduk)) * scale,
## join_key = make_key(kabkota)
## ) |>
## dplyr::filter(dplyr::between(tahun, 2019, 2023))
##
## # Agregat penduduk total per kab/tahun (untuk peta & tabel utama)
## pop_total <- pop_clean |>
## dplyr::group_by(tahun, kabkota, join_key) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
##
## # --- Gabung HIV + Penduduk total (Prevalensi per 100rb) ----------------------
## df <- hiv_clean |>
## dplyr::group_by(tahun, kabkota, join_key) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop") |>
## dplyr::left_join(pop_total, by = c("tahun", "kabkota", "join_key")) |>
## dplyr::mutate(prev_per100k = dplyr::if_else(!is.na(penduduk) & penduduk > 0, (kasus / penduduk) * 1e5, NA_real_))
##
## years <- sort(unique(df$tahun))
## kab_list <- sort(unique(df$kabkota))
##
## # --- Shapefile Jabar ---------------------------------------------------------
## indo <- geodata::gadm(country = "IDN", level = 2, path = tempdir())
##
## # --- Daftar provinsi (untuk tab Gender) ---
## prov_choices <- sort(unique(indo$NAME_1))
## default_prov <- if ("Jawa Barat" %in% prov_choices) "Jawa Barat" else
## if ("West Java" %in% prov_choices) "West Java" else prov_choices[1]
##
## jabar <- indo[indo$NAME_1 %in% c("West Java", "Jawa Barat"), ]
##
## has_engtype <- "ENGTYPE_2" %in% names(jabar)
## city_set_norm <- c("BOGOR", "BEKASI", "BANDUNG", "DEPOK", "CIMAHI", "SUKABUMI", "CIREBON", "TASIKMALAYA", "BANJAR")
##
## jabar_sf <- sf::st_as_sf(jabar) |>
## dplyr::filter(if (has_engtype) ENGTYPE_2 %in% c("Regency", "City", "Municipality") else TRUE) |>
## dplyr::mutate(
## base_name = toupper(NAME_2),
## base_name = gsub("^(KOTA|KABUPATEN)\\s+", "", base_name),
## is_city = if (has_engtype) {
## !is.na(ENGTYPE_2) & ENGTYPE_2 %in% c("City", "Municipality")
## } else {
## base_name %in% city_set_norm
## },
## kabkota_label = ifelse(is_city, paste("KOTA", base_name), paste("KABUPATEN", base_name)),
## kabkota_label = clean_label(kabkota_label),
## join_key = make_key(kabkota_label)
## ) |>
## sf::st_transform(4326)
##
## # --- Agregasi roll-up per tahun untuk peta (kasus + penduduk + prevalensi) ---
## make_rollup_counts <- function(target_year) {
## df |>
## dplyr::filter(tahun == target_year) |>
## dplyr::group_by(join_key, kabkota) |>
## dplyr::summarise(
## kasus = sum(kasus, na.rm = TRUE),
## penduduk = sum(penduduk, na.rm = TRUE),
## .groups = "drop"
## ) |>
## dplyr::mutate(prev_per100k = dplyr::if_else(!is.na(penduduk) & penduduk > 0, (kasus / penduduk) * 1e5, NA_real_))
## }
##
## # ringkasan provinsi per tahun (Total, YoY, CAGR) -- untuk kasus total
## sum_tahunan <- df |>
## dplyr::group_by(tahun) |>
## dplyr::summarise(total_kasus = sum(kasus, na.rm = TRUE), .groups = "drop") |>
## dplyr::arrange(tahun) |>
## dplyr::mutate(yoy = (total_kasus - dplyr::lag(total_kasus)) / dplyr::lag(total_kasus))
##
## calc_cagr_dyn <- function(tbl, t_end) {
## t0 <- min(tbl$tahun, na.rm = TRUE)
## if (t_end <= t0) return(NA_real_)
## d0 <- tbl$total_kasus[tbl$tahun == t0]
## d1 <- tbl$total_kasus[tbl$tahun == t_end]
## if (!length(d0) || !length(d1) || is.na(d0) || d0 == 0) return(NA_real_)
## (d1 / d0)^(1 / (t_end - t0)) - 1
## }
##
## # --- 2) Theme ---------------------------------------------------------------
## app_theme <- bslib::bs_theme(
## version = 5,
## primary = "#6C5CE7",
## secondary = "#00C2FF",
## success = "#00D8A0",
## info = "#3EC1D3",
## warning = "#F6C667",
## danger = "#FF6B6B",
## base_font = bslib::font_google("Inter"),
## heading_font = bslib::font_google("Poppins"),
## "border-radius" = ".9rem",
## "btn-border-radius" = ".9rem",
## "body-bg" = "#ffffff",
## "body-color" = "#212529",
## "card-bg" = "#ffffff"
## )
##
## # setelah app_theme <- bslib::bs_theme(...)
## dark_theme <- bslib::bs_theme(
## version = 5,
## bg = "#0b1220", # gelap
## fg = "#E6EEF6", # teks terang
## primary = "#8A7BFF",
## secondary = "#00C2FF",
## success = "#00D8A0",
## info = "#3EC1D3",
## warning = "#F6C667",
## danger = "#FF6B6B",
## base_font = bslib::font_google("Inter"),
## heading_font = bslib::font_google("Poppins"),
## "border-radius" = ".9rem",
## "btn-border-radius" = ".9rem",
## # be mindful: enable body background + card bg tweaks
## "body-bg" = "#0b1220",
## "body-color" = "#E6EEF6",
## "card-bg" = "#0f1724"
## )
##
## # --- 3) UI -------------------------------------------------------------------
## ui <- bslib::page_navbar(
## title = div("π Kasus HIV Jawa Barat 2019β2023", style = "font-weight:700;"),
## theme = app_theme,
## bg = "white",
##
## # --- OVERRIDE: Perkecil ikon/teks KPI + beri gap ikon-teks -------------------
## tags$head(
## tags$style(HTML("
## /* ============================
## GLOBAL: pastikan bisa scroll
## ============================ */
## html, body, .bslib-page, .bslib-page-fill, .bslib-page-setup, .container-fluid {
## height: auto !important;
## overflow-y: auto !important;
## }
## .bslib-value-box, .card, .bslib-card {
## overflow: visible !important;
## }
##
## /* =========================================
## KPI HIV β seragam, compact, 1 baris scroll
## ========================================= */
## .kpi-row{
## display:flex;
## flex-wrap:nowrap; /* tetap 1 baris, bisa digeser */
## justify-content:space-between;
## gap:14px;
## overflow-x:auto; /* horizontal scroll bila tidak muat */
## padding-bottom:4px;
## --kpiW: 230px; /* lebar seragam */
## --kpiH: 170px; /* tinggi seragam (lebih kecil) */
## }
## .kpi-card{ flex:0 0 var(--kpiW); max-width:var(--kpiW); }
##
## .bslib-value-box{
## height:var(--kpiH);
## display:flex; flex-direction:column; justify-content:space-between;
## border-radius:14px; padding:14px 16px; position:relative;
## transition:transform .2s ease, box-shadow .2s ease, filter .2s ease;
## }
## .bslib-value-box:hover{
## transform:translateY(-3px);
## box-shadow:0 12px 24px rgba(108,92,231,.18),0 3px 8px rgba(0,0,0,.06);
## }
##
## /* Ikon diperkecil & rata kiri */
## .value-box-showcase{ margin-bottom:6px !important; line-height:1 !important; }
## .value-box-showcase i, .value-box-showcase svg{
## width:20px !important; height:20px !important;
## }
##
## /* Typografi diperkecil agar muat */
## .bslib-value-box .value-box-title{
## font-size:clamp(.82rem, .9vw, .9rem); line-height:1.25; margin:0 0 .25rem 0;
## }
## .bslib-value-box .value-box-value{
## font-size:clamp(1.2rem, 1.6vw, 1.6rem); line-height:1.12; font-weight:600;
## }
## .bslib-value-box p{ font-size:clamp(.78rem, .9vw, .85rem); margin-bottom:0; }
##
## /* Wilayah tertinggi (nama panjang) */
## .kpi-5 .value-box-value{ font-size:clamp(1.0rem, 1.4vw, 1.45rem); }
##
## /* Animasi ringan */
## @keyframes kpiIn{ from{opacity:0;transform:translateY(6px) scale(.985);} to{opacity:1;transform:translateY(0) scale(1);} }
## .kpi{ animation:kpiIn .45s cubic-bezier(.2,.8,.2,1) both; }
## .kpi-1{animation-delay:.00s}.kpi-2{animation-delay:.04s}.kpi-3{animation-delay:.08s}.kpi-4{animation-delay:.12s}.kpi-5{animation-delay:.16s}
##
## /* Hapus margin besar yang bikin layout βngunciβ */
## .bslib-value-box .value-box-showcase{ margin-bottom:8px !important; }
##
## /* ========== KPI: ukuran benar-benar seragam ========== */
## .kpi-row{ --kpiW:260px; --kpiH:320px; gap:8px; overflow-x:auto; display:flex; flex-wrap:nowrap; }
## .kpi-card{ flex:0 0 var(--kpiW); max-width:var(--kpiW); height:var(--kpiH); display:flex; }
## .kpi-card .bslib-value-box{ height:100% !important; width:100%; box-sizing:border-box;
## display:flex; flex-direction:column; justify-content:space-between; }
##
## /* Judul (biar bisa 2 baris, tidak dipotong) */
## .bslib-value-box .value-box-title{
## font-size: clamp(.82rem, .9vw, .9rem);
## line-height: 1.25;
## margin: 0 0 .25rem 0;
## white-space: normal !important;
## overflow: visible !important;
## text-overflow: unset !important;
## }
##
## /* Angka utama (tetap satu baris tapi tidak kepotong) */
## .bslib-value-box .value-box-value{
## font-size: clamp(1.2rem, 1.6vw, 1.6rem);
## line-height: 1.12;
## font-weight: 600;
## white-space: nowrap;
## overflow: hidden;
## text-overflow: ellipsis;
## }
##
## /* Subteks (biar bisa lebih dari 2 baris) */
## .bslib-value-box p{
## font-size: clamp(.7rem, .8vw, .75rem);
## margin: 0;
## white-space: normal !important;
## overflow: visible !important;
## display: block !important;
## -webkit-line-clamp: unset !important;
## -webkit-box-orient: unset !important;
## }
##
## /* Ikon kecil & konsisten */
## .value-box-showcase{ margin-bottom:6px !important; line-height:1 !important; }
## .value-box-showcase i, .value-box-showcase svg{ width:20px !important; height:20px !important; }
##
## /* 2) Nilai di kartu 'Wilayah Tertinggi' boleh 2 baris, tidak dipotong */
## .kpi-5 .value-box-value{
## white-space: normal !important; /* boleh wrap */
## overflow: visible !important;
## text-overflow: unset !important;
## line-height: 1.05;
## font-size: clamp(1.0rem, 1.35vw, 1.35rem); /* sedikit lebih kecil agar muat */
## display: -webkit-box; /* batasi 2 baris biar tidak kepanjangan */
## -webkit-line-clamp: 2;
## -webkit-box-orient: vertical;
## }
##
## /* 3) Subteks di kartu 'Wilayah Tertinggi' juga jangan dipotong */
## .kpi-5 p{
## white-space: normal !important;
## overflow: visible !important;
## display: block !important;
## -webkit-line-clamp: unset !important;
## -webkit-box-orient: unset !important;
## }
##
##
## "))
## ),
##
## header = bslib::nav_item(
## div(
## class = "px-3 py-2 rounded-xl",
## style = "background:linear-gradient(90deg,#6C5CE7,#00C2FF);color:white;font-weight:600;",
## "Sumber: Dinkes Jabar (kasus; lama+baru) & BPS (penduduk; per gender). Periode 2019β2023."
## )
## ),
##
## sidebar = bslib::sidebar(
## open = TRUE,
## width = 360,
##
## sliderInput(
## "tahun", "Tahun",
## min = min(years), max = max(years), value = max(years), step = 1,
## animate = animationOptions(interval = 2500, loop = TRUE)
## ),
##
## selectInput(
## "yoy_base", "Bandingkan YoY terhadap tahun:",
## choices = years, selected = max(years) - 1
## ),
##
## shinyWidgets::radioGroupButtons(
## "metric", "Metrik",
## choices = c("Kasus" = "cases", "Prevalensi/100rb" = "prev", "Penduduk" = "pop"),
## selected = "cases", justified = TRUE, size = "sm", width = "100%"
## ),
##
## # ---- Theme switch (SATU kontrol saja) ----
## shinyWidgets::prettyToggle(
## inputId = "dark_mode",
## label_on = "Gelap", label_off = "Terang",
## icon_on = icon("moon"), icon_off = icon("sun"),
## status_on = "primary", # <β ganti dari "dark"
## status_off= "warning",
## outline = TRUE, plain = TRUE
## ),
##
## shinyWidgets::pickerInput(
## "kab", "Kab/Kota (opsional)",
## choices = kab_list, multiple = TRUE,
## options = list(`live-search` = TRUE, `actions-box` = TRUE, title = "Semua kab/kota", size = 10)
## ),
##
## tags$hr()
## ),
##
## bslib::card_body(
##
## # KPI
## div(
## class = "kpi-row",
## div(
## class = "kpi-card kpi kpi-1",
## bslib::value_box(
## title = "Total Penduduk", value = textOutput("kpi_pop"),
## showcase = icon("users"), theme_color = "secondary",
## p(textOutput("kpi_pop_sub"), class = "mb-0 text-muted")
## )
## ),
## div(
## class = "kpi-card kpi kpi-2",
## bslib::value_box(
## title = "Total Kasus", value = textOutput("kpi_cases"),
## showcase = icon("chart-column"), theme_color = "primary",
## p(textOutput("kpi_cases_sub"), class = "mb-0 text-muted")
## )
## ),
## div(
## class = "kpi-card kpi kpi-3",
## bslib::value_box(
## title = "Perbandingan tahunan", value = textOutput("kpi_yoy"),
## showcase = icon("arrow-trend-up"), theme_color = "info",
## p(textOutput("kpi_yoy_sub"), class = "mb-0 text-muted")
## )
## ),
## div(
## class = "kpi-card kpi kpi-4",
## bslib::value_box(
## title = "Rata-rata kenaikan per tahun", value = textOutput("kpi_cagr"),
## showcase = icon("chart-line"), theme_color = "warning",
## p(textOutput("kpi_cagr_sub"), class = "mb-0 text-muted")
## )
## ),
## div(
## class = "kpi-card kpi kpi-5",
## bslib::value_box(
## title = "Wilayah Tertinggi",
## value = textOutput("kpi_top_name"),
## showcase = icon("trophy"), theme_color = "success",
## p(textOutput("kpi_top_val"), class = "mb-0 text-muted"),
## p(textOutput("kpi_top_desc"), class = "mb-0 text-muted")
## )
## )
## ),
##
## # Tabset konten
## bslib::navset_card_pill(
## id = "cards",
##
## bslib::nav_panel(
## "πΊοΈ Peta Kasus/Prevalensi",
## withSpinner(leafletOutput("map", height = "60vh"), type = 4)
## ),
##
## bslib::nav_panel(
## "π Tren & YoY",
## div(
## class = "mb-2",
## shinyWidgets::radioGroupButtons(
## inputId = "trend_level", label = "Level",
## choices = c("Provinsi", "Kab/Kota"), selected = "Provinsi",
## justified = TRUE, size = "sm"
## ),
## conditionalPanel(
## condition = "input.trend_level == 'Kab/Kota'",
## shinyWidgets::pickerInput(
## "trend_kab", "Pilih Kab/Kota (bisa lebih dari 1)",
## choices = kab_list, multiple = TRUE,
## options = list(`live-search` = TRUE, `actions-box` = TRUE, title = "Pilih kab/kota")
## )
## )
## ),
## div(
## class = "mt-2",
## withSpinner(plotlyOutput("trend_total", height = 380), type = 4),
## br(),
## withSpinner(plotlyOutput("trend_yoy", height = 320), type = 4),
## br(),
## withSpinner(plotlyOutput("trend_pop", height = 320), type = 4)
## )
## ),
##
## bslib::nav_panel(
## "π Top-10 Wilayah",
## withSpinner(plotlyOutput("rankTop10", height = 500), type = 4)
## ),
##
## bslib::nav_panel(
## "π» Gender",
## div(
## class = "mb-2",
## shinyWidgets::radioGroupButtons(
## inputId = "gender_level", label = "Level",
## choices = c("Provinsi", "Kab/Kota"),
## selected = "Provinsi", justified = TRUE, size = "sm"
## ),
## conditionalPanel(
## condition = "input.gender_level == 'Kab/Kota'",
## shinyWidgets::pickerInput(
## "gender_kab", "Pilih Kab/Kota (bisa lebih dari 1)",
## choices = kab_list, multiple = TRUE,
## options = list(`live-search`=TRUE, `actions-box`=TRUE, title="Pilih kab/kota")
## )
## )
## ),
## withSpinner(plotlyOutput("gender_plot", height = 480), type = 4),
## br(),
## ),
##
## bslib::nav_panel(
## "π Ukuran Asosiasi",
## div(
## class = "mb-3",
## shinyWidgets::radioGroupButtons(
## inputId = "assoc_level", label = "Level",
## choices = c("Provinsi", "Kab/Kota"), selected = "Provinsi",
## justified = TRUE, size = "sm"
## ),
## conditionalPanel(
## condition = "input.assoc_level == 'Kab/Kota'",
## shinyWidgets::pickerInput(
## "assoc_kab", "Pilih 1 Kab/Kota",
## choices = kab_list, multiple = FALSE,
## options = list(`live-search` = TRUE)
## )
## )
## ),
## div(
## class = "assoc-boxes",
## bslib::layout_columns(
## col_widths = c(6, 6),
## bslib::value_box(
## title = "Prevalence Ratio (PR)",
## value = textOutput("assoc_pr"),
## p(textOutput("assoc_pr_ci"), class = "mb-0 text-muted")
## ),
## bslib::value_box(
## title = "Prevalence Odds Ratio (POR)",
## value = textOutput("assoc_por"),
## p(textOutput("assoc_por_ci"), class = "mb-0 text-muted")
## )
## ),
## br(),
## withSpinner(DTOutput("assoc_table"), type = 4),
## div(
## class = "mt-2 small text-muted",
## HTML(
## "Catatan: <em>PR = (a/n<sub>exp</sub>)/(c/n<sub>ref</sub>);
## SE[log(PR)] β β(1/a β 1/n<sub>exp</sub> + 1/c β 1/n<sub>ref</sub>)</em>.
## <br/><em>POR = (a/b)/(c/d); SE[log(POR)] β β(1/a + 1/b + 1/c + 1/d)</em>.
## Di sini <strong>eksposur = LAKI-LAKI</strong>, <strong>referensi = PEREMPUAN</strong>,
## a=kasus laki-laki, b=non-kasus laki-laki, c=kasus perempuan, d=non-kasus perempuan."
## )
## )
## )
## ),
##
## bslib::nav_panel(
## "𧬠Deskripsi Kasus",
## bslib::layout_columns(
## col_widths = c(7, 5),
## bslib::card(
## bslib::card_header("Faktor AgentβHostβEnvironment"),
## p("Berikut uraian hubungan antara faktor penyebab penyakit HIV di Jawa Barat:"),
## tags$ul(
## tags$li(strong("Agent:"), " Virus HIV yang menyerang sistem imun manusia (khususnya sel CD4)."),
## tags$li(strong("Host:"), " Individu dengan perilaku berisiko (seks tanpa kondom, penggunaan jarum suntik bersama, dsb)."),
## tags$li(strong("Environment:"), " Faktor lingkungan sosial dan akses layanan kesehatan yang mempengaruhi penularan serta deteksi dini.")
## ),
## p("Faktor-faktor ini saling berinteraksi dan menentukan tingkat kerentanan individu terhadap infeksi.")
## ),
## bslib::card(
## bslib::card_header("Diagram Hubungan Faktor Risiko"),
## plotOutput("risk_diagram", height = 340)
## )
## )
## ),
##
## bslib::nav_panel(
## "π§ͺ Desain Studi",
## bslib::layout_columns(
## col_widths = c(6, 6),
## bslib::card(
## bslib::card_header("Pilihan Desain Epidemiologi (Simulatif)"),
## radioButtons(
## "design_choice", "Pilih desain studi:",
## choices = c("Cross-sectional", "Case-control", "Cohort", "Eksperimen"),
## selected = "Cross-sectional"
## )
## ),
##
## bslib::card(
## bslib::card_header("Deskripsi Desain"),
## htmlOutput("design_desc")
## )
## )
## ),
##
## bslib::nav_panel(
## "π Tabel Data",
## bslib::navset_card_pill(
## bslib::nav_panel("Utama", withSpinner(DTOutput("table_main"), type = 4))
## )
## )
## )
## )
## )
##
## # --- 4) SERVER ---------------------------------------------------------------
## server <- function(input, output, session) {
##
## # data terfilter (tahun + pilihan kab/kota)
## dff <- reactive({
## req(input$tahun)
## d <- df |> dplyr::filter(tahun == input$tahun)
## if (!is.null(input$kab) && length(input$kab) > 0) d <- d |> dplyr::filter(kabkota %in% input$kab)
## d
## })
##
## # Pastikan theme default sudah terpasang (app_theme didefinisikan lebih dulu)
## # dan biarkan user switch mengganti saat runtime.
## observeEvent(input$dark_mode, {
## if (isTRUE(input$dark_mode)) {
## session$setCurrentTheme(dark_theme)
## } else {
## session$setCurrentTheme(app_theme)
## }
## }, ignoreInit = FALSE)
##
## # --- Ukuran asosiasi (Gender) ----------------------------------------------
## compute_assoc_gender <- function(yr, scope = c("Provinsi", "Kab/Kota"), kab = NULL) {
## scope <- match.arg(scope)
## hc <- hiv_clean |> dplyr::filter(tahun == yr, !is.na(gender))
## pc <- pop_clean |> dplyr::filter(tahun == yr, !is.na(gender))
##
## if (scope == "Kab/Kota" && length(kab) == 1 && !is.na(kab)) {
## hc <- hc |> dplyr::filter(kabkota == kab)
## pc <- pc |> dplyr::filter(kabkota == kab)
## }
##
## kasus_gender <- hc |>
## dplyr::group_by(gender) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop")
##
## pend_gender <- pc |>
## dplyr::group_by(gender) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
##
## dd <- dplyr::full_join(kasus_gender, pend_gender, by = "gender") |>
## dplyr::mutate(
## kasus = dplyr::coalesce(kasus, 0L),
## penduduk = dplyr::coalesce(penduduk, NA_real_),
## nonkasus = ifelse(!is.na(penduduk), pmax(penduduk - kasus, 0), NA_real_)
## )
##
## if (!all(c("LAKI-LAKI", "PEREMPUAN") %in% dd$gender)) return(NULL)
## E <- dd[dd$gender == "LAKI-LAKI", ]
## R <- dd[dd$gender == "PEREMPUAN", ]
##
## if (any(is.na(c(E$penduduk, R$penduduk))) || any(c(E$penduduk, R$penduduk) <= 0)) return(NULL)
##
## a <- as.numeric(E$kasus); b <- as.numeric(E$nonkasus)
## c_ <- as.numeric(R$kasus); d <- as.numeric(R$nonkasus)
## nE <- as.numeric(E$penduduk); nR <- as.numeric(R$penduduk)
##
## if (any(c(a, b, c_, d) == 0)) {
## a <- a + 0.5; b <- b + 0.5; c_ <- c_ + 0.5; d <- d + 0.5
## nE <- a + b
## nR <- c_ + d
## }
##
## pE <- a / nE
## pR <- c_ / nR
##
## PR <- ifelse(pR > 0, pE / pR, NA_real_)
## seLPR <- sqrt((1 / a) - (1 / nE) + (1 / c_) - (1 / nR))
## LPR <- ifelse(is.finite(seLPR), exp(log(PR) - 1.96 * seLPR), NA_real_)
## UPR <- ifelse(is.finite(seLPR), exp(log(PR) + 1.96 * seLPR), NA_real_)
##
## OR <- (a / b) / (c_ / d)
## seLOR <- sqrt(1 / a + 1 / b + 1 / c_ + 1 / d)
## LOR <- ifelse(is.finite(seLOR), exp(log(OR) - 1.96 * seLOR), NA_real_)
## UOR <- ifelse(is.finite(seLOR), exp(log(OR) + 1.96 * seLOR), NA_real_)
##
## list(
## table = data.frame(
## Kelompok = c("LAKI-LAKI (E)", "PEREMPUAN (R)"),
## Kasus = c(a, c_),
## `Non-kasus` = c(b, d),
## Penduduk = c(nE, nR),
## Prevalensi = c(pE, pR)
## ),
## PR = PR, PR_L = LPR, PR_U = UPR,
## OR = OR, OR_L = LOR, OR_U = UOR
## )
## }
##
## assoc_res <- reactive({
## yr <- req(input$tahun)
## scope <- req(input$assoc_level)
## kab <- if (identical(scope, "Kab/Kota")) req(input$assoc_kab) else NULL
## compute_assoc_gender(yr, scope, kab)
## })
##
## output$assoc_pr <- renderText({
## res <- assoc_res(); validate(need(!is.null(res), "Data gender/penduduk tidak lengkap."))
## if (is.na(res$PR)) "NA" else sprintf("%.3f", res$PR)
## })
## output$assoc_pr_ci <- renderText({
## res <- assoc_res(); validate(need(!is.null(res), ""))
## if (any(is.na(c(res$PR_L, res$PR_U)))) "CI 95%: NA" else sprintf("CI 95%%: %.3f β %.3f", res$PR_L, res$PR_U)
## })
## output$assoc_por <- renderText({
## res <- assoc_res(); validate(need(!is.null(res), ""))
## if (is.na(res$OR)) "NA" else sprintf("%.3f", res$OR)
## })
## output$assoc_por_ci <- renderText({
## res <- assoc_res(); validate(need(!is.null(res), ""))
## if (any(is.na(c(res$OR_L, res$OR_U)))) "CI 95%: NA" else sprintf("CI 95%%: %.3f β %.3f", res$OR_L, res$OR_U)
## })
## output$assoc_table <- renderDT({
## res <- assoc_res(); validate(need(!is.null(res), "Data gender/penduduk tidak lengkap."))
## dt <- res$table |> dplyr::mutate(Prevalensi = scales::percent(Prevalensi, accuracy = 0.01))
## datatable(dt, rownames = FALSE, options = list(pageLength = 5, dom = "tip"),
## class = "stripe hover row-border order-column", style = "bootstrap5")
## })
##
## # agregat per kab/kota (kasus, penduduk, prevalensi)
## top_stats <- reactive({
## d <- dff() |>
## dplyr::group_by(kabkota) |>
## dplyr::summarise(
## kasus = sum(kasus, na.rm = TRUE),
## penduduk = sum(penduduk, na.rm = TRUE),
## prev = dplyr::if_else(sum(penduduk, na.rm = TRUE) > 0,
## (sum(kasus, na.rm = TRUE) / sum(penduduk, na.rm = TRUE)) * 1e5,
## NA_real_),
## .groups = "drop"
## )
## if (nrow(d) == 0) return(d[0, ])
## ord <- if (identical(input$metric, "prev")) {
## order(d$prev, decreasing = TRUE, na.last = TRUE)
## } else if (identical(input$metric, "pop")) {
## order(d$penduduk, decreasing = TRUE, na.last = TRUE)
## } else {
## order(d$kasus, decreasing = TRUE, na.last = TRUE)
## }
## d[ord, , drop = FALSE] |> dplyr::slice(1)
## })
##
## output$kpi_top_name <- renderText({ ts <- top_stats(); if (!nrow(ts) || is.na(ts$kabkota[1])) "-" else ts$kabkota[1] })
## output$kpi_top_val <- renderText({
## ts <- top_stats(); if (!nrow(ts)) return("")
## if (identical(input$metric, "prev")) {
## ifelse(is.na(ts$prev), "β", sprintf("%0.1f per 100rb", ts$prev))
## } else if (identical(input$metric, "pop")) {
## lab_si(ts$penduduk)
## } else {
## scales::comma(ts$kasus)
## }
## })
##
## output$kpi_top_desc <- renderText({
## ts <- top_stats(); if (!nrow(ts)) return("")
## paste0(
## "Kasus: ", scales::comma(ts$kasus),
## " β’ Prevalensi: ", ifelse(is.na(ts$prev), "β", sprintf("%.1f", ts$prev)), " per 100rb",
## " β’ Penduduk: ", lab_si(ts$penduduk)
## )
## })
##
## # KPI YoY/CAGR (berbasis total kasus provinsi)
## sum_tahunan_react <- reactive({
## df |>
## dplyr::group_by(tahun) |>
## dplyr::summarise(
## total_kasus = sum(kasus, na.rm = TRUE),
## total_pop = sum(penduduk, na.rm = TRUE),
## .groups = "drop"
## ) |>
## dplyr::arrange(tahun) |>
## dplyr::mutate(yoy = (total_kasus - dplyr::lag(total_kasus)) / dplyr::lag(total_kasus))
## })
## cagr_dynamic <- reactive({ st <- sum_tahunan_react(); calc_cagr_dyn(st, as.integer(req(input$tahun))) })
## output$kpi_cagr <- renderText({ x <- cagr_dynamic(); if (is.na(x)) "β" else paste0(scales::percent(x, accuracy = 0.01), " / th") })
## output$kpi_cagr_sub <- renderText({ st <- sum_tahunan_react(); paste0(min(st$tahun, na.rm = TRUE), " \u2192 ", input$tahun, " (rata-rata per tahun)") })
## output$kpi_yoy <- renderText({
## st <- sum_tahunan_react()
## cur <- st$total_kasus[st$tahun == input$tahun]; base <- st$total_kasus[st$tahun == input$yoy_base]
## if (!length(cur) || !length(base) || is.na(base) || base == 0) return("β")
## pct <- (cur - base) / base
## paste0(ifelse(pct >= 0, "β² ", "βΌ "), scales::percent(abs(pct), accuracy = 0.1))
## })
## output$kpi_yoy_sub <- renderText({
## st <- sum_tahunan_react()
## cur <- st$total_kasus[st$tahun == input$tahun]; base <- st$total_kasus[st$tahun == input$yoy_base]
## naik <- (cur - base) >= 0
## paste0(ifelse(naik, "Naik", "Turun"), " dibanding ", input$yoy_base)
## })
## output$kpi_cases <- renderText({ d <- dff(); tot <- suppressWarnings(sum(d$kasus, na.rm = TRUE)); if (is.infinite(tot) || is.na(tot)) "0" else scales::comma(as.numeric(tot)) })
## output$kpi_cases_sub <- renderText({ paste0("Total kasus provinsi (", input$tahun, ")") })
## output$kpi_pop <- renderText({ d <- df %>% dplyr::filter(tahun == req(input$tahun)); scales::comma(sum(d$penduduk, na.rm = TRUE)) })
## output$kpi_pop_sub <- renderText({ paste0("Penduduk provinsi (", input$tahun, ")") })
##
## # --- Diagram faktor risiko --------------------------------------------------
## output$risk_diagram <- renderPlot({
## nodes <- data.frame(name = c("Host", "Agent", "Environment", "Infeksi HIV"))
## edges <- data.frame(from = c("Host", "Agent", "Environment"), to = "Infeksi HIV")
## g <- igraph::graph_from_data_frame(edges, vertices = nodes)
## plot(
## g,
## layout = igraph::layout_in_circle(g),
## vertex.color = c("#00C2FF", "#FF6B6B", "#F6C667", "#6C5CE7"),
## vertex.size = 90,
## vertex.label.color = "black",
## vertex.label.cex = 1.0,
## edge.arrow.size = 0.8,
## edge.width = 2,
## margin = 0,
## main = "Hubungan Faktor Risiko HIV"
## )
## }, height = 380, width = 350)
##
## # --- Desain Studi Simulatif -------------------------------------------------
## output$design_desc <- renderUI({
## switch(
## input$design_choice,
## "Cross-sectional" = HTML("
## <p><b>Tipe:</b> Potong lintang</p>
## <p><b>Tujuan:</b> Mengukur prevalensi HIV pada tahun tertentu.</p>
## <p><b>Variabel utama:</b> Status HIV (positif/negatif), faktor risiko perilaku.</p>
## <p><b>Populasi:</b> Penduduk usia β₯15 tahun di Jawa Barat.</p>
## <p><b>Sampling:</b> Cluster sampling per kabupaten/kota.</p>
## <p><b>Bias potensial:</b> Bias seleksi (responden enggan melapor status HIV).</p>
## "),
## "Case-control" = HTML("
## <p><b>Tipe:</b> Kasusβkontrol retrospektif.</p>
## <p><b>Tujuan:</b> Menilai hubungan perilaku berisiko terhadap kejadian HIV.</p>
## <p><b>Variabel utama:</b> Riwayat penggunaan jarum suntik, hubungan seksual tanpa kondom.</p>
## <p><b>Populasi:</b> Pasien HIV (kasus) dan individu sehat (kontrol) di fasilitas kesehatan.</p>
## <p><b>Sampling:</b> Matching usia & jenis kelamin.</p>
## <p><b>Bias potensial:</b> Recall bias & selection bias.</p>
## "),
## "Cohort" = HTML("
## <p><b>Tipe:</b> Kohort prospektif.</p>
## <p><b>Tujuan:</b> Melihat insidensi HIV baru di antara populasi berisiko.</p>
## <p><b>Variabel utama:</b> Pajanan perilaku & hasil HIV follow-up.</p>
## <p><b>Populasi:</b> Pekerja seks, pengguna narkoba suntik.</p>
## <p><b>Sampling:</b> Purposive sampling di wilayah tinggi prevalensi.</p>
## <p><b>Bias potensial:</b> Loss to follow-up, informasi sensitif.</p>
## "),
## "Eksperimen" = HTML("
## <p><b>Tipe:</b> Intervensi eksperimental.</p>
## <p><b>Tujuan:</b> Mengevaluasi efektivitas program edukasi HIV/AIDS.</p>
## <p><b>Variabel utama:</b> Perubahan pengetahuan & perilaku pencegahan.</p>
## <p><b>Populasi:</b> Remaja SMA di daerah perkotaan Jawa Barat.</p>
## <p><b>Sampling:</b> Randomisasi sekolah ke kelompok intervensi vs kontrol.</p>
## <p><b>Bias potensial:</b> Kontaminasi antar kelompok, efek Hawthorne.</p>
## ")
## )
## })
##
## # --- PETA (kasus / prevalensi / penduduk) ----------------------------------
## rollup_pop <- function(target_year) {
## pop_total %>%
## dplyr::filter(tahun == target_year) %>%
## dplyr::mutate(
## is_city = grepl("^KOTA\\s", kabkota),
## base_name = toupper(gsub("^(KABUPATEN|KOTA)\\s+", "", kabkota))
## ) %>%
## dplyr::group_by(base_name, is_city) %>%
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
## }
## build_map_df <- function(target_year) {
## agg <- make_rollup_counts(target_year) |>
## dplyr::mutate(
## is_city = grepl("^KOTA\\s", kabkota),
## base_name = toupper(gsub("^(KABUPATEN|KOTA)\\s+", "", kabkota))
## )
## md <- jabar_sf |> dplyr::left_join(agg, by = c("base_name", "is_city"))
## if (all(is.na(md$kasus))) {
## md <- jabar_sf |> dplyr::left_join(make_rollup_counts(target_year), by = "join_key")
## }
## md
## }
## map_widget <- reactive({
## if (identical(input$metric, "pop")) {
## md <- jabar_sf %>%
## dplyr::left_join(rollup_pop(req(input$tahun)), by = c("base_name","is_city"))
## } else {
## md <- build_map_df(req(input$tahun))
## }
##
## # tentukan nama tiles berdasarkan mode
## tiles_name <- if (isTRUE(input$dark_mode)) "CartoDB.DarkMatter" else "CartoDB.Voyager"
##
## if (input$metric == "prev") {
## value_vec <- md$prev_per100k; legend_t <- "Prevalensi (per 100rb)"
## val_lab <- ifelse(is.na(md$prev_per100k), "β", sprintf("%0.1f per 100rb", md$prev_per100k))
## } else if (input$metric == "pop") {
## value_vec <- md$penduduk; legend_t <- "Penduduk"
## val_lab <- ifelse(is.na(md$penduduk), "β", lab_si(md$penduduk))
## } else {
## value_vec <- md$kasus; legend_t <- "Kasus (absolut)"
## val_lab <- ifelse(is.na(md$kasus), "β", format(md$kasus, big.mark = ","))
## }
##
## domain_vals <- if (length(value_vec) == 0 || all(is.na(value_vec))) c(0,1) else value_vec
## pal <- colorBin(viridis(7, option = "C"), domain = domain_vals, bins = 7, pretty = TRUE, na.color = "#F2F4F7")
## fill_cols <- pal(value_vec)
## labels <- sprintf(
## "<b>%s</b><br/>%s<br/>Penduduk: %s",
## ifelse(is.na(md$kabkota_label), md$NAME_2, md$kabkota_label),
## val_lab,
## ifelse(is.na(md$penduduk), "β", lab_si(md$penduduk))
## ) |> lapply(htmltools::HTML)
##
## leaflet(md, options = leafletOptions(minZoom = 6, maxZoom = 12)) %>%
## addProviderTiles(tiles_name) %>%
## addPolygons(
## fillColor = fill_cols, weight = 1, color = "#FFFFFF", opacity = 1,
## fillOpacity = 0.9, label = labels,
## highlight = highlightOptions(weight = 2, color = "#111827", bringToFront = TRUE),
## smoothFactor = 0.2
## ) %>%
## addLegend(pal = pal, values = domain_vals, opacity = .95, title = legend_t, position = "bottomright")
## })
##
## output$map <- renderLeaflet({ map_widget() })
##
## # --- Tren Total & YoY ------------------------------------------------------
## prov_year <- reactive({
## df |>
## dplyr::group_by(tahun) |>
## dplyr::summarise(
## total_kasus = sum(kasus, na.rm = TRUE),
## total_pop = sum(penduduk, na.rm = TRUE),
## prev_per100k = ifelse(total_pop > 0, (total_kasus / total_pop) * 1e5, NA_real_),
## .groups = "drop"
## ) |>
## dplyr::arrange(tahun)
## })
## series_time <- reactive({
## if (identical(input$trend_level, "Provinsi")) {
## df |>
## dplyr::group_by(tahun) |>
## dplyr::summarise(
## label = "PROVINSI",
## kasus = sum(kasus, na.rm = TRUE),
## penduduk = sum(penduduk, na.rm = TRUE),
## .groups = "drop"
## ) |>
## dplyr::mutate(prev_per100k = dplyr::if_else(penduduk > 0, (kasus / penduduk) * 1e5, NA_real_)) |>
## dplyr::arrange(tahun)
## } else {
## validate(need(length(input$trend_kab) > 0, "Pilih minimal 1 kab/kota."))
## df |>
## dplyr::filter(kabkota %in% input$trend_kab) |>
## dplyr::group_by(tahun, kabkota) |>
## dplyr::summarise(
## kasus = sum(kasus, na.rm = TRUE),
## penduduk = sum(penduduk, na.rm = TRUE),
## .groups = "drop_last"
## ) |>
## dplyr::ungroup() |>
## dplyr::mutate(
## label = kabkota,
## prev_per100k = dplyr::if_else(penduduk > 0, (kasus / penduduk) * 1e5, NA_real_)
## ) |>
## dplyr::arrange(label, tahun)
## }
## })
## series_yoy <- reactive({
## dd <- series_time()
## if (identical(input$trend_level, "Provinsi")) {
## dd |>
## dplyr::arrange(tahun) |>
## dplyr::mutate(yoy = (kasus - dplyr::lag(kasus)) / dplyr::lag(kasus))
## } else {
## dd |>
## dplyr::group_by(label) |>
## dplyr::arrange(tahun, .by_group = TRUE) |>
## dplyr::mutate(yoy = (kasus - dplyr::lag(kasus)) / dplyr::lag(kasus)) |>
## dplyr::ungroup()
## }
## })
## output$trend_total <- renderPlotly({
## dd <- series_time()
## if (identical(input$metric, "prev")) {
## p <- ggplot(dd, aes(x = tahun, y = prev_per100k, group = label, text = label)) +
## geom_line(aes(color = label), linewidth = 1) +
## geom_point(aes(color = label), size = 2) +
## scale_x_continuous(breaks = sort(unique(dd$tahun))) +
## labs(title = if (identical(input$trend_level, "Provinsi"))
## "Prevalensi HIV per Tahun β Provinsi" else "Prevalensi HIV per Tahun β Kab/Kota",
## x = NULL, y = "per 100.000", color = NULL)
## } else {
## p <- ggplot(dd, aes(x = tahun, y = kasus, group = label, text = label)) +
## geom_line(aes(color = label), linewidth = 1) +
## geom_point(aes(color = label), size = 2) +
## scale_x_continuous(breaks = sort(unique(dd$tahun))) +
## scale_y_continuous(labels = scales::comma) +
## labs(title = if (identical(input$trend_level, "Provinsi"))
## "Total Kasus HIV per Tahun β Provinsi" else "Total Kasus HIV per Tahun β Kab/Kota",
## x = NULL, y = "Kasus", color = NULL)
## }
## ggplotly(p + theme_minimal(base_size = 12))
## })
## output$trend_yoy <- renderPlotly({
## dd <- series_yoy() |> dplyr::filter(!is.na(yoy) & is.finite(yoy))
## p <- ggplot(dd, aes(x = tahun, y = yoy, group = label)) +
## geom_hline(yintercept = 0, linetype = 2) +
## geom_line(aes(color = label), linewidth = 1) +
## geom_point(aes(color = label), size = 2) +
## scale_x_continuous(breaks = sort(unique(dd$tahun))) +
## scale_y_continuous(labels = scales::percent_format(accuracy = 0.1)) +
## labs(title = if (identical(input$trend_level, "Provinsi"))
## "Perbandingan Tahunan (YoY) β Provinsi" else "Perbandingan Tahunan (YoY) β Kab/Kota",
## x = NULL, y = "YoY", color = NULL)
## ggplotly(p + theme_minimal(base_size = 12))
## })
## output$trend_pop <- renderPlotly({
## dd <- series_time()
## p <- ggplot(dd, aes(x = tahun, y = penduduk, group = label)) +
## geom_line(aes(color = label), linewidth = 1) +
## geom_point(aes(color = label), size = 2) +
## scale_x_continuous(breaks = sort(unique(dd$tahun))) +
## scale_y_continuous(labels = lab_si) +
## labs(title = if (identical(input$trend_level, "Provinsi"))
## "Jumlah Penduduk per Tahun β Provinsi" else "Jumlah Penduduk per Tahun β Kab/Kota",
## x = NULL, y = "Penduduk", color = NULL)
## ggplotly(p + theme_minimal(base_size = 12))
## })
##
## # --- Top-10 Wilayah --------------------------------------------------------
## output$rankTop10 <- renderPlotly({
## if (input$metric == "prev") {
## dd <- dff() |>
## dplyr::group_by(kabkota) |>
## dplyr::summarise(prev_per100k = sum(kasus) / sum(penduduk) * 1e5, .groups = "drop") |>
## dplyr::arrange(dplyr::desc(prev_per100k)) |>
## dplyr::slice_head(n = 10)
## p <- ggplot(dd, aes(x = reorder(kabkota, prev_per100k), y = prev_per100k, fill = prev_per100k)) +
## geom_col() + coord_flip() +
## scale_fill_viridis_c(option = "C", end = .95) +
## labs(title = paste0("Top-10 Prevalensi (", input$tahun, ")"), x = NULL, y = "per 100.000", fill = "per 100rb") +
## theme_minimal(base_size = 12)
## } else if (input$metric == "pop") {
## dd <- dff() |>
## dplyr::group_by(kabkota) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop") |>
## dplyr::arrange(dplyr::desc(penduduk)) |>
## dplyr::slice_head(n = 10)
## p <- ggplot(dd, aes(x = reorder(kabkota, penduduk), y = penduduk, fill = penduduk)) +
## geom_col() + coord_flip() +
## scale_y_continuous(labels = lab_si) +
## scale_fill_viridis_c(option = "C", end = .95) +
## labs(title = paste0("Top-10 Penduduk (", input$tahun, ")"), x = NULL, y = "Penduduk", fill = "Penduduk") +
## theme_minimal(base_size = 12)
## } else {
## dd <- dff() |>
## dplyr::group_by(kabkota) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop") |>
## dplyr::arrange(dplyr::desc(kasus)) |>
## dplyr::slice_head(n = 10)
## p <- ggplot(dd, aes(x = reorder(kabkota, kasus), y = kasus, fill = kasus)) +
## geom_col() + coord_flip() +
## scale_y_continuous(labels = scales::comma) +
## scale_fill_viridis_c(option = "C", end = .95) +
## labs(title = paste0("Top-10 Kasus (", input$tahun, ")"), x = NULL, y = "Kasus", fill = "Kasus") +
## theme_minimal(base_size = 12)
## }
## ggplotly(p)
## })
##
## # --- GENDER (grafik) -------------------------------------------------------
## output$gender_plot <- renderPlotly({
## yr <- req(input$tahun)
## lvl <- req(input$gender_level)
## met <- req(input$metric)
##
## # sumber kasus & penduduk
## kasus <- hiv_clean |>
## dplyr::filter(tahun == yr, !is.na(gender))
## pop <- pop_clean |>
## dplyr::filter(tahun == yr, !is.na(gender))
##
## if (identical(lvl, "Provinsi")) {
## kasus_agg <- kasus |>
## dplyr::group_by(gender) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop")
## pop_agg <- pop |>
## dplyr::group_by(gender) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
##
## dd <- kasus_agg |>
## dplyr::left_join(pop_agg, by = "gender") |>
## dplyr::mutate(prev_per100k = ifelse(!is.na(penduduk) & penduduk > 0, kasus/penduduk*1e5, NA_real_))
##
## p <- switch(met,
## "prev" = {
## if (all(is.na(dd$prev_per100k))) {
## ggplot(dd, aes(gender, kasus, fill = gender)) + geom_col() +
## scale_y_continuous(labels = scales::comma) +
## labs(title = paste0("Kasus HIV menurut Gender β Provinsi (", yr, ")"), x = NULL, y = "Kasus")
## } else {
## ggplot(dd, aes(gender, prev_per100k, fill = gender)) + geom_col() +
## labs(title = paste0("Prevalensi HIV per Gender β Provinsi (", yr, ")"), x = NULL, y = "per 100.000")
## }
## },
## "pop" = {
## if (all(is.na(dd$penduduk))) {
## ggplot(dd, aes(gender, kasus, fill = gender)) + geom_col() +
## scale_y_continuous(labels = scales::comma) +
## labs(title = paste0("Kasus HIV menurut Gender β Provinsi (", yr, ")"), x = NULL, y = "Kasus")
## } else {
## ggplot(dd, aes(gender, penduduk, fill = gender)) + geom_col() +
## scale_y_continuous(labels = lab_si) +
## labs(title = paste0("Penduduk menurut Gender β Provinsi (", yr, ")"), x = NULL, y = "Penduduk")
## }
## },
## {
## ggplot(dd, aes(gender, kasus, fill = gender)) + geom_col() +
## scale_y_continuous(labels = scales::comma) +
## labs(title = paste0("Kasus HIV menurut Gender β Provinsi (", yr, ")"), x = NULL, y = "Kasus")
## }
## )
## } else {
## validate(need(length(input$gender_kab) > 0, "Pilih minimal 1 kab/kota."))
## kasus_agg <- kasus |>
## dplyr::filter(kabkota %in% input$gender_kab) |>
## dplyr::group_by(kabkota, gender) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop")
## pop_agg <- pop |>
## dplyr::filter(kabkota %in% input$gender_kab) |>
## dplyr::group_by(kabkota, gender) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
##
## dd <- kasus_agg |>
## dplyr::left_join(pop_agg, by = c("kabkota","gender")) |>
## dplyr::mutate(prev_per100k = ifelse(!is.na(penduduk) & penduduk > 0, kasus/penduduk*1e5, NA_real_))
##
## p <- switch(met,
## "prev" = {
## yvar <- if (all(is.na(dd$prev_per100k))) "kasus" else "prev_per100k"
## ylab <- if (yvar == "kasus") "Kasus" else "per 100.000"
## ggplot(dd, aes(x = reorder(kabkota, ifelse(yvar=="kasus", kasus, prev_per100k)),
## y = .data[[yvar]], fill = gender)) +
## geom_col(position = "dodge") + coord_flip() +
## { if (yvar=="kasus") scale_y_continuous(labels = scales::comma) else NULL } +
## labs(title = paste0("Gender menurut Kab/Kota (", yr, ")"), x = NULL, y = ylab, fill = "Gender")
## },
## "pop" = {
## yvar <- if (all(is.na(dd$penduduk))) "kasus" else "penduduk"
## ylab <- if (yvar == "kasus") "Kasus" else "Penduduk"
## ggplot(dd, aes(x = reorder(kabkota, .data[[yvar]]),
## y = .data[[yvar]], fill = gender)) +
## geom_col(position = "dodge") + coord_flip() +
## { if (yvar=="penduduk") scale_y_continuous(labels = lab_si) else scale_y_continuous(labels = scales::comma) } +
## labs(title = paste0("Gender menurut Kab/Kota (", yr, ")"), x = NULL, y = ylab, fill = "Gender")
## },
## {
## ggplot(dd, aes(x = reorder(kabkota, kasus), y = kasus, fill = gender)) +
## geom_col(position = "dodge") + coord_flip() +
## scale_y_continuous(labels = scales::comma) +
## labs(title = paste0("Kasus HIV menurut Gender β Kab/Kota (", yr, ")"), x = NULL, y = "Kasus", fill = "Gender")
## }
## )
## }
##
## ggplotly(p + theme_minimal(base_size = 12) + theme(legend.title = element_blank()))
## })
##
## # --- Tabel Utama & Gender --------------------------------------------------
## output$table_main <- renderDT({
## dff() |>
## dplyr::select(tahun, kabkota, kasus, penduduk, prev_per100k) |>
## dplyr::transmute(
## Tahun = tahun,
## `Kab/Kota` = kabkota,
## Kasus = kasus,
## Penduduk = penduduk,
## `Prevalensi per 100.000` = round(prev_per100k, 2)
## ) |>
## datatable(options = list(pageLength = 10, scrollX = TRUE, dom = "tip"),
## class = "stripe hover row-border order-column", style = "bootstrap5")
## })
## output$table_gender <- renderDT({
## yr <- req(input$tahun)
## lvl <- req(input$gender_level)
##
## if (identical(lvl, "Provinsi")) {
## # agregat provinsi (2 baris: L/P)
## kasus_gender <- hiv_clean |>
## dplyr::filter(tahun == yr, !is.na(gender)) |>
## dplyr::group_by(gender) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop")
## pop_gender <- pop_clean |>
## dplyr::filter(tahun == yr, !is.na(gender)) |>
## dplyr::group_by(gender) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
##
## dd <- kasus_gender |>
## dplyr::left_join(pop_gender, by = "gender") |>
## dplyr::mutate(prev_per100k = dplyr::if_else(!is.na(penduduk) & penduduk > 0, kasus/penduduk*1e5, NA_real_)) |>
## dplyr::transmute(
## Tahun = yr, Level = "PROVINSI", Gender = gender, Kasus = kasus,
## Penduduk = penduduk, `Prevalensi per 100.000` = round(prev_per100k, 2)
## )
## } else {
## # Level Kab/Kota
## if (is.null(input$gender_kab) || length(input$gender_kab) == 0) {
## # placeholder kosong (hilangkan spinner)
## dd <- tibble::tibble(
## Tahun = integer(), `Kab/Kota` = character(), Gender = character(),
## Kasus = integer(), Penduduk = numeric(), `Prevalensi per 100.000` = numeric()
## )
## } else {
## kasus_gender_kab <- hiv_clean |>
## dplyr::filter(tahun == yr, !is.na(gender), kabkota %in% input$gender_kab) |>
## dplyr::group_by(kabkota, gender) |>
## dplyr::summarise(kasus = sum(kasus, na.rm = TRUE), .groups = "drop")
## pop_gender_kab <- pop_clean |>
## dplyr::filter(tahun == yr, !is.na(gender), kabkota %in% input$gender_kab) |>
## dplyr::group_by(kabkota, gender) |>
## dplyr::summarise(penduduk = sum(penduduk, na.rm = TRUE), .groups = "drop")
##
## dd <- kasus_gender_kab |>
## dplyr::left_join(pop_gender_kab, by = c("kabkota","gender")) |>
## dplyr::mutate(prev_per100k = dplyr::if_else(!is.na(penduduk) & penduduk > 0, kasus/penduduk*1e5, NA_real_)) |>
## dplyr::transmute(
## Tahun = yr, `Kab/Kota` = kabkota, Gender = gender, Kasus = kasus,
## Penduduk = penduduk, `Prevalensi per 100.000` = round(prev_per100k, 2)
## ) |>
## dplyr::arrange(`Kab/Kota`, Gender)
## }
## }
##
## datatable(
## dd,
## options = list(pageLength = 10, scrollX = TRUE, dom = "tip"),
## class = "stripe hover row-border order-column",
## style = "bootstrap5",
## caption = htmltools::tags$caption(
## style = "caption-side: top; text-align:left;",
## if (identical(lvl, "Kab/Kota") && (is.null(input$gender_kab) || length(input$gender_kab)==0))
## "Pilih kab/kota terlebih dahulu." else NULL
## )
## )
## })
## }
##
## shinyApp(ui, server)