ANALISIS EPIDEMIOLOGIS DAN PERAMALAN KASUS HEPATITIS A DI PROVINSI DKI JAKARTA
Ditujukan Guna Memenuhi Ujian Akhir Semester Mata Kuliah Epidemiologi
Muhammad Al Fauzan (140610230011)
Penelitian ini bertujuan untuk menganalisis pola epidemiologis dan melakukan peramalan kasus Hepatitis A di Provinsi DKI Jakarta berdasarkan pendekatan spasial dan temporal. Data yang digunakan berasal dari Sistem Surveilans Penyakit Dinas Kesehatan Provinsi DKI Jakarta dan Badan Pusat Statistik (BPS) Provinsi DKI Jakarta periode 2017–2025. Analisis epidemiologis dilakukan melalui perhitungan incidence rate (IR) per 100.000 penduduk, analisis spasial antar kabupaten/kota, serta uji autokorelasi spasial menggunakan Moran’s I, sedangkan analisis temporal dilakukan dengan metode peramalan Holt, Exponential Smoothing (ETS), dan Autoregressive Integrated Moving Average (ARIMA). Hasil penelitian menunjukkan bahwa kasus Hepatitis A di Provinsi DKI Jakarta mengalami fluktuasi yang signifikan dengan puncak kasus pada tahun 2019 dan penurunan tajam pada tahun 2021, serta distribusi IR yang tidak merata antarwilayah pada tahun 2025 tanpa adanya autokorelasi spasial yang signifikan. Evaluasi peramalan menunjukkan bahwa metode Holt memberikan kinerja terbaik dibandingkan ETS dan ARIMA, sementara proyeksi 12 bulan ke depan mengindikasikan jumlah kasus relatif rendah hingga menengah dengan tingkat ketidakpastian yang cukup tinggi. Temuan ini diharapkan dapat menjadi dasar pendukung dalam perencanaan surveilans dan pengendalian Hepatitis A berbasis data di Provinsi DKI Jakarta.
Kata kunci: Hepatitis A, epidemiologi, incidence rate, analisis spasial, peramalan, DKI Jakarta.
This study aims to analyze epidemiological patterns and forecast Hepatitis A cases in DKI Jakarta Province based on spatial and temporal approaches. The data used are from the Disease Surveillance System of the DKI Jakarta Provincial Health Office and the Central Statistics Agency (BPS) of DKI Jakarta Province for the period 2017–2025. Epidemiological analysis was conducted through calculating the incidence rate (IR) per 100,000 population, spatial analysis between districts/cities, and spatial autocorrelation tests using Moran’s I, while temporal analysis was conducted using the Holt forecasting method, Exponential Smoothing (ETS), and Autoregressive Integrated Moving Average (ARIMA). The results of the study indicate that Hepatitis A cases in DKI Jakarta Province experienced significant fluctuations with a peak in 2019 and a sharp decline in 2021, as well as an uneven distribution of IR between regions in 2025 without significant spatial autocorrelation. Forecasting evaluations showed that the Holt method performed better than ETS and ARIMA, while 12-month projections indicated a relatively low to medium number of cases with a high degree of uncertainty. These findings are expected to provide a basis for data-driven Hepatitis A surveillance and control planning in Jakarta Province.
Keywords: Hepatitis A, epidemiology, incidence rate, spatial analysis, forecasting, DKI Jakarta.
Hepatitis A merupakan penyakit infeksi akut pada hati yang disebabkan oleh virus Hepatitis A (HAV) dan umumnya ditularkan melalui jalur fekal–oral, terutama melalui konsumsi makanan dan minuman yang terkontaminasi [1]. Penyakit ini masih menjadi masalah kesehatan masyarakat di berbagai negara berkembang, termasuk Indonesia, terutama pada wilayah dengan kepadatan penduduk tinggi dan sanitasi lingkungan yang belum optimal [2].
Di Indonesia, Hepatitis A secara berkala menimbulkan kejadian luar biasa (KLB), khususnya di lingkungan sekolah, asrama, dan permukiman padat penduduk [3]. Provinsi DKI Jakarta sebagai pusat aktivitas ekonomi dan pemerintahan memiliki karakteristik epidemiologis yang unik, yaitu kepadatan penduduk yang sangat tinggi, mobilitas penduduk yang intens, serta heterogenitas kondisi sanitasi dan perilaku hidup bersih dan sehat antarwilayah administratif [4]. Kondisi tersebut berpotensi meningkatkan risiko penularan penyakit berbasis lingkungan, termasuk Hepatitis A.
Pendekatan epidemiologi memegang peranan penting dalam memahami pola kejadian Hepatitis A melalui analisis distribusi orang, tempat, dan waktu [5]. Salah satu indikator utama yang digunakan adalah incidence rate (IR), yang menggambarkan tingkat kejadian kasus baru dalam suatu populasi pada periode tertentu [6]. Analisis IR secara spasial antar kabupaten/kota memungkinkan identifikasi wilayah dengan risiko relatif lebih tinggi, sehingga dapat mendukung penentuan prioritas intervensi kesehatan masyarakat [7].
Selain analisis deskriptif dan spasial, pemanfaatan analisis deret waktu (time series) menjadi semakin penting dalam epidemiologi modern. Analisis temporal memungkinkan peneliti untuk memahami tren, fluktuasi musiman, serta lonjakan kasus yang dapat mengindikasikan terjadinya wabah [8]. Lebih lanjut, peramalan (forecasting) kasus penyakit menular menggunakan metode statistik seperti Holt, Exponential Smoothing (ETS), dan Autoregressive Integrated Moving Average (ARIMA) telah banyak digunakan untuk mendukung sistem peringatan dini dan perencanaan sumber daya kesehatan [9][10].
Meskipun data Hepatitis A di Provinsi DKI Jakarta telah tersedia melalui Sistem Surveilans Penyakit Dinas Kesehatan Provinsi DKI Jakarta dan data penduduk dari Badan Pusat Statistik (BPS), pemanfaatan data tersebut meliputi analisis epidemiologis, spasial, dan peramalan masih relatif terbatas dalam kajian akademik. Oleh karena itu, diperlukan suatu penelitian yang mengintegrasikan pendekatan epidemiologi deskriptif, analisis spasial, dan peramalan deret waktu untuk memberikan gambaran yang komprehensif mengenai dinamika Hepatitis A di Provinsi DKI Jakarta. Penelitian ini yang diharapkan dapat memberikan kontribusi ilmiah serta menjadi masukan bagi pengambilan kebijakan kesehatan masyarakat berbasis data.
Berdasarkan latar belakang yang telah diuraikan, rumusan masalah dalam penelitian ini adalah sebagai berikut:
Tujuan umum penelitian ini adalah untuk menganalisis pola epidemiologis dan melakukan peramalan kasus Hepatitis A di Provinsi DKI Jakarta berdasarkan data surveilans dan data kependudukan resmi. Secara khusus, penelitian ini bertujuan untuk:
Epidemiologi didefinisikan sebagai ilmu yang mempelajari distribusi dan determinan kejadian penyakit dalam populasi serta penerapannya untuk pengendalian masalah kesehatan [5]. Pendekatan epidemiologi menekankan analisis berdasarkan tiga dimensi utama, yaitu orang (person), tempat (place), dan waktu (time) [6].
Model agent–host–environment merupakan kerangka klasik dalam epidemiologi penyakit menular [11].
1. Agent (Agen Penyebab)
Agent pada kasus Hepatitis A
adalah Virus Hepatitis A (Hepatitis A Virus/HAV), yaitu virus RNA dari
famili Picornaviridae dan genus Hepatovirus. Virus ini bersifat tahan
terhadap kondisi lingkungan, mampu bertahan hidup dalam air dan makanan
yang terkontaminasi, serta resisten terhadap suhu rendah dan kondisi
asam ringan. HAV ditularkan terutama melalui jalur fekal–oral, baik
melalui konsumsi makanan dan minuman yang terkontaminasi tinja
penderita, maupun melalui kontak langsung antarindividu. Masa inkubasi
yang relatif panjang, berkisar 15–50 hari, menyebabkan infeksi sering
tidak terdeteksi sejak awal, sehingga memungkinkan terjadinya penularan
lanjutan sebelum gejala klinis muncul. Selain itu, infeksi HAV pada anak
sering bersifat asimtomatik atau ringan, yang semakin meningkatkan
potensi penyebaran virus di masyarakat.
2. Host (Inang)
Host pada Hepatitis A adalah manusia yang
rentan terhadap infeksi HAV, dengan tingkat kerentanan yang dipengaruhi
oleh faktor biologis, perilaku, dan imunitas. Kelompok host yang
berisiko tinggi meliputi anak-anak usia sekolah, remaja, serta individu
dewasa yang belum memiliki kekebalan terhadap HAV, baik melalui infeksi
sebelumnya maupun vaksinasi.
Perilaku host memegang peranan
penting dalam penularan penyakit, terutama kebiasaan higiene personal
yang kurang baik, seperti tidak mencuci tangan sebelum makan atau
setelah buang air besar. Di wilayah perkotaan padat seperti DKI Jakarta,
mobilitas penduduk yang tinggi serta interaksi sosial yang intens,
misalnya di sekolah, tempat kerja, dan pusat kuliner, meningkatkan
peluang kontak antara individu rentan dengan sumber infeksi. Tingkat
keparahan gejala juga cenderung lebih berat pada usia dewasa
dibandingkan anak-anak, sehingga beban penyakit pada kelompok usia
produktif menjadi lebih signifikan.
3. Environment (Lingkungan)
Environment atau lingkungan
berperan sebagai faktor pendukung utama dalam transmisi Hepatitis A.
Lingkungan dengan sanitasi yang tidak memadai, akses air bersih yang
terbatas, serta pengelolaan limbah yang kurang baik menciptakan kondisi
ideal bagi penyebaran HAV. Kontaminasi sumber air dan makanan oleh tinja
yang mengandung virus menjadi jalur penularan yang dominan.
Kondisi kepadatan penduduk yang tinggi di DKI Jakarta memperbesar risiko
terjadinya klaster kasus, terutama di kawasan permukiman padat, sekolah,
dan pusat aktivitas ekonomi. Selain itu, keberadaan pedagang makanan
dengan standar higiene yang tidak konsisten dapat menjadi sumber
penularan tidak langsung. Faktor lingkungan ini menjelaskan mengapa
distribusi kasus Hepatitis A sering kali tidak merata secara spasial
antarwilayah, sebagaimana terlihat pada variasi incidence rate (IR)
antar kabupaten/kota.
Terjadinya kasus Hepatitis A merupakan hasil interaksi dinamis antara virus HAV yang mampu bertahan di lingkungan, host yang rentan akibat kurangnya imunitas dan perilaku berisiko, serta lingkungan yang mendukung transmisi melalui sanitasi dan kepadatan penduduk. Oleh karena itu, pengendalian Hepatitis A tidak dapat hanya berfokus pada satu komponen saja, melainkan memerlukan pendekatan terpadu melalui peningkatan higiene dan sanitasi lingkungan, perlindungan host melalui edukasi dan vaksinasi, serta penguatan sistem surveilans untuk mendeteksi dan merespons penularan sejak dini. Hepatitis A adalah penyakit hati akut yang umumnya bersifat self-limiting, namun dapat menyebabkan beban kesehatan yang signifikan ketika terjadi wabah [1]. Penularan terutama terjadi melalui konsumsi makanan atau air yang terkontaminasi, serta kontak dekat antarindividu [12]. Studi sebelumnya menunjukkan bahwa wilayah perkotaan padat penduduk memiliki risiko lebih tinggi terhadap wabah Hepatitis A [4][13].
Incidence rate merupakan ukuran frekuensi kejadian kasus baru dalam suatu populasi pada periode tertentu [6]. Ukuran ini banyak digunakan dalam studi epidemiologi penyakit menular untuk membandingkan risiko antarwilayah dan antarperiode waktu [7][14].
Analisis spasial digunakan untuk mengkaji pola geografis kejadian penyakit dan mengidentifikasi area berisiko tinggi [15]. Salah satu metode yang umum digunakan adalah Moran’s I, yang mengukur derajat autokorelasi spasial global [16]. Nilai Moran’s I yang signifikan menunjukkan adanya pengelompokan (clustering) spasial, sedangkan nilai yang tidak signifikan mengindikasikan distribusi yang relatif acak.
Analisis temporal dalam epidemiologi bertujuan untuk memahami pola
kejadian penyakit berdasarkan dimensi waktu, termasuk tren jangka
panjang, fluktuasi musiman, dan perubahan mendadak akibat kejadian luar
biasa seperti kejadian luar biasa atau intervensi kesehatan masyarakat
[8]. Pendekatan ini penting untuk mengidentifikasi periode peningkatan
risiko, mengevaluasi efektivitas kebijakan pengendalian penyakit, serta
mendukung pengambilan keputusan berbasis bukti.
Dalam konteks
penyakit menular seperti Hepatitis A, analisis temporal memiliki peran
strategis karena penyakit ini sering menunjukkan fluktuasi kasus yang
dipengaruhi oleh faktor lingkungan, perilaku masyarakat, serta kondisi
sanitasi. Oleh karena itu, peramalan deret waktu (time series
forecasting) digunakan untuk memperkirakan jumlah kasus di masa
mendatang berdasarkan pola historis, sehingga dapat dimanfaatkan sebagai
bagian dari sistem peringatan dini (early warning system) dan
perencanaan intervensi kesehatan yang lebih proaktif [9].
Berbagai metode peramalan deret waktu telah dikembangkan, mulai dari
metode smoothing hingga model berbasis stokastik. Dalam penelitian ini,
digunakan tiga metode utama yang umum diaplikasikan dalam epidemiologi,
yaitu metode Holt, Exponential Smoothing (ETS), dan Autoregressive
Integrated Moving Average (ARIMA).
Metode Holt merupakan pengembangan dari simple exponential smoothing yang dirancang untuk menangani data deret waktu yang memiliki pola tren, tetapi tidak mengandung komponen musiman [10]. Metode ini memperhitungkan dua komponen utama, yaitu level (tingkat rata-rata) dan tren (arah perubahan data), yang diperbarui secara eksponensial pada setiap periode waktu. Keunggulan metode Holt terletak pada kesederhanaan model dan kemampuannya dalam menangkap perubahan tren secara adaptif. Dalam konteks epidemiologi, metode Holt sering digunakan untuk memodelkan penyakit dengan pola tren jangka panjang yang relatif stabil, tanpa fluktuasi musiman yang kuat. Selain itu, metode ini tidak memerlukan proses transformasi atau differencing data, sehingga relatif mudah diterapkan dan diinterpretasikan [11]. Namun demikian, metode Holt memiliki keterbatasan ketika data menunjukkan pola musiman yang kompleks atau perubahan struktural yang tajam. Oleh karena itu, evaluasi kinerja model tetap diperlukan untuk memastikan bahwa metode ini sesuai dengan karakteristik data kasus Hepatitis A yang dianalisis.
Exponential Smoothing atau ETS merupakan keluarga model peramalan yang lebih fleksibel dibandingkan metode Holt, karena mampu mengakomodasi berbagai kombinasi komponen level, tren, dan musiman secara otomatis [9]. Model ETS dinyatakan dalam notasi (Error, Trend, Seasonal), yang memungkinkan penyesuaian bentuk error (aditif atau multiplikatif), tren, serta pola musiman sesuai dengan karakteristik data. Dalam aplikasi epidemiologi, ETS banyak digunakan karena kemampuannya untuk menangkap pola kompleks pada data penyakit menular, termasuk fluktuasi musiman yang sering terjadi akibat perubahan cuaca, mobilitas penduduk, atau kebiasaan sosial [12]. Pemilihan struktur model ETS biasanya dilakukan secara otomatis menggunakan kriteria informasi, seperti Akaike Information Criterion (AIC), sehingga model yang dihasilkan bersifat data-driven. Kelebihan utama ETS adalah kemampuannya menghasilkan interval prediksi yang informatif, yang sangat berguna dalam konteks kesehatan masyarakat untuk menggambarkan ketidakpastian peramalan. Meskipun demikian, ETS tetap bersifat univariat dan tidak secara eksplisit memodelkan hubungan kausal dengan faktor eksternal, sehingga interpretasi hasil harus dilakukan dengan hati-hati.
Model Autoregressive Integrated Moving Average (ARIMA) merupakan salah satu model deret waktu yang paling banyak digunakan dalam analisis epidemiologi [17]. Model ini mengombinasikan tiga komponen utama, yaitu autoregressive (AR), integrated (I), dan moving average (MA), yang masing-masing berfungsi untuk menangkap ketergantungan data terhadap nilai masa lalu, mengatasi ketidakstasioneran melalui differencing, serta memodelkan error acak. Keunggulan ARIMA terletak pada kerangka teoritis yang kuat dan kemampuannya dalam menangani data deret waktu yang tidak stasioner, yang sering dijumpai pada data kasus penyakit menular. Dalam praktik epidemiologi, ARIMA banyak digunakan untuk peramalan penyakit seperti influenza, dengue, dan hepatitis, baik dalam skala regional maupun nasional [18]. Proses pemodelan ARIMA melibatkan beberapa tahap penting, yaitu identifikasi orde model, pengujian stasioneritas, estimasi parameter, serta evaluasi diagnostik residu. Meskipun model ini relatif kompleks dibandingkan metode smoothing, ARIMA sering memberikan hasil peramalan yang kompetitif, terutama ketika data menunjukkan pola autocorrelation yang kuat. Dalam penelitian ini, model ARIMA digunakan sebagai pembanding terhadap metode smoothing untuk mengevaluasi metode peramalan yang paling sesuai dengan karakteristik temporal kasus Hepatitis A di Provinsi DKI Jakarta.
Penelitian ini menggunakan desain penelitian deskriptif kuantitatif dengan pendekatan cross-sectional dan time series. Pendekatan cross-sectional digunakan untuk menggambarkan distribusi dan tingkat kejadian kasus Hepatitis A antarwilayah administrasi kabupaten/kota di Provinsi DKI Jakarta pada periode tertentu, sedangkan pendekatan time series digunakan untuk menganalisis pola temporal serta melakukan peramalan jumlah kasus Hepatitis A berdasarkan data runtun waktu bulanan.
Pendekatan deskriptif dipilih karena penelitian ini bertujuan untuk memotret kondisi epidemiologis yang terjadi, bukan untuk menguji hubungan kausal antarvariabel. Selain itu, kombinasi analisis epidemiologis dan peramalan deret waktu memungkinkan penelitian ini tidak hanya memberikan gambaran beban penyakit saat ini, tetapi juga memproyeksikan kecenderungan kasus di masa mendatang sebagai bagian dari sistem kewaspadaan dini penyakit menular.
Penelitian ini menggunakan data sekunder yang diperoleh dari instansi resmi pemerintah, sehingga memiliki tingkat validitas dan reliabilitas yang tinggi. Adapun sumber data yang digunakan adalah sebagai berikut:
Data jumlah penduduk diperoleh dari Badan Pusat Statistik (BPS) Provinsi DKI Jakarta, yang memuat informasi jumlah penduduk menurut kabupaten/kota dan tahun pengamatan. Data ini digunakan sebagai populasi berisiko (denominator) dalam perhitungan ukuran epidemiologis incidence rate Hepatitis A.
Data kasus Hepatitis A diperoleh dari Sistem Surveilans Penyakit Dinas Kesehatan Provinsi DKI Jakarta, yang mencatat jumlah kasus Hepatitis A secara berkala berdasarkan wilayah administrasi dan waktu kejadian. Data ini digunakan sebagai numerator dalam analisis epidemiologis serta sebagai data utama dalam analisis deret waktu dan peramalan.
Variabel yang digunakan dalam penelitian ini terdiri atas variabel utama dan variabel turunan, yang dijelaskan sebagai berikut:
Jumlah kasus Hepatitis A yang dilaporkan pada setiap wilayah kabupaten/kota dan periode waktu tertentu. Variabel ini merupakan variabel utama dalam analisis epidemiologis dan peramalan.
Jumlah penduduk pada masing-masing wilayah kabupaten/kota di Provinsi DKI Jakarta. Variabel ini digunakan sebagai populasi berisiko dalam perhitungan incidence rate.
Variabel turunan yang menggambarkan tingkat kejadian kasus baru Hepatitis A pada populasi berisiko dalam periode waktu tertentu. IR digunakan untuk membandingkan risiko kejadian Hepatitis A antarwilayah dengan ukuran populasi yang berbeda.
Variabel waktu yang digunakan dalam analisis deret waktu untuk mengidentifikasi pola tren, fluktuasi, dan melakukan peramalan jumlah kasus Hepatitis A.
Analisis data dalam penelitian ini dilakukan melalui beberapa tahapan analisis epidemiologis dan statistika sebagai berikut:
Incidence Rate digunakan untuk menggambarkan tingkat kejadian Hepatitis A dengan mempertimbangkan ukuran populasi pada masing-masing wilayah. Perhitungan incidence rate dilakukan menggunakan rumus:
\[IR = \frac{Jumlah\ Kasus\ Hepatitis\ A}{Jumlah\ Penduduk} \times 100.000\]
Nilai incidence rate dinyatakan per 100.000 penduduk agar mudah diinterpretasikan dan memungkinkan perbandingan risiko kejadian Hepatitis A antar kabupaten/kota di Provinsi DKI Jakarta secara objektif.
Analisis deret waktu dilakukan terhadap data jumlah kasus Hepatitis A bulanan untuk mengidentifikasi pola tren dan fluktuasi kasus dari waktu ke waktu. Tahapan analisis deret waktu meliputi:
Untuk memprediksi jumlah kasus Hepatitis A pada periode mendatang, penelitian ini menggunakan tiga metode peramalan deret waktu, yaitu:
Hasil peramalan dari ketiga metode dibandingkan untuk menilai kecenderungan jumlah kasus Hepatitis A serta mengevaluasi metode yang memberikan performa terbaik.
Alur kerja penelitian ini disusun secara sistematis agar proses analisis dapat dilakukan secara terstruktur, dengan tahapan sebagai berikut:
Data penelitian ini mencakup jumlah kasus (Cases), jumlah penduduk (Population), dan Incidence Rate (IR) per 100.000 penduduk. Ukuran epidemiologis tersebut dihitung menggunakan formula:
\[IR = \frac{Jumlah\ Kasus\ Hepatitis\ A}{Jumlah\ Penduduk} \times 100.000\]
IR merupakan ukuran risiko kejadian baru yang dinormalisasi oleh populasi sehingga lebih tepat untuk membandingkan antarwilayah (kab/kota) maupun antarwaktu, terutama ketika jumlah penduduk berbeda besar. Jika hanya melihat jumlah kasus, wilayah berpenduduk besar cenderung tampak “lebih buruk”, padahal risikonya bisa lebih kecil. Karena itu, IR penting sebagai dasar prioritas intervensi (misalnya pengetatan higiene pangan, penyelidikan epidemiologi, dan edukasi perilaku hidup bersih).
Contoh tahun 2017 menunjukkan variasi IR antarwilayah (misalnya Jakarta Selatan 4,89 vs Jakarta Utara 0,618; Kepulauan Seribu 0). Variasi IR menunjukkan perbedaan risiko yang mungkin dipengaruhi oleh kepadatan, mobilitas, perilaku hygiene, serta paparan melalui makanan/air. Nilai IR = 0 (misalnya Kepulauan Seribu pada tahun tertentu) dapat bermakna tidak ada kasus terlapor, namun secara surveilans perlu diingat bahwa nol laporan tidak selalu berarti nol kejadian (potensi under-reporting, akses layanan, atau perubahan definisi/ketepatan pelaporan).
Berikut adalah ringkasan perkembangan kasus Hepatitis A di Provinsi DKI Jakarta selama periode 2017–2025:
| Tahun | Kasus | Penduduk | IR per 100.000 |
|---|---|---|---|
| 2017 | 319 | 10.374.235 | 3,07 |
| 2018 | 453 | 10.467.629 | 4,33 |
| 2019 | 685 | 10.557.810 | 6,49 |
| 2020 | 303 | 10.562.088 | 2,87 |
| 2021 | 74 | 10.605.437 | 0,70 |
| 2022 | 139 | 10.640.007 | 1,31 |
| 2023 | 147 | 10.672.100 | 1,38 |
| 2024 | 146 | 10.685.000 | 1,37 |
| 2025 | 220 | 10.677.975 | 2,06 |
Puncak 2019 (IR 6,49) mengindikasikan periode peningkatan risiko yang kuat dan secara epidemiologi konsisten dengan potensi terjadinya klaster (misalnya paparan bersama melalui makanan/air atau transmisi pada setting komunal seperti sekolah/asrama/lingkungan padat). Penurunan tajam hingga 2021 (IR 0,70) dapat mengindikasikan penurunan transmisi, tetapi juga perlu dipertimbangkan faktor surveilans (perubahan perilaku mencari layanan, perubahan pelaporan, atau dinamika kegiatan masyarakat). Kenaikan kembali pada 2025 (IR 2,06) menunjukkan risiko yang tidak kembali setinggi 2019, namun cukup untuk menandakan perlunya penguatan surveilans dan pencegahan, karena Hepatitis A mudah membentuk klaster ketika kondisi sanitasi/higiene menurun.
| Indikator | Kasus | IR per 100.000 |
|---|---|---|
| Minimum | 74 | 0,698 |
| Maksimum | 685 | 6,49 |
| Mean | 276 | 2,62 |
| Median | 220 | 2,06 |
| Range | 611 | 5,79 |
Range IR yang besar (5,79) menunjukkan volatilitas risiko antar tahun; ini penting karena penyakit berbasis fekal–oral sangat sensitif terhadap perubahan sanitasi, kejadian kontaminasi, dan peristiwa klaster. Median IR (2,06) lebih rendah dari maksimum jauh menandakan adanya tahun “outlier” yang sangat tinggi (2019). Dalam epidemiologi, outlier seperti ini sering terkait kejadian terfokus (KLB) alih-alih peningkatan gradual.
| Indikator | Kasus | IR per 100.000 |
|---|---|---|
| Minimum | 0 | 0 |
| Maksimum | 635 | 3,82 |
| Mean | 414 | 2,28 |
| Median | 520 | 2,44 |
| Range | 635 | 3,82 |
Perbedaan IR antar wilayah menunjukkan heterogenitas risiko: beberapa wilayah mungkin lebih sering mengalami paparan (misalnya rantai makanan, kepadatan hunian, sanitasi lingkungan). Nilai IR maksimum akumulatif 3,82 (selama 9 tahun) menandakan wilayah tersebut secara konsisten menyumbang risiko relatif lebih tinggi sepanjang periode pengamatan, wilayah seperti ini layak menjadi prioritas investigasi faktor risiko lokal dan intervensi higiene-sanitasi yang lebih intensif.
Pemetaan dilakukan untuk melihat sebaran risiko pada tahun terbaru (2025).
| Peringkat | Wilayah | Kasus | Penduduk | IR per 100.000 |
|---|---|---|---|---|
| 1 | Jakarta Barat | 118 | 2.487.199 | 4,74 |
| 2 | Jakarta Utara | 48 | 1.819.009 | 2,64 |
| 3 | Jakarta Timur | 31 | 3.085.058 | 1,00 |
| 4 | Jakarta Pusat | 10 | 1.038.396 | 0,963 |
| 5 | Jakarta Selatan | 13 | 2.219.225 | 0,586 |
| 6 | Kepulauan Seribu | 0 | 29.088 | 0 |
Jakarta Barat (IR 4,74) menjadi wilayah dengan risiko tertinggi pada 2025. Dalam konteks Hepatitis A (fekal–oral), IR tinggi dapat mencerminkan kombinasi: kepadatan, mobilitas, kerentanan higiene pangan, serta potensi paparan pada kelompok komunal. Ini menjadi sinyal prioritas untuk: peningkatan inspeksi higiene pangan, edukasi cuci tangan, dan investigasi klaster (misalnya jika kenaikan terjadi pada waktu yang berdekatan). Jakarta Utara (IR 2,64) sebagai peringkat kedua menunjukkan adanya “kantong” risiko tinggi lainnya. Dalam epidemiologi perkotaan, dua wilayah berisiko tinggi secara bersamaan dapat mengindikasikan adanya faktor struktural yang mirip (misalnya rantai distribusi pangan, kepadatan, atau kondisi sanitasi tertentu). Wilayah dengan IR rendah (Jakarta Selatan 0,586) bukan berarti aman sepenuhnya; IR rendah dapat juga dipengaruhi oleh variasi perilaku pelaporan, akses layanan, atau perbedaan intensitas surveilans. Namun, secara program, wilayah dengan IR rendah dapat dijadikan benchmark untuk menelaah praktik pencegahan yang mungkin lebih baik.
Uji Moran’s I menghasilkan nilai \(-0,1249\) dengan p-value \(0,1961\). Tidak signifikannya autokorelasi spasial positif menunjukkan bahwa pada skala kab/kota, peningkatan risiko tidak membentuk pola “mengelompok” yang kuat secara statistik. Secara epidemiologis, ini bisa berarti penularan/kejadian lebih dipengaruhi paparan spesifik (misalnya klaster institusional atau sumber makanan tertentu), bukan penyebaran gradual antar wilayah bertetangga atau unit analisis terlalu besar (kab/kota) sehingga klaster mikro (kelurahan/kecamatan) tidak tertangkap. Mengingat hanya ada 6 unit wilayah, daya uji statistik juga terbatas. Dengan demikian, rekomendasi yang lebih tajam adalah melakukan pemetaan pada unit yang lebih kecil (kecamatan/kelurahan) apabila data tersedia, agar pola klaster lebih terlihat.
Data deret waktu disusun berdasarkan total kasus bulanan seluruh provinsi.
Ringkasan deret waktu menunjukkan nilai minimum 2 dan maksimum 142 kasus per bulan. Nilai maksimum bulanan 142 mengindikasikan episode lonjakan yang sangat kuat. Hepatitis A memiliki masa inkubasi relatif panjang (minggu), sehingga puncak kasus bulanan sering mencerminkan paparan yang terjadi beberapa minggu sebelumnya. Ini penting untuk investigasi: puncak tajam biasanya lebih konsisten dengan paparan bersama (misalnya kontaminasi makanan/air) dibanding peningkatan perlahan. Mean yang lebih besar dari median (23,02 vs 15,5) menunjukkan adanya bulan-bulan ekstrem yang “mengangkat” rata-rata, tipikal pada penyakit yang dapat mengalami klaster/KLB.
Uji stasioneritas (ADF p-value 0,1377 dan KPSS p-value 0,01512) menunjukkan Ketidakstasioneran yang mengindikasikan adanya perubahan level/tren sepanjang waktu. Dalam epidemiologi, perubahan tersebut dapat mencerminkan dinamika nyata kejadian penyakit (misalnya KLB, perubahan perilaku higienis, intervensi) maupun perubahan sistem pelaporan. Karena itu, pemodelan ARIMA yang dapat mengakomodasi differencing menjadi relevan untuk menangani pola non-stasioner..
Akurasi model dievaluasi menggunakan data uji (test set) 12 bulan terakhir.
| Metode | RMSE | MAE | MAPE (%) |
|---|---|---|---|
| Holt | 8,530 | 6,739 | 64,610 |
| ARIMA | 9,128 | 7,692 | 61,518 |
| ETS | 10,847 | 9,416 | 64,612 |
Holt terbaik pada RMSE/MAE menunjukkan bahwa untuk periode terbaru, pola data lebih cocok ditangkap oleh komponen level-tren sederhana. Ini sering terjadi ketika dinamika terbaru relatif stabil dan tidak menunjukkan musiman kuat. MAPE tinggi (≈61–65%) perlu dibaca hati-hati dalam konteks epidemiologi: ketika kasus bulanan kecil, kesalahan absolut kecil sekalipun dapat menjadi persentase besar. Oleh karena itu, RMSE/MAE sering lebih informatif untuk perencanaan operasional (misalnya estimasi beban layanan), sedangkan MAPE lebih sensitif untuk periode rendah kasus. Dalam aplikasi kesehatan masyarakat, tujuan forecasting bukan hanya “angka tepat”, tetapi juga sinyal kenaikan risiko. Model yang stabil dan responsif terhadap perubahan level sering lebih bermanfaat untuk deteksi dini dibanding mengejar akurasi persentase pada bulan-bulan rendah.
Perbedaan kurva prediksi menunjukkan perbedaan asumsi model terhadap level/tren dan volatilitas. Model dengan interval lebih lebar merefleksikan ketidakpastian yang lebih tinggi, ini penting untuk komunikasi risiko (bukan sekadar angka titik).
Forecast menggunakan seluruh data untuk memproyeksikan 12 bulan berikutnya (Januari – Desember 2026).
Proyeksi mean pada 2026 cenderung berada pada kisaran sekitar 6–25 kasus/bulan tergantung metode dan bulan. Ini mengindikasikan potensi beban kasus tetap ada, meskipun tidak diprediksi setinggi periode puncak historis. Interval prediksi yang lebar (bahkan negatif pada batas bawah) menunjukkan ketidakpastian tinggi akibat volatilitas historis dan adanya lonjakan besar di masa lalu. Dalam epidemiologi, ini menegaskan bahwa sistem harus siap menghadapi skenario lebih buruk (upper bound), terutama karena Hepatitis A dapat melonjak bila ada kejadian kontaminasi/klaster. Secara praktis, nilai batas bawah negatif tidak bermakna secara biologis, untuk interpretasi program dapat dipotong menjadi 0 kasus.
Berikut adalah tabel lengkap hasil peramalan (forecast) yang mencakup nilai rata-rata (mean) serta interval kepercayaan 80% dan 95% untuk ketiga metode (Holt, ETS, dan ARIMA) selama periode Januari hingga Desember 2026:
| Tanggal | Metode | Forecast | Lo80 | Hi80 | Lo95 | Hi95 |
|---|---|---|---|---|---|---|
| 2026-01-01 | Holt | 9,21 | -10,65 | 29,06 | -21,16 | 39,57 |
| 2026-02-01 | Holt | 8,93 | -15,59 | 33,46 | -28,57 | 46,44 |
| 2026-03-01 | Holt | 8,66 | -19,78 | 37,09 | -34,83 | 52,15 |
| 2026-04-01 | Holt | 8,38 | -23,49 | 40,26 | -40,36 | 57,13 |
| 2026-05-01 | Holt | 8,11 | -26,86 | 43,08 | -45,38 | 61,60 |
| 2026-06-01 | Holt | 7,83 | -29,99 | 45,66 | -50,01 | 65,68 |
| 2026-07-01 | Holt | 7,56 | -32,91 | 48,03 | -54,34 | 69,46 |
| 2026-08-01 | Holt | 7,29 | -35,67 | 50,24 | -58,41 | 72,98 |
| 2026-09-01 | Holt | 7,01 | -38,30 | 52,32 | -62,28 | 76,31 |
| 2026-10-01 | Holt | 6,74 | -40,81 | 54,28 | -65,98 | 79,45 |
| 2026-11-01 | Holt | 6,46 | -43,22 | 56,14 | -69,52 | 82,44 |
| 2026-12-01 | Holt | 6,19 | -45,54 | 57,92 | -72,93 | 85,30 |
| 2026-01-01 | ETS | 12,18 | 3,81 | 20,55 | -0,62 | 24,97 |
| 2026-02-01 | ETS | 12,11 | 2,86 | 21,36 | -2,04 | 26,26 |
| 2026-03-01 | ETS | 14,32 | 2,33 | 26,31 | -4,01 | 32,66 |
| 2026-04-01 | ETS | 10,13 | 0,93 | 19,32 | -3,93 | 24,19 |
| 2026-05-01 | ETS | 11,89 | 0,28 | 23,50 | -5,87 | 29,65 |
| 2026-06-01 | ETS | 11,34 | -0,51 | 23,19 | -6,78 | 29,46 |
| 2026-07-01 | ETS | 9,35 | -1,04 | 19,74 | -6,54 | 25,24 |
| 2026-08-01 | ETS | 9,43 | -1,68 | 20,54 | -7,56 | 26,42 |
| 2026-09-01 | ETS | 11,17 | -2,72 | 25,07 | -10,08 | 32,42 |
| 2026-10-01 | ETS | 13,32 | -4,12 | 30,77 | -13,36 | 40,00 |
| 2026-11-01 | ETS | 25,14 | -9,43 | 59,71 | -27,73 | 78,01 |
| 2026-12-01 | ETS | 14,50 | -6,40 | 35,41 | -17,47 | 46,48 |
| 2026-01-01 | ARIMA | 5,27 | -13,16 | 23,69 | -22,92 | 33,45 |
| 2026-02-01 | ARIMA | 8,23 | -14,84 | 31,30 | -27,06 | 43,51 |
| 2026-03-01 | ARIMA | 15,27 | -10,12 | 40,66 | -23,56 | 54,11 |
| 2026-04-01 | ARIMA | 15,72 | -10,97 | 42,41 | -25,10 | 56,54 |
| 2026-05-01 | ARIMA | 14,28 | -13,20 | 41,75 | -27,74 | 56,29 |
| 2026-06-01 | ARIMA | 12,11 | -15,87 | 40,08 | -30,68 | 54,89 |
| 2026-07-01 | ARIMA | 17,15 | -11,16 | 45,47 | -26,15 | 60,46 |
| 2026-08-01 | ARIMA | 20,40 | -8,16 | 48,96 | -23,28 | 64,08 |
| 2026-09-01 | ARIMA | 18,55 | -10,19 | 47,30 | -25,41 | 62,52 |
| 2026-10-01 | ARIMA | 16,18 | -12,71 | 45,08 | -28,01 | 60,37 |
| 2026-11-01 | ARIMA | 15,84 | -13,18 | 44,86 | -28,55 | 60,22 |
| 2026-12-01 | ARIMA | 11,68 | -17,44 | 40,81 | -32,86 | 56,23 |
Forecast di atas memberikan skenario baseline (nilai mean), sedangkan interval memberikan gambaran mengenai skenario ketidakpastian atau risiko peningkatan kasus di masa depan[cite: 408]. [cite_start]Dalam konteks manajemen program kesehatan, nilai upper bound (Hi80 dan Hi95) menjadi sangat relevan untuk keperluan kesiapsiagaan, seperti perencanaan sumber daya surveilans, persiapan investigasi klaster, dan intensifikasi edukasi higiene.
Bulan-bulan dengan nilai upper bound yang lebih tinggi, misalnya pada metode ETS untuk bulan November 2026, dapat ditafsirkan sebagai periode dengan tingkat ketidakpastian terbesar menurut model. [cite_start]Meskipun angka tersebut bukan merupakan kepastian kejadian, informasi ini sebaiknya dijadikan dasar penetapan “bulan kewaspadaan” dalam sistem peringatan dini (early warning system) penyakit Hepatitis A di Provinsi DKI Jakarta.
Penulis mengucapkan terima kasih yang sebesar-besarnya kepada I Gede Nyoman Mindra Jaya, Ph.D. selaku dosen pengampu mata kuliah Epidemiologi, atas bimbingan, arahan, serta penjelasan konseptual yang diberikan selama proses perkuliahan. Arahan tersebut sangat membantu penulis dalam memahami konsep dasar hingga penerapan epidemiologi, khususnya dalam analisis ukuran epidemiologis, interpretasi hasil, serta penyusunan laporan penelitian ini. Penulis juga menyampaikan terima kasih kepada Badan Pusat Statistik (BPS) Provinsi DKI Jakarta atas ketersediaan data kependudukan, serta kepada Dinas Kesehatan Provinsi DKI Jakarta melalui Sistem Surveilans Penyakit, yang telah menyediakan data kasus Hepatitis A sehingga penelitian ini dapat dilaksanakan secara komprehensif dan berbasis data resmi.
Selain itu, penulis mengakui peran teknologi kecerdasan buatan
(Artificial Intelligence/AI) sebagai alat bantu dalam proses penyusunan
penelitian ini. AI digunakan secara terbatas dan bertanggung jawab
untuk:
1. Membantu penulisan serta penyusunan kode Python dalam
analisis ukuran epidemiologis dan peramalan kasus Hepatitis A.
2.
Membantu penyusunan kode R dalam pengembangan dashboard R Shiny
epidemiologis interaktif untuk visualisasi data Hepatitis A Provinsi DKI
Jakarta.
3. Membantu pencarian serta penyesuaian referensi ilmiah
yang relevan dengan topik epidemiologi dan peramalan penyakit
menular.
Penggunaan AI dalam penelitian ini bersifat sebagai alat bantu pendukung, sedangkan seluruh proses analisis, interpretasi hasil, pengambilan keputusan metodologis, dan penulisan akhir laporan tetap berada di bawah tanggung jawab penuh penulis.
Penulis juga mengucapkan terima kasih kepada rekan-rekan mahasiswa yang telah memberikan dukungan, diskusi, dan masukan selama proses pengerjaan tugas ini. Akhir kata, penulis menyadari bahwa laporan ini masih memiliki keterbatasan, sehingga kritik dan saran yang membangun sangat diharapkan sebagai bahan perbaikan di masa mendatang.
[1] World Health Organization. (2017).Hepatitis A. Geneva: WHO
Press.
[2] Centers for Disease Control and Prevention. (2022).
Hepatitis A Epidemiology and Prevention of Vaccine-Preventable Diseases.
Atlanta: CDC.
[3] Gordis, L. (2014). Epidemiology (5th ed.).
Philadelphia: Elsevier Saunders.
[4] Friis, R. H., & Sellers, T.
A. (2021). Epidemiology for Public Health Practice (6th ed.).
Burlington: Jones & Bartlett Learning.
[5] Rothman, K. J.,
Greenland, S., & Lash, T. L. (2008). Modern Epidemiology (3rd ed.).
Philadelphia: Lippincott Williams & Wilkins.
[6] Bonita, R.,
Beaglehole, R., & Kjellström, T. (2006). Basic Epidemiology (2nd
ed.). Geneva: World Health Organization.
[7] Lawson, A. B. (2018).
Bayesian Disease Mapping: Hierarchical Modeling in Spatial Epidemiology
(2nd ed.). Boca Raton: CRC Press.
[8] Brookmeyer, R., & Stroup.
D. F. (2004). Monitoring the Health of Populations: Statistical
Principles and Methods for Public Health Surveillance. New York: Oxford
University Press.
[9] Hyndman, R. J., & Athanasopoulos, G.
(2021). Forecasting: Principles and Practice (3rd ed.). Melbourne:
OTexts.
[10] Holt, C. C. (1957). Forecasting seasonals and trends by
exponentially weighted moving averages. Office of Naval Research
Memorandum, 52, 1-10.
[11] Gardner, E. S. (2006). Exponential
smoothing: The state of the art Part II. International Journal of
Forecasting, 22(4), 637-666.
[12] Taylor, J. W. (2010). Exponential
smoothing with a damped multiplicative trend. International Journal of
Forecasting, 26(2), 281-293.
[13] Cowpertwait, P. S. P., &
Metcalfe, A. V. (2009). Introductory Time Series with R. New York:
Springer.
[14] Chatfield, C. (2004). The Analysis of Time Series: An
Introduction (6th ed.). Boca Raton: Chapman & Hall/CRC.
[15]
Box, G. E. P., Jenkins, G. M., Reinsel, G. C., & Ljung, G. M.
(2015). Time Series Analysis: Forecasting and Control (5th ed.).
Hoboken: John Wiley & Sons.
[16] Helfenstein, U. (1996).
Box-Jenkins modelling of some viral infectious diseases. Statistics in
Medicine, 15(23), 2563-2576.
[17] Cowling, B. J., Lau, E. H. Y.,
Lam, C. L. K., Cheng, C. K. Y., Kovar, J., Chan, K. H., & Peiris, J.
S. M. (2008). Effects of school closures, 2008 winter influenza season,
Hong Kong. Emerging Infectious Diseases, 14(10), 1660-1662.
[18]
Earnest, A., Chen, M. I. C., Ng, D., & Leo, Y. S. (2005). Using
autoregressive integrated moving average (ARIMA) models to predict and
monitor the number of beds occupied during a SARS outbreak in a tertiary
hospital in Singapore. BMC Health Services Research, 5(1), 36.
# ANALISIS HEPATITIS A DKI JAKARTA
rm(list = ls()); gc()
# PACKAGES
pkgs <- c(
"dplyr","tidyr","readr","stringr","lubridate",
"ggplot2","scales","sf","forecast","tseries","spdep"
)
to_install <- pkgs[!pkgs %in% installed.packages()[,"Package"]]
if (length(to_install) > 0) install.packages(to_install, dependencies = TRUE)
invisible(lapply(pkgs, library, character.only = TRUE))
options(scipen = 999)
theme_set(theme_minimal(base_size = 13))
# 1) HELPER FUNCTIONS
num_clean <- function(x) {
x <- as.character(x)
x <- stringr::str_replace_all(x, "\\.", "")
x <- stringr::str_replace_all(x, ",", ".")
suppressWarnings(as.numeric(x))
}
std_wilayah <- function(x) {
x <- stringr::str_squish(as.character(x))
x <- stringr::str_replace_all(x, regex("^Kota\\s+", ignore_case = TRUE), "")
x <- stringr::str_replace_all(x, "Kab\\.?\\s*Kep\\.?\\s*Seribu", "Kepulauan Seribu")
x <- stringr::str_replace_all(x, "Kabupaten\\s*Kepulauan\\s*Seribu", "Kepulauan Seribu")
x <- stringr::str_replace_all(x, "Kep\\.?\\s*Seribu", "Kepulauan Seribu")
x
}
get_years <- function(x) {
x <- as.character(x)
y <- stringr::str_extract(x, "\\b(19\\d{2}|20\\d{2})\\b")
as.integer(y)
}
rmse <- function(actual, pred) sqrt(mean((actual - pred)^2, na.rm = TRUE))
mae <- function(actual, pred) mean(abs(actual - pred), na.rm = TRUE)
mape_safe <- function(actual, pred) {
actual <- as.numeric(actual); pred <- as.numeric(pred)
idx <- which(actual != 0 & !is.na(actual) & !is.na(pred))
if (length(idx) == 0) return(NA_real_)
mean(abs((actual[idx] - pred[idx]) / actual[idx])) * 100
}
is_jateng_row <- function(wilayah) {
stringr::str_detect(stringr::str_to_upper(wilayah), "JAWA\\s*TENGAH")
}
is_provinsi_row <- function(wilayah) {
stringr::str_detect(stringr::str_to_upper(wilayah), "PROVINSI")
}
guess_name_col <- function(sfobj) {
cand <- c("nama","NAMOBJ","WADMKK","NAME_2","NAME","KABKOT","KAB_KOTA","KABKOTA","Wilayah")
hit <- cand[cand %in% names(sfobj)]
if (length(hit) > 0) return(hit[1])
chr_cols <- names(sfobj)[sapply(sfobj, is.character)]
if (length(chr_cols) == 0) stop("Tidak menemukan kolom nama wilayah (kolom karakter) di GeoJSON.")
chr_cols[1]
}
# 2) LOAD DATA FUNCTIONS
load_crosssection_csv <- function(path) {
raw <- suppressMessages(readr::read_csv(path, show_col_types = FALSE, col_names = TRUE))
header_row <- raw[1, ]
dat <- raw[-1, ] %>% as.data.frame()
idx_pop_marker <- which(names(raw) == "Data Jumlah Penduduk")
if (length(idx_pop_marker) == 0) stop("Kolom marker 'Data Jumlah Penduduk' tidak ditemukan pada crosssection CSV.")
case_cols <- 2:(idx_pop_marker - 1)
pop_cols <- (idx_pop_marker):(ncol(raw))
case_years <- get_years(unlist(header_row[case_cols]))
if (all(is.na(case_years))) case_years <- get_years(names(raw)[case_cols])
pop_years <- get_years(unlist(header_row[pop_cols]))
if (all(is.na(pop_years))) pop_years <- get_years(names(raw)[pop_cols])
if (all(is.na(case_years))) case_years <- 2017:2025
if (all(is.na(pop_years))) pop_years <- 2017:2025
case_years <- case_years[!is.na(case_years)]
pop_years <- pop_years[!is.na(pop_years)]
cases_wide <- dat %>%
dplyr::select(Wilayah, dplyr::all_of(names(raw)[case_cols])) %>%
dplyr::mutate(Wilayah = std_wilayah(Wilayah)) %>%
dplyr::rename_with(~ paste0("Case_", case_years[seq_along(.x)]), .cols = -Wilayah) %>%
dplyr::mutate(dplyr::across(dplyr::starts_with("Case_"), ~ num_clean(.x)))
pop_wide <- dat %>%
dplyr::select(Wilayah, dplyr::all_of(names(raw)[pop_cols])) %>%
dplyr::mutate(Wilayah = std_wilayah(Wilayah))
pop_names <- paste0("Pop_", pop_years[seq_len(ncol(pop_wide) - 1)])
names(pop_wide) <- c("Wilayah", pop_names)
pop_wide <- pop_wide %>% dplyr::mutate(dplyr::across(dplyr::starts_with("Pop_"), ~ num_clean(.x)))
cs <- dplyr::left_join(cases_wide, pop_wide, by = "Wilayah") %>%
tidyr::pivot_longer(cols = -Wilayah, names_to = "key", values_to = "value") %>%
tidyr::separate(key, into = c("type","year"), sep = "_", convert = TRUE) %>%
tidyr::pivot_wider(names_from = type, values_from = value) %>%
dplyr::rename(Cases = Case, Population = Pop) %>%
dplyr::mutate(
year = as.integer(year),
Wilayah = std_wilayah(Wilayah),
IR_100k = dplyr::if_else(Population > 0, (Cases/Population) * 100000, NA_real_)
)
cs
}
load_timeseries_csv <- function(path) {
raw <- suppressMessages(readr::read_csv(path, show_col_types = FALSE))
# posisi kolom Total
total_idx <- which(toupper(names(raw)) == "TOTAL")
if (length(total_idx) == 0) total_idx <- NA_integer_
new_names <- names(raw)
# Rename kolom wilayah berdasarkan baris pertama (header wilayah)
# - kolom 2..(total_idx-1) saja yang di-rename
# - rename hanya jika value baris pertama tidak NA/kosong
start_j <- 2
end_j <- if (!is.na(total_idx)) (total_idx - 1) else ncol(raw)
for (j in start_j:end_j) {
cand <- raw[[j]][1]
cand <- as.character(cand)
cand <- stringr::str_squish(cand)
if (!is.na(cand) && nzchar(cand)) {
new_names[j] <- cand
}
}
# Pastikan tidak ada nama kolom NA / kosong
bad <- is.na(new_names) | new_names == ""
if (any(bad)) {
new_names[bad] <- paste0("V", which(bad))
}
# Pastikan unik (hindari duplikat)
new_names <- make.unique(new_names)
names(raw) <- new_names
# Buang baris pertama (baris header wilayah)
ts <- raw[-1, ]
# Parse tanggal aman
ts <- ts %>%
dplyr::mutate(
Tanggal = as.character(Tanggal),
Tanggal = suppressWarnings(lubridate::dmy(Tanggal)),
Tanggal = dplyr::if_else(is.na(Tanggal), suppressWarnings(lubridate::ymd(as.character(raw[-1, "Tanggal"]))), Tanggal),
Tanggal = dplyr::if_else(is.na(Tanggal), suppressWarnings(lubridate::mdy(as.character(raw[-1, "Tanggal"]))), Tanggal)
)
# Konversi numerik untuk semua kolom selain Tanggal
ts <- ts %>%
dplyr::mutate(dplyr::across(-Tanggal, ~ num_clean(.x)))
# Kalau Total belum ada, buat Total dari penjumlahan semua wilayah
if (!("Total" %in% names(ts))) {
ts <- ts %>% dplyr::mutate(Total = rowSums(dplyr::across(-Tanggal), na.rm = TRUE))
}
# Bentuk long untuk analisis kab/kota
ts_long <- ts %>%
tidyr::pivot_longer(cols = -c(Tanggal, Total), names_to = "Wilayah", values_to = "Cases") %>%
dplyr::mutate(
Wilayah = std_wilayah(Wilayah),
year = lubridate::year(Tanggal)
)
list(ts_wide = ts, ts_long = ts_long)
}
# 3) PATH FILE
BASE_PATH <- "C:/Users/ajayg/Documents/data"
CS_PATH <- file.path(BASE_PATH, "DATA UAS EPIDEM - DATA Crossection.csv")
TS_PATH <- file.path(BASE_PATH, "DATA UAS EPIDEM - Data Time Series.csv")
GEO_PATH <- file.path(BASE_PATH, "id-jk.min.geojson")
CS_PATH <- normalizePath(CS_PATH, winslash = "/", mustWork = FALSE)
TS_PATH <- normalizePath(TS_PATH, winslash = "/", mustWork = FALSE)
GEO_PATH <- normalizePath(GEO_PATH, winslash = "/", mustWork = FALSE)
stopifnot(file.exists(CS_PATH))
stopifnot(file.exists(TS_PATH))
stopifnot(file.exists(GEO_PATH))
cs_raw <- load_crosssection_csv(CS_PATH)
ts_obj <- load_timeseries_csv(TS_PATH)
ts_wide <- ts_obj$ts_wide
ts_long <- ts_obj$ts_long
# 4) CLEAN khusus: buang baris "PROVINSI JAWA TENGAH"
cs_raw <- cs_raw %>% dplyr::filter(!is_jateng_row(Wilayah))
ts_long <- ts_long %>% dplyr::filter(!is_jateng_row(Wilayah))
# Split level
cs_prov <- cs_raw %>% dplyr::filter(is_provinsi_row(Wilayah))
cs_kab <- cs_raw %>% dplyr::filter(!is_provinsi_row(Wilayah))
# (1) DATA PENELITIAN: tampilkan 6 atas & 6 bawah
cat("\n==================== (1) DATA PENELITIAN (CROSS-SECTION) ====================\n")
cs_view <- cs_raw %>%
dplyr::arrange(year, Wilayah) %>%
dplyr::select(Wilayah, year, Cases, Population, IR_100k)
cat("\n--- 6 Baris Teratas (head) ---\n")
print(utils::head(cs_view, 6))
cat("\n--- 6 Baris Terbawah (tail) ---\n")
print(utils::tail(cs_view, 6))
cat("\n==================== DATA TIME SERIES (BULANAN) ====================\n")
ts_view <- ts_long %>%
dplyr::arrange(Tanggal, Wilayah) %>%
dplyr::select(Tanggal, Wilayah, Cases)
cat("\n--- 6 Baris Teratas (head) ---\n")
print(utils::head(ts_view, 6))
cat("\n--- 6 Baris Terbawah (tail) ---\n")
print(utils::tail(ts_view, 6))
# (2) STATISTIK DESKRIPTIF: max, min, mean, median, range, dan IR
cat("\n==================== (2) STATISTIK DESKRIPTIF ====================\n")
# Provinsi per tahun (agregasi dari kab/kota supaya konsisten)
prov_year <- cs_kab %>%
dplyr::group_by(year) %>%
dplyr::summarise(
Cases = sum(Cases, na.rm = TRUE),
Population = sum(Population, na.rm = TRUE),
IR_100k = dplyr::if_else(Population > 0, (Cases/Population) * 100000, NA_real_),
.groups = "drop"
)
desc_prov <- prov_year %>%
dplyr::summarise(
min_cases = min(Cases, na.rm = TRUE),
max_cases = max(Cases, na.rm = TRUE),
mean_cases = mean(Cases, na.rm = TRUE),
median_cases = median(Cases, na.rm = TRUE),
range_cases = max_cases - min_cases,
min_ir = min(IR_100k, na.rm = TRUE),
max_ir = max(IR_100k, na.rm = TRUE),
mean_ir = mean(IR_100k, na.rm = TRUE),
median_ir = median(IR_100k, na.rm = TRUE),
range_ir = max_ir - min_ir
)
cat("\n--- Ringkasan Provinsi (agregasi kab/kota) per tahun ---\n")
print(prov_year)
cat("\n--- Statistik deskriptif (Provinsi) ---\n")
print(desc_prov)
# Kab/kota akumulasi seluruh tahun
kab_all <- cs_kab %>%
dplyr::group_by(Wilayah) %>%
dplyr::summarise(
Cases = sum(Cases, na.rm = TRUE),
Population = sum(Population, na.rm = TRUE),
IR_100k = dplyr::if_else(Population > 0, (Cases/Population) * 100000, NA_real_),
.groups = "drop"
)
desc_kab <- kab_all %>%
dplyr::summarise(
min_cases = min(Cases, na.rm = TRUE),
max_cases = max(Cases, na.rm = TRUE),
mean_cases = mean(Cases, na.rm = TRUE),
median_cases = median(Cases, na.rm = TRUE),
range_cases = max_cases - min_cases,
min_ir = min(IR_100k, na.rm = TRUE),
max_ir = max(IR_100k, na.rm = TRUE),
mean_ir = mean(IR_100k, na.rm = TRUE),
median_ir = median(IR_100k, na.rm = TRUE),
range_ir = max_ir - min_ir
)
cat("\n--- Statistik deskriptif (Kab/Kota, akumulasi seluruh tahun) ---\n")
print(desc_kab)
# (3) ANALISIS SPASIAL
cat("\n==================== (3) ANALISIS SPASIAL (DKI GEOJSON) ====================\n")
g_dki <- sf::st_read(GEO_PATH, quiet = TRUE) %>% sf::st_make_valid()
name_col <- guess_name_col(g_dki)
g_dki <- g_dki %>%
dplyr::mutate(Wilayah = std_wilayah(.data[[name_col]]))
year_latest <- max(cs_kab$year, na.rm = TRUE)
map_df <- cs_kab %>%
dplyr::filter(year == year_latest) %>%
dplyr::select(Wilayah, year, Cases, Population, IR_100k)
g_map <- g_dki %>%
dplyr::left_join(map_df, by = "Wilayah")
cat(paste0("\nPeta dibuat untuk tahun: ", year_latest, "\n"))
cat("Cek wilayah yang tidak match (jika ada):\n")
print(
g_map %>%
dplyr::filter(is.na(Cases)) %>%
sf::st_drop_geometry() %>%
dplyr::select(Wilayah)
)
p_map <- ggplot(g_map) +
geom_sf(aes(fill = IR_100k), linewidth = 0.2) +
scale_fill_continuous(
labels = scales::number_format(accuracy = 0.01),
na.value = "grey90"
) +
labs(
title = paste0("Peta Incidence Rate (IR) Hepatitis A per 100.000 Penduduk - DKI Jakarta (", year_latest, ")"),
subtitle = "IR = kasus / penduduk × 100.000",
fill = "IR/100k"
)
print(p_map)
# (Opsional) Moran's I global untuk IR (tahun terbaru)
moran_data <- g_map %>%
dplyr::filter(!is.na(IR_100k)) %>%
sf::st_transform(4326)
if (nrow(moran_data) >= 3) {
nb <- spdep::poly2nb(moran_data, queen = TRUE)
lw <- spdep::nb2listw(nb, style = "W", zero.policy = TRUE)
moran_res <- spdep::moran.test(moran_data$IR_100k, lw, zero.policy = TRUE)
cat("\n--- Moran's I (Global) untuk IR_100k (tahun terbaru) ---\n")
print(moran_res)
} else {
cat("\n(Moran's I dilewati) Data IR valid terlalu sedikit untuk uji autokorelasi spasial.\n")
}
# ranking IR kab/kota tahun terbaru
rank_ir_latest <- cs_kab %>%
dplyr::filter(year == year_latest) %>%
dplyr::arrange(dplyr::desc(IR_100k)) %>%
dplyr::mutate(rank = dplyr::row_number()) %>%
dplyr::select(rank, Wilayah, year, Cases, Population, IR_100k)
cat("\n--- Ranking IR Kab/Kota (tahun terbaru) ---\n")
print(rank_ir_latest)
# (4) ANALISIS TEMPORAL + FORECASTING
cat("\n==================== (4) ANALISIS TEMPORAL + FORECASTING ====================\n")
# Total provinsi bulanan = sum kab/kota per tanggal
ts_prov <- ts_long %>%
dplyr::group_by(Tanggal) %>%
dplyr::summarise(Cases = sum(Cases, na.rm = TRUE), .groups = "drop") %>%
dplyr::arrange(Tanggal)
start_y <- lubridate::year(min(ts_prov$Tanggal))
start_m <- lubridate::month(min(ts_prov$Tanggal))
y_ts <- ts(ts_prov$Cases, start = c(start_y, start_m), frequency = 12)
cat("\n--- Ringkasan deret waktu (Total Provinsi) ---\n")
print(summary(y_ts))
plot(y_ts, main = "Tren Bulanan Kasus Hepatitis A (Total Provinsi DKI Jakarta)",
ylab = "Kasus", xlab = "Tahun")
# Uji stasioneritas (opsional, berguna untuk narasi ARIMA)
cat("\n--- Uji Stasioneritas (opsional) ---\n")
cat("ADF Test (tseries::adf.test):\n")
print(tseries::adf.test(y_ts))
cat("\nKPSS Test (tseries::kpss.test):\n")
print(tseries::kpss.test(y_ts))
H_TEST <- 12
H_FUTURE <- 12
n <- length(y_ts)
if (n <= H_TEST) stop("Data time series lebih pendek dari 12 bulan, tidak cukup untuk test horizon.")
train_ts <- window(y_ts, end = time(y_ts)[n - H_TEST])
test_ts <- window(y_ts, start = time(y_ts)[n - H_TEST + 1])
# Holt
fit_holt <- forecast::holt(train_ts, h = H_TEST)
# ETS
fit_ets <- forecast::ets(train_ts)
fc_ets <- forecast::forecast(fit_ets, h = H_TEST)
# ARIMA
fit_arima <- forecast::auto.arima(train_ts, seasonal = TRUE, stepwise = TRUE, approximation = FALSE)
fc_arima <- forecast::forecast(fit_arima, h = H_TEST)
# Evaluasi TEST
actual <- as.numeric(test_ts)
pred_holt <- as.numeric(fit_holt$mean)
pred_ets <- as.numeric(fc_ets$mean)
pred_arima <- as.numeric(fc_arima$mean)
metrics <- data.frame(
Metode = c("Holt","ETS","ARIMA"),
RMSE = c(rmse(actual, pred_holt), rmse(actual, pred_ets), rmse(actual, pred_arima)),
MAE = c(mae(actual, pred_holt), mae(actual, pred_ets), mae(actual, pred_arima)),
MAPE = c(mape_safe(actual, pred_holt), mape_safe(actual, pred_ets), mape_safe(actual, pred_arima))
) %>% dplyr::arrange(RMSE)
cat("\n--- Akurasi TEST (12 bulan terakhir) ---\n")
print(metrics)
# Plot perbandingan forecast TEST
plot(fc_ets, main = "Perbandingan Forecast TEST 12 bulan: Holt vs ETS vs ARIMA",
ylab = "Kasus", xlab = "Tahun")
lines(fit_holt$mean, lty = 2)
lines(fc_arima$mean, lty = 3)
legend("topright", legend = c("ETS","Holt","ARIMA"), lty = c(1,2,3), bty = "n")
# Forecast 12 bulan ke depan pakai seluruh data
final_holt <- forecast::holt(y_ts, h = H_FUTURE)
final_ets <- forecast::forecast(forecast::ets(y_ts), h = H_FUTURE)
final_arima <- forecast::forecast(forecast::auto.arima(y_ts, seasonal = TRUE), h = H_FUTURE)
plot(final_ets, main = "Forecast 12 Bulan ke Depan (Seluruh Data): ETS + Holt + ARIMA",
ylab = "Kasus", xlab = "Tahun")
lines(final_holt$mean, lty = 2)
lines(final_arima$mean, lty = 3)
legend("topright",
legend = c("ETS (base + interval)", "Holt (mean)", "ARIMA (mean)"),
lty = c(1,2,3), bty = "n")
# Tabel forecast 12 bulan ke depan
last_date <- max(ts_prov$Tanggal, na.rm = TRUE)
future_dates <- seq(last_date %m+% months(1), by = "1 month", length.out = H_FUTURE)
pack_fc <- function(fc, metode) {
data.frame(
Tanggal = future_dates,
Metode = metode,
Forecast = as.numeric(fc$mean),
Lo80 = if (!is.null(fc$lower)) as.numeric(fc$lower[,1]) else NA_real_,
Hi80 = if (!is.null(fc$upper)) as.numeric(fc$upper[,1]) else NA_real_,
Lo95 = if (!is.null(fc$lower)) as.numeric(fc$lower[,2]) else NA_real_,
Hi95 = if (!is.null(fc$upper)) as.numeric(fc$upper[,2]) else NA_real_
)
}
fc_future_tbl <- dplyr::bind_rows(
pack_fc(final_holt, "Holt"),
pack_fc(final_ets, "ETS"),
pack_fc(final_arima, "ARIMA")
) %>%
dplyr::mutate(dplyr::across(c(Forecast,Lo80,Hi80,Lo95,Hi95), ~ round(.x, 2)))
cat("\n--- Tabel Forecast 12 Bulan ke Depan (3 metode) ---\n")
print(fc_future_tbl)
cat("\n=== SELESAI. Output ada di Console + plot muncul di Plot panel. ===\n")
# ============================================================
# DASHBOARD EPIDEMIOLOGI HEPATITIS A DKI JAKARTA
# Author: Muhammad Al Fauzan
# ============================================================
# ===============================
# 1) LOAD LIBRARIES
# ===============================
library(shiny)
library(shinydashboard)
library(tidyverse)
library(leaflet)
library(plotly)
library(DT)
library(sf)
library(scales)
library(shinycssloaders)
library(shinyWidgets)
library(shinyjs)
library(lubridate)
# Forecast & tests
library(forecast)
library(tseries)
# Spatial
library(spdep)
# ===============================
# 2) PATH (DEPLOY-SAFE)
# ===============================
BASE_PATH <- "data"
CASE_FILE <- "DATA UAS EPIDEM - Data Kasus HAV.csv"
POP_FILE <- "DATA UAS EPIDEM - Data Jumlah Penduduk.csv"
TS_FILE <- "DATAA UAS EPIDEM - Data Deret Waktu.csv"
GEO_FILE <- "id-jk.min.geojson"
CASE_PATH <- normalizePath(file.path(BASE_PATH, CASE_FILE), winslash = "/", mustWork = FALSE)
POP_PATH <- normalizePath(file.path(BASE_PATH, POP_FILE), winslash = "/", mustWork = FALSE)
TS_PATH <- normalizePath(file.path(BASE_PATH, TS_FILE), winslash = "/", mustWork = FALSE)
GEO_PATH <- normalizePath(file.path(BASE_PATH, GEO_FILE), winslash = "/", mustWork = FALSE)
cat("getwd():", getwd(), "\n")
cat("Files in data/:\n"); print(if (dir.exists(BASE_PATH)) list.files(BASE_PATH) else "")
cat("CASE exists:", file.exists(CASE_PATH), "\n")
cat("POP exists:", file.exists(POP_PATH), "\n")
cat("TS exists:", file.exists(TS_PATH), "\n")
cat("GEO exists:", file.exists(GEO_PATH), "\n")
# ===============================
# 3) HELPERS
# ===============================
read_semicolon <- function(path) {
readr::read_delim(path, delim = ";", show_col_types = FALSE, trim_ws = TRUE)
}
std_wilayah <- function(x) {
x %>%
as.character() %>%
toupper() %>%
str_replace_all("\\s+", " ") %>%
str_trim() %>%
str_replace_all("^KOTA\\s+", "") %>%
str_replace_all("^KABUPATEN\\s+", "") %>%
str_replace_all("^KAB\\.?\\s*", "") %>%
str_replace_all("ADM\\.?\\s*", "") %>%
str_replace_all("JAKARTA\\s+KEPULAUAN", "KEPULAUAN") %>%
str_replace_all("KEP\\.\\s*SERIBU", "KEPULAUAN SERIBU") %>%
str_replace_all("^KOTA\\s+", "") %>%
str_replace_all("^KAB\\.?\\s*", "")
}
# buang titik ribuan: 1.056.896 -> 1056896, dukung koma desimal
num_clean_id <- function(x) {
x <- as.character(x)
x <- str_replace_all(x, "\\s+", "")
x <- str_replace_all(x, "\\.", "")
x <- str_replace_all(x, ",", ".")
suppressWarnings(as.numeric(x))
}
rmse <- function(actual, pred) sqrt(mean((actual - pred)^2, na.rm = TRUE))
mae <- function(actual, pred) mean(abs(actual - pred), na.rm = TRUE)
mape_safe <- function(actual, pred) {
denom <- ifelse(actual == 0, NA_real_, actual)
mean(abs((actual - pred) / denom), na.rm = TRUE) * 100
}
guess_name_col <- function(g) {
cand <- c("name","NAME","Name","NAMOBJ","WADMKK","KAB_KOTA","KABKOT","KOTA",
"wilayah","WILAYAH","KABUPATEN","KABUPATEN_KOTA")
hit <- cand[cand %in% names(g)]
if (length(hit) > 0) return(hit[1])
chr_cols <- names(g)[purrr::map_lgl(g, ~ is.character(.x) || is.factor(.x))]
if (length(chr_cols) == 0) return(names(g)[1])
chr_cols[1]
}
# ===============================
# 4) LOAD WIDE (2017..2025) -> LONG (SAFE)
# ===============================
load_wide_years <- function(path, value_name) {
df <- read_semicolon(path)
# rapikan nama kolom + trim
names(df) <- names(df) %>%
stringr::str_replace_all("\ufeff", "") %>%
stringr::str_trim() %>%
as.character()
if (!("Wilayah" %in% names(df))) {
stop("File ", basename(path), " harus punya kolom 'Wilayah'. Kolom terbaca: ",
paste(names(df), collapse = ", "))
}
year_cols <- names(df)[stringr::str_detect(names(df), "^\\s*20[0-9]{2}\\s*$")]
if (length(year_cols) == 0) {
stop("Tidak menemukan kolom tahun (2017..2025) pada: ", basename(path),
" | Kolom terbaca: ", paste(names(df), collapse = ", "))
}
# PAKSA semua kolom tahun jadi character sebelum pivot (FIX error combine type)
df <- df %>% mutate(across(all_of(year_cols), ~ as.character(.x)))
df %>%
select(Wilayah, all_of(year_cols)) %>%
pivot_longer(cols = all_of(year_cols), names_to = "year", values_to = value_name) %>%
mutate(
year = as.integer(str_trim(year)),
Wilayah = as.character(Wilayah),
Wilayah_std = std_wilayah(Wilayah)
)
}
# ===============================
# 5) BUILD CROSS SECTION: Cases + Population + IR/100k
# ===============================
build_crosssection <- function(case_path, pop_path) {
kasus <- load_wide_years(case_path, "Cases") %>%
mutate(Cases = num_clean_id(Cases))
pop <- load_wide_years(pop_path, "Population") %>%
mutate(Population = num_clean_id(Population))
left_join(kasus, pop, by = c("Wilayah", "Wilayah_std", "year")) %>%
mutate(IR_100k = if_else(Population > 0, (Cases / Population) * 100000, NA_real_))
}
# ===============================
# 6) LOAD TIME SERIES (MONTHLY) FROM TS_FILE
# Format: Tanggal;Jakarta Pusat;...;Total
# ===============================
load_ts_monthly <- function(ts_path) {
df <- read_semicolon(ts_path)
# rapikan nama kolom (BOM/spasi sering bikin "Tanggal" jadi aneh)
names(df) <- names(df) %>%
stringr::str_replace_all("\ufeff", "") %>%
stringr::str_trim() %>%
as.character()
nm <- tolower(names(df))
col_tgl <- names(df)[which(nm %in% c("tanggal","date","waktu"))][1]
if (is.na(col_tgl)) col_tgl <- names(df)[which(stringr::str_detect(nm, "tanggal|date"))][1]
if (is.na(col_tgl)) {
stop("TS tidak punya kolom Tanggal/Date. Kolom terbaca: ", paste(names(df), collapse = ", "))
}
# simpan raw date ke kolom sementara (biar tidak kehapus saat col_tgl == 'Tanggal')
df <- df %>%
mutate(Date_raw = .data[[col_tgl]])
# hapus kolom tanggal lama hanya jika BUKAN Date_raw
df <- df %>% select(-all_of(col_tgl))
# parse tanggal (format kamu dd/mm/yyyy -> dmy)
df <- df %>%
mutate(
Tanggal = suppressWarnings(lubridate::dmy(Date_raw))
)
# fallback: coba ymd kalau dmy gagal
if (all(is.na(df$Tanggal))) {
df <- df %>%
mutate(Tanggal = suppressWarnings(lubridate::ymd(Date_raw)))
}
if (all(is.na(df$Tanggal))) {
stop("Kolom tanggal ada, tapi format tidak terbaca. Contoh valid: 01/01/2017 atau 2017-01-01")
}
# drop kolom raw
df <- df %>% select(-Date_raw)
# rapikan numeric semua kolom selain Tanggal
value_cols <- setdiff(names(df), "Tanggal")
df <- df %>% mutate(across(all_of(value_cols), num_clean_id))
# jadikan long untuk database (wilayah = semua kolom kecuali Tanggal & Total)
value_cols <- setdiff(names(df), "Tanggal")
wilayah_cols <- setdiff(value_cols, "Total")
ts_long <- df %>%
pivot_longer(cols = all_of(wilayah_cols),
names_to = "Wilayah", values_to = "Cases") %>%
mutate(Wilayah_std = std_wilayah(Wilayah)) %>%
arrange(Tanggal, Wilayah)
# total provinsi: pakai Total kalau ada, else sum
if ("Total" %in% value_cols) {
ts_total <- df %>%
transmute(Tanggal, Cases = .data[["Total"]]) %>%
arrange(Tanggal)
} else {
ts_total <- ts_long %>%
group_by(Tanggal) %>%
summarise(Cases = sum(Cases, na.rm = TRUE), .groups = "drop") %>%
arrange(Tanggal)
}
list(ts_long = ts_long, ts_total = ts_total, ts_wide = df)
}
# ===============================
# 7) FORECAST OBJECTS (Holt/ETS/ARIMA)
# ===============================
compute_forecast_objects <- function(ts_total_df) {
ts_total_df <- ts_total_df %>% arrange(Tanggal) %>% filter(!is.na(Tanggal))
# cek bulanan: minimal 24 bulan
validate(need(nrow(ts_total_df) >= 24, "Data TS bulanan terlalu pendek (minimal 24 bulan disarankan)."))
start_y <- year(min(ts_total_df$Tanggal))
start_m <- month(min(ts_total_df$Tanggal))
y_ts <- ts(ts_total_df$Cases, start = c(start_y, start_m), frequency = 12)
adf <- tryCatch(tseries::adf.test(y_ts), error = function(e) NULL)
kpss <- tryCatch(tseries::kpss.test(y_ts), error = function(e) NULL)
H_TEST <- 12
n <- length(y_ts)
validate(need(n > H_TEST, "Deret waktu < 12 bulan, tidak cukup untuk evaluasi TEST."))
train_ts <- window(y_ts, end = time(y_ts)[n - H_TEST])
test_ts <- window(y_ts, start = time(y_ts)[n - H_TEST + 1])
fc_holt <- forecast::holt(train_ts, h = H_TEST)
fit_ets <- forecast::ets(train_ts)
fc_ets <- forecast::forecast(fit_ets, h = H_TEST)
fit_arima <- forecast::auto.arima(train_ts, seasonal = TRUE, stepwise = TRUE, approximation = FALSE)
fc_arima <- forecast::forecast(fit_arima, h = H_TEST)
actual <- as.numeric(test_ts)
metrics <- tibble(
Metode = c("Holt","ETS","ARIMA"),
RMSE = c(rmse(actual, as.numeric(fc_holt$mean)),
rmse(actual, as.numeric(fc_ets$mean)),
rmse(actual, as.numeric(fc_arima$mean))),
MAE = c(mae(actual, as.numeric(fc_holt$mean)),
mae(actual, as.numeric(fc_ets$mean)),
mae(actual, as.numeric(fc_arima$mean))),
MAPE = c(mape_safe(actual, as.numeric(fc_holt$mean)),
mape_safe(actual, as.numeric(fc_ets$mean)),
mape_safe(actual, as.numeric(fc_arima$mean)))
) %>% arrange(RMSE)
# forecast 12 bulan ke depan pakai seluruh data
H_FUTURE <- 12
final_holt <- forecast::holt(y_ts, h = H_FUTURE)
final_ets <- forecast::forecast(forecast::ets(y_ts), h = H_FUTURE)
final_arima <- forecast::forecast(forecast::auto.arima(y_ts, seasonal = TRUE), h = H_FUTURE)
last_date <- max(ts_total_df$Tanggal, na.rm = TRUE)
future_dates <- seq(last_date %m+% months(1), by = "1 month", length.out = H_FUTURE)
pack_fc <- function(fc, metode) {
tibble(
Tanggal = future_dates,
Metode = metode,
Forecast = as.numeric(fc$mean),
Lo80 = as.numeric(fc$lower[,1]),
Hi80 = as.numeric(fc$upper[,1]),
Lo95 = as.numeric(fc$lower[,2]),
Hi95 = as.numeric(fc$upper[,2])
)
}
fc_future_tbl <- bind_rows(
pack_fc(final_holt, "Holt"),
pack_fc(final_ets, "ETS"),
pack_fc(final_arima, "ARIMA")
) %>% mutate(across(c(Forecast, Lo80, Hi80, Lo95, Hi95), ~ round(.x, 2)))
# test dates (ambil 12 tanggal terakhir dari df)
test_dates <- tail(ts_total_df$Tanggal, H_TEST)
df_test <- tibble(
Tanggal = test_dates,
Actual = actual,
Holt = as.numeric(fc_holt$mean),
ETS = as.numeric(fc_ets$mean),
ARIMA = as.numeric(fc_arima$mean)
)
list(
ts_total = ts_total_df,
y_ts = y_ts,
adf = adf,
kpss = kpss,
metrics = metrics,
df_test = df_test,
fc_future_tbl = fc_future_tbl
)
}
# ===============================
# 8) LOAD ALL (ONLY 4 FILES)
# ===============================
cs_raw <- NULL
ts_pack <- NULL
g_dki <- NULL
if (file.exists(CASE_PATH) && file.exists(POP_PATH)) {
cs_raw <- tryCatch(build_crosssection(CASE_PATH, POP_PATH),
error = function(e) { message("ERROR build_crosssection(): ", e$message); NULL })
}
if (file.exists(TS_PATH)) {
ts_pack <- tryCatch(load_ts_monthly(TS_PATH),
error = function(e) { message("ERROR load TS: ", e$message); NULL })
}
if (file.exists(GEO_PATH)) {
g_dki <- tryCatch(sf::st_read(GEO_PATH, quiet = TRUE),
error = function(e) { message("ERROR read GEO: ", e$message); NULL })
if (!is.null(g_dki)) {
name_col <- guess_name_col(g_dki)
g_dki <- g_dki %>% mutate(Wilayah_std = std_wilayah(as.character(.data[[name_col]])))
}
}
# ===============================
# 9) UI
# ===============================
ui <- dashboardPage(
title = "Hepatitis A DKI Jakarta",
skin = "black",
dashboardHeader(
title = span("HEPATITIS A DKI JAKARTA", style="font-weight:900; font-family:'Poppins',sans-serif; font-size:14px;"),
titleWidth = 280
),
dashboardSidebar(
width = 280,
sidebarMenu(
id = "tabs",
menuItem("Dashboard", tabName = "dashboard", icon = icon("chart-pie")),
menuItem("Statistik Deskriptif", tabName = "stats", icon = icon("table")),
menuItem("Peta IR/100k", tabName = "map", icon = icon("map")),
menuItem("Temporal & Forecast", tabName = "forecast", icon = icon("chart-line")),
menuItem("Database", tabName = "data", icon = icon("database")),
hr(),
div(style="padding: 0 15px 10px 15px;",
h5("FILTER", style="color:#bfc7d5; font-weight:900; font-size:11px; letter-spacing:.7px; margin-bottom:10px;"),
uiOutput("ui_filter_wilayah"),
br(),
uiOutput("ui_year_range"),
br(),
uiOutput("ui_map_year")
)
)
),
dashboardBody(
useShinyjs(),
tags$head(
tags$link(rel="stylesheet", href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700;800;900&display=swap"),
tags$style(HTML("
body, h1, h2, h3, h4, h5, h6, .main-header .logo { font-family:'Poppins',sans-serif !important; }
.content-wrapper { background:#f4f6f9; }
.skin-black .main-header .navbar { background: linear-gradient(90deg, #1f3a5f 0%, #2e86c1 100%); }
.skin-black .main-header .logo { background:#1f3a5f; color:#fff; font-weight:900; }
.skin-black .main-sidebar { background:#1d2b3a; }
.box { border-radius:16px !important; border:none !important; box-shadow: 0 10px 26px rgba(16,24,40,.08) !important; overflow:hidden; }
.box-title { font-weight:900 !important; }
.small-box { border-radius:16px !important; box-shadow: 0 10px 26px rgba(16,24,40,.10) !important; }
.leaflet-container { border-radius:14px; }
"))
),
tabItems(
# ---------------- DASHBOARD
tabItem(tabName = "dashboard",
fluidRow(
column(12, h3("Monitoring Epidemiologi Hepatitis A – DKI Jakarta",
style="font-weight:900; margin-top:0; color:#1f3a5f;"))
),
fluidRow(
valueBoxOutput("vbox_kasus", width = 3),
valueBoxOutput("vbox_ir", width = 3),
valueBoxOutput("vbox_toprisk", width = 3),
valueBoxOutput("vbox_moran", width = 3)
),
fluidRow(
box(width = 8, title = "Tren IR/100k (Kab/Kota) – Cross Section", status = "primary", solidHeader = TRUE,
withSpinner(plotlyOutput("plot_trend_ir", height = "380px"), type = 4)
),
box(width = 4, title = "Top 5 Risiko (IR) – Tahun Peta", status = "warning", solidHeader = TRUE,
tableOutput("top_rank_table")
)
)
),
# ---------------- STATISTIK
tabItem(tabName = "stats",
fluidRow(
column(12, h3("Statistik Deskriptif (Provinsi & Kab/Kota)",
style="font-weight:900; margin-top:0; color:#1f3a5f;"))
),
fluidRow(
box(width = 6, title = "Ringkasan Tahunan Provinsi (Agregasi)", status = "primary", solidHeader = TRUE,
dataTableOutput("tbl_prov_year")
),
box(width = 6, title = "Deskriptif Provinsi (Min/Max/Mean/Median/Range)", status = "warning", solidHeader = TRUE,
tableOutput("tbl_desc_prov")
)
),
fluidRow(
box(width = 12, title = "Deskriptif Kab/Kota (IR/100k) – Min/Max/Mean/Median/Range", status = "info", solidHeader = TRUE,
dataTableOutput("tbl_desc_kabkota")
)
),
fluidRow(
box(width = 12, title = "Ranking IR Kab/Kota (per Tahun)", status = "danger", solidHeader = TRUE,
dataTableOutput("tbl_ranking")
)
)
),
# ---------------- MAP
tabItem(tabName = "map",
fluidRow(
column(12, h3("Peta Incidence Rate (IR/100k) – Tahun Terpilih",
style="font-weight:900; margin-top:0; color:#1f3a5f;"))
),
fluidRow(
box(width = 12, title = "Peta IR/100k", status = "danger", solidHeader = TRUE,
withSpinner(leafletOutput("epid_map", height = "520px"), type = 6)
)
)
),
# ---------------- FORECAST
tabItem(tabName = "forecast",
fluidRow(
column(12, h3("Analisis Temporal & Forecast 12 Bulan (Holt vs ETS vs ARIMA)",
style="font-weight:900; margin-top:0; color:#1f3a5f;"))
),
fluidRow(
box(width = 6, title = "Tren Bulanan Kasus (Total Provinsi) – TS File", status = "primary", solidHeader = TRUE,
withSpinner(plotlyOutput("plot_ts_total", height = "320px"), type = 4),
uiOutput("stationarity_box")
),
box(width = 6, title = "Akurasi TEST (12 bulan terakhir)", status = "danger", solidHeader = TRUE,
dataTableOutput("tbl_metrics"),
p("Catatan: MAPE bisa besar saat Actual kecil. RMSE/MAE lebih stabil.",
style="font-size:12px; color:#667085; margin-top:10px;")
)
),
fluidRow(
box(width = 12, title = "Prediksi TEST 12 bulan: Actual vs Holt/ETS/ARIMA", status = "info", solidHeader = TRUE,
withSpinner(plotlyOutput("plot_fc_test", height = "360px"), type = 6)
)
),
fluidRow(
box(width = 12, title = "Forecast 12 Bulan ke Depan (Mean + Interval)", status = "success", solidHeader = TRUE,
withSpinner(plotlyOutput("plot_fc_future", height = "360px"), type = 6),
br(),
dataTableOutput("tbl_fc_future")
)
)
),
# ---------------- DATABASE
tabItem(tabName = "data",
fluidRow(
box(width = 12, title = "Database Cross-Section (Kasus, Penduduk, IR/100k)", status = "primary", solidHeader = TRUE,
dataTableOutput("raw_table_cs")
)
),
fluidRow(
box(width = 12, title = "Database Time Series (Long) – dari TS File", status = "info", solidHeader = TRUE,
dataTableOutput("raw_table_ts_long")
)
),
fluidRow(
box(width = 12, title = "Database Time Series (Total Provinsi)", status = "warning", solidHeader = TRUE,
dataTableOutput("raw_table_ts_total")
)
)
)
)
)
)
# ===============================
# 10) SERVER
# ===============================
server <- function(input, output, session) {
observe({
if (is.null(cs_raw)) showNotification("ERROR: Cross-section gagal dibaca. Cek delimiter ';' dan nama kolom.", type="error", duration=12)
if (is.null(ts_pack)) showNotification("ERROR: Time-series gagal dibaca. Pastikan TS punya kolom Tanggal + Total.", type="error", duration=12)
if (is.null(g_dki)) showNotification("WARNING: GeoJSON gagal dibaca. Peta tidak tampil.", type="warning", duration=10)
})
cs_data <- reactive({
validate(need(!is.null(cs_raw), "Cross-section belum terbaca."))
cs_raw
})
ts_long <- reactive({
validate(need(!is.null(ts_pack), "Time-series belum terbaca."))
ts_pack$ts_long
})
ts_total <- reactive({
validate(need(!is.null(ts_pack), "Time-series belum terbaca."))
ts_pack$ts_total
})
# ---------- UI FILTERS
output$ui_filter_wilayah <- renderUI({
req(cs_data())
wilayah_choices <- cs_data()$Wilayah %>%
unique() %>%
as.character() %>%
sort()
wilayah_choices <- wilayah_choices[!stringr::str_detect(toupper(wilayah_choices), "PROVINSI")]
pickerInput("filter_wilayah", "Wilayah:",
choices = c("SEMUA WILAYAH", wilayah_choices),
selected = "SEMUA WILAYAH",
options = list(`style`="btn-default", `live-search`=TRUE)
)
})
output$ui_year_range <- renderUI({
req(cs_data())
yrs <- sort(unique(cs_data()$year))
sliderTextInput("year_range", "Rentang Tahun:",
choices = yrs, selected = c(min(yrs), max(yrs)), grid = TRUE
)
})
output$ui_map_year <- renderUI({
req(cs_data())
yrs <- sort(unique(cs_data()$year))
sliderTextInput("map_year", "Tahun Peta:",
choices = yrs, selected = max(yrs), grid = TRUE
)
})
filtered_cs <- reactive({
req(cs_data(), input$year_range)
df <- cs_data() %>% filter(year >= input$year_range[1], year <= input$year_range[2])
if (!is.null(input$filter_wilayah) && input$filter_wilayah != "SEMUA WILAYAH") {
df <- df %>% filter(Wilayah == input$filter_wilayah)
}
df
})
# ---------- PROVINSI (agregasi semua kab/kota) per tahun
prov_year <- reactive({
req(cs_data(), input$year_range)
cs_data() %>%
filter(year >= input$year_range[1], year <= input$year_range[2]) %>%
group_by(year) %>%
summarise(
Cases = sum(Cases, na.rm = TRUE),
Population = sum(Population, na.rm = TRUE),
IR_100k = if_else(Population > 0, (Cases/Population)*100000, NA_real_),
.groups = "drop"
) %>%
arrange(year) %>%
mutate(IR_100k = round(IR_100k, 3))
})
# =========================
# VALUE BOXES
# =========================
output$vbox_kasus <- renderValueBox({
req(filtered_cs())
valueBox(comma(sum(filtered_cs()$Cases, na.rm=TRUE)), "Total Kasus (filter)", icon=icon("hospital-user"), color="aqua")
})
output$vbox_ir <- renderValueBox({
req(filtered_cs())
valueBox(sprintf("%.2f", mean(filtered_cs()$IR_100k, na.rm=TRUE)), "Rata-rata IR/100k", icon=icon("chart-line"), color="blue")
})
output$vbox_toprisk <- renderValueBox({
req(cs_data(), input$map_year)
top1 <- cs_data() %>%
filter(year == input$map_year) %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
arrange(desc(IR_100k)) %>%
slice(1)
label <- if (nrow(top1)==1) paste0(top1$Wilayah, " (", sprintf("%.2f", top1$IR_100k), ")") else "-"
valueBox(label, paste0("Top IR (", input$map_year, ")"), icon=icon("triangle-exclamation"), color="yellow")
})
output$vbox_moran <- renderValueBox({
req(cs_data(), input$map_year)
validate(need(!is.null(g_dki), "GeoJSON belum tersedia."))
map_df <- cs_data() %>%
filter(year == input$map_year) %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
transmute(Wilayah_std, IR_100k)
g_map <- g_dki %>% left_join(map_df, by="Wilayah_std")
moran_data <- g_map %>% filter(!is.na(IR_100k))
if (nrow(moran_data) < 3) {
valueBox("N/A", "Moran’s I", icon=icon("globe"), color="red")
} else {
nb <- spdep::poly2nb(moran_data, queen = TRUE)
lw <- spdep::nb2listw(nb, style="W", zero.policy = TRUE)
mor <- spdep::moran.test(moran_data$IR_100k, lw, zero.policy = TRUE)
label <- paste0(sprintf("%.3f", mor$estimate[["Moran I statistic"]]), " | p=", sprintf("%.3f", mor$p.value))
valueBox(label, paste0("Moran’s I (", input$map_year, ")"), icon=icon("globe"), color="red")
}
})
# =========================
# DASHBOARD: TREND IR
# =========================
output$plot_trend_ir <- renderPlotly({
req(cs_data(), input$year_range)
df <- cs_data() %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
filter(year >= input$year_range[1], year <= input$year_range[2])
if (!is.null(input$filter_wilayah) && input$filter_wilayah != "SEMUA WILAYAH") {
df <- df %>% filter(Wilayah == input$filter_wilayah)
}
p <- ggplot(df, aes(x=year, y=IR_100k, color=Wilayah, group=Wilayah)) +
geom_line(linewidth=1) + geom_point(size=2) +
theme_minimal() +
labs(x="Tahun", y="IR per 100.000", color="Wilayah") +
scale_x_continuous(breaks=seq(min(df$year), max(df$year), 1))
ggplotly(p) %>% config(displayModeBar = FALSE)
})
output$top_rank_table <- renderTable({
req(cs_data(), input$map_year)
cs_data() %>%
filter(year == input$map_year) %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
arrange(desc(IR_100k)) %>%
head(5) %>%
transmute(Wilayah, Kasus=Cases, `IR/100k`=round(IR_100k, 2))
}, striped=TRUE)
# =========================
# STATISTIK
# =========================
output$tbl_prov_year <- renderDataTable({
req(prov_year())
datatable(prov_year(), rownames=FALSE, options=list(pageLength=10, scrollX=TRUE))
})
output$tbl_desc_prov <- renderTable({
req(prov_year())
py <- prov_year()
tibble(
Indikator = c("Minimum","Maksimum","Mean","Median","Range"),
Kasus = c(min(py$Cases, na.rm=TRUE),
max(py$Cases, na.rm=TRUE),
mean(py$Cases, na.rm=TRUE),
median(py$Cases, na.rm=TRUE),
max(py$Cases, na.rm=TRUE) - min(py$Cases, na.rm=TRUE)),
IR_100k = round(c(min(py$IR_100k, na.rm=TRUE),
max(py$IR_100k, na.rm=TRUE),
mean(py$IR_100k, na.rm=TRUE),
median(py$IR_100k, na.rm=TRUE),
max(py$IR_100k, na.rm=TRUE) - min(py$IR_100k, na.rm=TRUE)), 3)
)
}, striped=TRUE, width="100%")
output$tbl_desc_kabkota <- renderDataTable({
req(cs_data())
kk <- cs_data() %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
group_by(Wilayah) %>%
summarise(
IR_min = min(IR_100k, na.rm=TRUE),
IR_max = max(IR_100k, na.rm=TRUE),
IR_mean = mean(IR_100k, na.rm=TRUE),
IR_median = median(IR_100k, na.rm=TRUE),
IR_range = IR_max - IR_min,
.groups="drop"
) %>%
mutate(across(starts_with("IR_"), ~ round(.x, 3))) %>%
arrange(desc(IR_mean))
datatable(kk, rownames=FALSE, options=list(pageLength=10, scrollX=TRUE))
})
output$tbl_ranking <- renderDataTable({
req(cs_data(), input$year_range)
df <- cs_data() %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
filter(year >= input$year_range[1], year <= input$year_range[2]) %>%
group_by(year) %>%
arrange(desc(IR_100k), .by_group = TRUE) %>%
mutate(Rank = row_number()) %>%
ungroup() %>%
transmute(year, Rank, Wilayah, Kasus=Cases, Penduduk=Population, IR_100k=round(IR_100k, 3)) %>%
arrange(year, Rank)
datatable(df, rownames=FALSE, options=list(pageLength=12, scrollX=TRUE))
})
# =========================
# MAP
# =========================
output$epid_map <- renderLeaflet({
req(cs_data(), input$map_year)
validate(need(!is.null(g_dki), "GeoJSON belum terbaca. Pastikan id-jk.min.geojson ada di data/."))
data_year <- cs_data() %>%
filter(year == input$map_year) %>%
filter(!str_detect(toupper(Wilayah), "PROVINSI")) %>%
transmute(Wilayah_std, Cases, Population, IR_100k)
shp <- g_dki %>% left_join(data_year, by="Wilayah_std")
pal <- colorNumeric(palette="Reds", domain=cs_data()$IR_100k, na.color="#f2f4f7")
leaflet(shp) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(
fillColor = ~pal(IR_100k),
weight=2, opacity=1, color="white", fillOpacity=0.88,
highlight = highlightOptions(weight=4, color="#e74c3c", bringToFront=TRUE),
# TOOLTIP saat hover
label = ~lapply(
paste0(
"",
"", Wilayah_std, "
",
"Tahun: ", input$map_year, "
",
"Kasus: ", scales::comma(Cases), "
",
"Penduduk: ", scales::comma(Population), "
",
"IR/100k: ", sprintf('%.2f', IR_100k),
""
),
htmltools::HTML
),
labelOptions = labelOptions(
direction = "auto",
sticky = TRUE,
opacity = 0.95,
textsize = "12px",
style = list(
"background-color" = "white",
"border" = "1px solid rgba(0,0,0,0.15)",
"border-radius" = "10px",
"padding" = "8px 10px",
"box-shadow" = "0 8px 18px rgba(0,0,0,0.12)"
)
),
# popup (klik) boleh tetap ada, biar lengkap
popup = ~paste0(
"", Wilayah_std, "
",
"Tahun: ", input$map_year, "
",
"Kasus: ", scales::comma(Cases), "
",
"Penduduk: ", scales::comma(Population), "
",
"IR/100k: ", sprintf('%.2f', IR_100k)
)
)})
# =========================
# FORECAST
# =========================
fc_obj <- reactive({
validate(need(!is.null(ts_pack), "Time-series belum terbaca."))
compute_forecast_objects(ts_total())
})
output$plot_ts_total <- renderPlotly({
obj <- fc_obj()
df <- obj$ts_total
p <- ggplot(df, aes(x=Tanggal, y=Cases)) +
geom_line(linewidth=1) +
theme_minimal() +
labs(x="Tanggal", y="Kasus (Total)", title="Tren Bulanan Kasus Hepatitis A (Total Provinsi)")
ggplotly(p) %>% config(displayModeBar=FALSE)
})
output$stationarity_box <- renderUI({
obj <- fc_obj()
adf_p <- if (!is.null(obj$adf)) obj$adf$p.value else NA_real_
kpss_p <- if (!is.null(obj$kpss)) obj$kpss$p.value else NA_real_
div(style="margin-top:10px; font-size:12px; color:#475467;",
tags$div(strong("Uji stasioneritas (ringkas):")),
tags$div(paste0("ADF p-value: ", ifelse(is.na(adf_p), "NA", sprintf("%.4f", adf_p)),
" | KPSS p-value: ", ifelse(is.na(kpss_p), "NA", sprintf("%.4f", kpss_p)))),
tags$div("Interpretasi cepat: ADF besar & KPSS kecil → cenderung tidak stasioner.")
)
})
output$tbl_metrics <- renderDataTable({
obj <- fc_obj()
datatable(obj$metrics %>% mutate(across(c(RMSE, MAE, MAPE), ~ round(.x, 3))),
rownames=FALSE, options=list(dom="t", pageLength=5))
})
output$plot_fc_test <- renderPlotly({
obj <- fc_obj()
df_line <- obj$df_test %>%
pivot_longer(cols=c("Holt","ETS","ARIMA"), names_to="Metode", values_to="Pred")
p <- ggplot() +
geom_line(data=obj$df_test, aes(x=Tanggal, y=Actual), linewidth=1.2) +
geom_line(data=df_line, aes(x=Tanggal, y=Pred, color=Metode), linewidth=1) +
theme_minimal() +
labs(title="Forecast TEST 12 Bulan: Actual vs Holt/ETS/ARIMA", x="Tanggal", y="Kasus")
ggplotly(p) %>% config(displayModeBar=FALSE)
})
output$plot_fc_future <- renderPlotly({
obj <- fc_obj()
fc <- obj$fc_future_tbl
p <- ggplot(fc, aes(x=Tanggal, y=Forecast, color=Metode)) +
geom_line(linewidth=1) +
theme_minimal() +
labs(title="Forecast 12 Bulan ke Depan (Mean)", x="Tanggal", y="Kasus")
ggplotly(p) %>% config(displayModeBar=FALSE)
})
output$tbl_fc_future <- renderDataTable({
obj <- fc_obj()
datatable(obj$fc_future_tbl, rownames=FALSE, extensions="Buttons",
options=list(dom="Bfrtip", buttons=c("copy","csv","excel","pdf","print"),
pageLength=12, scrollX=TRUE))
})
# =========================
# DATABASE
# =========================
output$raw_table_cs <- renderDataTable({
req(cs_data())
df <- cs_data() %>%
select(Wilayah, year, Cases, Population, IR_100k) %>%
mutate(IR_100k = round(IR_100k, 3))
datatable(df, filter="top", extensions="Buttons",
options=list(dom="Bfrtip", buttons=c("copy","csv","excel","pdf","print"),
pageLength=12, scrollX=TRUE),
rownames=FALSE)
})
output$raw_table_ts_long <- renderDataTable({
req(ts_long())
df <- ts_long() %>% select(Tanggal, Wilayah, Cases) %>% arrange(Tanggal, Wilayah)
datatable(df, filter="top", options=list(pageLength=12, scrollX=TRUE), rownames=FALSE)
})
output$raw_table_ts_total <- renderDataTable({
req(ts_total())
datatable(ts_total(), filter="top", options=list(pageLength=12, scrollX=TRUE), rownames=FALSE)
})
}
# ===============================
# 11) RUN APP
# ===============================
shinyApp(ui, server)
Bagian ini menyajikan dataset utama yang digunakan dalam seluruh analisis penelitian ini. Data meliputi jumlah kasus tahunan dan bulanan Hepatitis A serta data kependudukan di Provinsi DKI Jakarta periode 2017–2025. Selain itu, dilampirkan pula sampel data indikator kesehatan dan sosial ekonomi tingkat kabupaten/kota sebagai referensi pengembangan model di masa mendatang.
| Wilayah | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 |
|---|---|---|---|---|---|---|---|---|---|
| Jakarta Pusat | 35 | 32 | 43 | 27 | 6 | 13 | 11 | 10 | 10 |
| Jakarta Utara | 11 | 165 | 137 | 84 | 16 | 65 | 62 | 47 | 48 |
| Jakarta Barat | 75 | 83 | 140 | 69 | 16 | 18 | 27 | 45 | 118 |
| Jakarta Selatan | 109 | 107 | 255 | 73 | 19 | 18 | 11 | 18 | 13 |
| Jakarta Timur | 89 | 66 | 110 | 50 | 17 | 25 | 36 | 26 | 31 |
| Kab. Kep. Seribu | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| TOTAL PROVINSI | 319 | 453 | 685 | 303 | 74 | 139 | 147 | 146 | 220 |
| Wilayah | 2017 | 2018 | 2019 | 2020 | 2025 |
|---|---|---|---|---|---|
| Jakarta Pusat | 921.344 | 924.686 | 928.109 | 1.056.896 | 1.038.396 |
| Jakarta Utara | 1.781.316 | 1.797.292 | 1.812.915 | 1.778.981 | 1.819.009 |
| Jakarta Barat | 2.528.065 | 2.559.362 | 2.589.933 | 2.434.511 | 2.487.199 |
| Jakarta Selatan | 2.226.830 | 2.246.137 | 2.264.699 | 2.226.812 | 2.219.225 |
| Jakarta Timur | 2.892.783 | 2.916.018 | 2.937.859 | 3.037.139 | 3.085.058 |
| Kab. Kep. Seribu | 23.897 | 24.134 | 24.295 | 27.749 | 29.088 |
| TOTAL PROVINSI | 10.374.235 | 10.467.629 | 10.557.810 | 10.562.088 | 10.677.975 |
Data kependudukan di atas merupakan populasi berisiko (population at risk) yang digunakan sebagai denominator dalam perhitungan Incidence Rate (IR) per 100.000 penduduk guna melakukan perbandingan risiko antarwilayah secara objektif.
| No | Wilayah | Kasus HIV | Penduduk | Pengangguran (%) | Kemiskinan (%) | IPM |
|---|---|---|---|---|---|---|
| 1 | Kab. Cilacap | 57 | 2.007.829 | 8,74 | 10,99 | 72,04 |
| 2 | Kab. Banyumas | 88 | 1.828.573 | 6,35 | 12,53 | 73,96 |
| 3 | Kab. Purbalingga | 55 | 1.027.333 | 5,61 | 14,99 | 70,51 |
| 4 | Kab. Banjarnegara | 64 | 1.047.226 | 6,26 | 14,90 | 69,16 |
| 5 | Kab. Kebumen | 57 | 1.397.555 | 5,11 | 16,34 | 71,88 |
| … | … | … | … | … | … | … |
| 36 | PROVINSI JATENG | 1.608 | 37.540.000 | 5,13 | 10,77 | 73,39 |
Seluruh data yang tercantum dalam lampiran ini telah diverifikasi kelengkapannya melalui proses pembersihan data (data cleaning) sebelum digunakan dalam pemodelan deret waktu (ARIMA, ETS, Holt) serta analisis autokorelasi spasial Moran’s I.
SELESAI.