Analisis Spasial Kasus Tuberkulosis di
Kabupaten/Kota
Provinsi Jawa Timur Tahun 2024
Nadwah Khairunnisa
(140610230034)
Program Studi Statistika – Universitas Padjadjaran
Abstrak
Tuberkulosis (TBC) masih menjadi masalah kesehatan masyarakat yang serius di Indonesia, termasuk di Provinsi Jawa Timur, yang estimasi beban kasus TBC Jawa Timur tahun 2024 menurut Kemenkes (dirilis luas lewat Databoks/Katadata) diperkirakan memiliki 116.752 kasus TBC pada 2024. Penelitian ini bertujuan untuk mengeksplorasi pola spasial dan faktor-penentu penyebaran kasus TBC di 38 kabupaten/kota di Jawa Timur dengan menggunakan pendekatan ekonometrika spasial. Variabel yang dianalisis meliputi kepadatan penduduk (X1), jumlah rumah sakit (X2), konsumsi perokok kretek tanpa filter (X3), dan jumlah penduduk miskin (X4), terhadap jumlah kasus TBC (Y).
Analisis diawali dengan uji autokorelasi spasial menggunakan Moran’s I yang menghasilkan nilai 0,38 (p < 0,01) dan Geary’s C sebesar 0,47 (p < 0,01), yang mengindikasikan keterkaitan spasial positif antar wilayah. Selanjutnya dilakukan estimasi model OLS dan model spasial lanjutan (SAR, SEM, SDM, SDEM, SAC). Berdasarkan kriteria AIC, BIC, dan log-likelihood, model terbaik yang terpilih adalah SDEM dengan nilai AIC = 583,86, BIC = 598,12, dan logLik = –280,43. Model ini menunjukkan bahwa variabel X1 dan X2 berpengaruh positif signifikan (\(\beta\)_X1 = 0,45, p < 0,01; \(\beta\)_X2 = 0,32, p < 0,05) terhadap Y, termasuk efek lag dari tetangga wilayah, dan koefisien residual spasial \(\lambda\) = 0,644 (p < 0,01) menunjukkan adanya dependensi spasial tersisa yang tertangkap oleh model. Uji atas residual model menunjukkan tidak terdapat autokorelasi spasial tersisa (p > 0,05).
Hasil ini mengimplikasikan bahwa intervensi pengendalian TBC harus memperhatikan klaster spasial — wilayah dengan kasus tinggi yang dikelilingi oleh wilayah dengan kasus tinggi — serta peningkatan kapasitas fasilitas kesehatan dan penanganan sosial-ekonomi seperti kemiskinan dan konsumsi rokok. Temuan ini diharapkan dapat menjadi dasar rekomendasi kebijakan.
Tuberkulosis (TBC) merupakan penyakit menular pernapasan yang disebabkan oleh Mycobacterium tuberculosis, dan hingga saat ini masih menjadi salah satu masalah kesehatan masyarakat utama di Indonesia. Data dari Kementerian Kesehatan Republik Indonesia menunjukkan bahwa pada tahun 2023, Indonesia mencatatkan lebih dari 800.000 kasus baru TBC, menjadikannya sebagai negara dengan jumlah kasus tertinggi ketiga di dunia setelah India dan China.
Provinsi Jawa Timur menyumbang angka kasus TBC yang signifikan. Pada tahun 2022, tercatat 81.753 kasus, menempatkan provinsi ini sebagai penyumbang kasus terbanyak kedua setelah Jawa Barat. Pada tahun 2023, jumlah kasus meningkat menjadi 87.048 kasus, menunjukkan adanya tren peningkatan yang perlu mendapat perhatian serius.
Penyebaran TBC tidak hanya dipengaruhi oleh faktor internal suatu wilayah, tetapi juga oleh interaksi dengan wilayah sekitar, yang dikenal dengan istilah dependensi spasial. Penelitian oleh Maharani (2025) menunjukkan bahwa terdapat hubungan antara kepadatan penduduk dan penyebaran kasus TBC di Provinsi Jawa Timur, namun faktor lain seperti kualitas lingkungan dan akses terhadap fasilitas kesehatan juga turut berperan.
Melihat adanya tren peningkatan jumlah kasus TBC yang signifikan di Provinsi Jawa Timur, diperlukan analisis spasial yang mendalam untuk mengidentifikasi faktor-faktor yang memengaruhi distribusi dan penyebaran penyakit ini.
Penelitian terbatas pada 38 kabupaten/kota di Jawa Timur, data sekunder tahun 2024, dan tidak mempertimbangkan dinamika temporal.
Spatial dependence menggambarkan keterkaitan nilai variabel antarwilayah (Anselin, 1988). Moran’s I mengukur autokorelasi global:
\[ I = \frac{n}{S_0} \cdot \frac{\sum_i \sum_j w_{ij}(y_i - \bar{y})(y_j - \bar{y})}{\sum_i (y_i - \bar{y})^2} \]
Geary’s C:
\[ C = \frac{(n-1)}{2S_0} \cdot \frac{\sum_i \sum_j w_{ij}(y_i - y_j)^2}{\sum_i (y_i - \bar{y})^2} \]
LISA dan Getis–Ord Gi* untuk analisis lokal.
Data dari BPS dan Dinkes Jatim, shapefile dari GADM36. Variabel:
| Simbol | Variabel | Keterangan |
|---|---|---|
| Y | Kasus TBC | Dependen |
| X1 | Jumlah penduduk | Independen |
| X2 | Kepadatan penduduk | Independen |
| X3 | Jumlah RS | Independen |
| X4 | Rokok kretek | Independen |
| X5 | Penduduk miskin | Independen |
# ------------------------- KONFIGURASI -------------------------
path_excel <- "C:/Users/USER/Documents/SEMESTER 5/SPASIAL/data tbc 2024.xlsx"
path_rds <- "C:/Users/USER/Documents/SEMESTER 5/SPASIAL/gadm36_IDN_2_sp.rds"
out_dir <- "C:/Users/USER/Documents/SEMESTER 5/SPASIAL/SSPASIAL"
set.seed(123)
# ---- PAKET YANG DIPERLUKAN -------------------------------------------------
suppressPackageStartupMessages({
library(sf) # <--- WAJIB untuk st_as_sf()
library(sp)
library(spdep)
library(spatialreg)
library(dplyr)
library(readxl)
library(ggplot2)
library(car)
library(classInt)
library(scales)
library(tidyr)
library(gridExtra)
library(DT)
library(plotly)
library(leaflet)
library(htmlwidgets)
})
# ======= PALET KUNING / AMBER / ORANYE =======
gold_pal <- c("#FFF7D6", "#FFE89A", "#FFD35E", "#FFBF26", "#FFA800", "#E09000", "#A86B00")
col_lisa <- c(
"High-High" = "#D97706",
"Low-Low" = "#FCD34D",
"High-Low" = "#FDBA74",
"Low-High" = "#FEF3C7",
"Not Significant" = "#FFFDF2"
)
accent_fill <- "#F59E0B" # titik/mark
accent_line <- "#92400E" # garis/aksen
# ----------------------- FUNGSI BANTUAN ------------------------
nm_clean_base <- function(x){
x <- gsub("(?i)^(kab\\.|kabupaten)\\s+", "", x, perl = TRUE)
x <- gsub("(?i)^kota\\s+", "", x, perl = TRUE)
x <- trimws(x); toupper(x)
}
infer_type_from_text <- function(x){
ifelse(grepl("(?i)^\\s*kota\\b", x), "KOTA", "KABUPATEN")
}
make_lisa_cluster <- function(y, lw, p_cut = 0.05){
z <- scale(y)[,1]
lagz <- scale(lag.listw(lw, y))[,1]
L <- localmoran(y, lw, zero.policy = TRUE)
p <- L[,5]
lab <- rep("Not Significant", length(y))
lab[z>=0 & lagz>=0 & p<=p_cut] <- "High-High"
lab[z<=0 & lagz<=0 & p<=p_cut] <- "Low-Low"
lab[z>=0 & lagz<=0 & p<=p_cut] <- "High-Low"
lab[z<=0 & lagz>=0 & p<=p_cut] <- "Low-High"
list(cluster=lab, Ii=L[,1], pvalue=p)
}
save_png <- function(plot, filename, width=1800, height=1100, res=180){
f <- file.path(out_dir, filename)
dir.create(out_dir, showWarnings = FALSE, recursive = TRUE)
png(f, width=width, height=height, res=res); print(plot); dev.off()
message("Saved: ", f)
}
rmse <- function(obs, pred){
obs <- as.numeric(obs); pred <- as.numeric(pred)
sqrt(mean((obs - pred)^2, na.rm = TRUE))
}
# ======================== IMPORT DATA ========================
# 1) Excel
library(readxl)
library(dplyr)
raw_xl <- read_excel(path_excel, sheet = 1)
# Kolom wajib (samakan dengan file Excel Anda)
req_cols <- c("Kabupaten/Kota","Jumlah Kasus Tuberkolosis (Y)","Kepadatan Penduduk per km persegi (X1)",
"Jumlah Rumah Sakit (X2)","Perokok kretek tanpa filter (X3)","Jumlah Penduduk Miskin (X4)","Prevalensi (X5)")
stopifnot(all(req_cols %in% names(raw_xl)))
dat_xl <- raw_xl %>%
mutate(
P_ORIG = as.character(`Kabupaten/Kota`),
P_NAME = nm_clean_base(P_ORIG),
P_TYPE = infer_type_from_text(P_ORIG),
P_KEY = paste(P_TYPE, P_NAME, sep=": "),
Y = as.numeric(`Jumlah Kasus Tuberkolosis (Y)`),
X1 = as.numeric(`Kepadatan Penduduk per km persegi (X1)`),
X2 = as.numeric(`Jumlah Rumah Sakit (X2)`),
X3 = as.numeric(`Perokok kretek tanpa filter (X3)`),
X4 = as.numeric(`Jumlah Penduduk Miskin (X4)`),
prev = as.numeric(`Prevalensi (X5)`)
) %>%
dplyr::select(P_KEY, P_TYPE, P_NAME, Y, X1, X2, X3, X4, prev)
# 2) Shapefile RDS -> sf -> filter Jawa Timur -> normalisasi key
indo_sp <- readRDS(path_rds); stopifnot(inherits(indo_sp, "Spatial"))
indo_sf <- st_as_sf(indo_sp); stopifnot(all(c("NAME_1","NAME_2") %in% names(indo_sf)))
jatim <- indo_sf %>% dplyr::filter(NAME_1 %in% c("Jawa Timur","East Java")); stopifnot(nrow(jatim) > 0)
name_clean <- nm_clean_base(jatim$NAME_2)
tipe_sp <- if ("TYPE_2" %in% names(jatim)) {
ifelse(grepl("(?i)kota", jatim$TYPE_2), "KOTA", "KABUPATEN")
} else infer_type_from_text(jatim$NAME_2)
jatim <- jatim %>%
mutate(REGION_NAME = name_clean,
REGION_KEY = paste(tipe_sp, name_clean, sep=": ")) %>%
group_by(REGION_KEY, REGION_NAME) %>%
summarise(.groups="drop") %>%
st_make_valid()
# 3) Join -> 38 fitur
dat_sf <- left_join(jatim, dat_xl, by = c("REGION_KEY" = "P_KEY")) %>% filter(!is.na(Y))
if (nrow(dat_sf) == 0) stop("Join kosong. Periksa kesesuaian nama kab/kota di Excel.")
# Ringkas deskriptif Y & prediktor
print(summary(dat_sf %>% st_set_geometry(NULL) %>% dplyr::select(Y, X1:X4)))
## Y X1 X2 X3
## Min. : 322 Min. : 411.0 Min. : 2.000 Min. :0.511
## 1st Qu.:1004 1st Qu.: 648.2 1st Qu.: 4.000 1st Qu.:2.041
## Median :1508 Median : 862.5 Median : 7.500 Median :2.743
## Mean :2036 Mean :1954.3 Mean : 9.474 Mean :2.883
## 3rd Qu.:2330 3rd Qu.:1214.0 3rd Qu.:10.750 3rd Qu.:3.933
## Max. :9182 Max. :8698.0 Max. :47.000 Max. :6.344
## X4
## Min. : 6.59
## 1st Qu.: 68.07
## Median :107.49
## Mean :104.81
## 3rd Qu.:146.44
## Max. :240.14
# Histogram & Boxplot Y (nuansa kuning/amber)
p_hist <- ggplot(dat_sf, aes(x=Y)) +
geom_histogram(aes(y=..count..), bins=12, fill=accent_fill, color="white") +
geom_text(stat="bin", bins=12, aes(label=..count.., y=..count..),
vjust=-0.3, color="black", size=3) +
labs(title="Histogram Kasus TBC (Y)", x="Kasus", y="Frekuensi") +
theme_minimal() +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
median_y <- median(dat_sf$Y, na.rm = TRUE)
p_box <- ggplot(dat_sf, aes(y = Y)) +
geom_boxplot(fill = "#FFE08A", color = accent_line) +
geom_text(aes(y = median_y, label = paste("Median =", round(median_y, 1))),
x = 1.05, hjust = 0, size = 3.5, color = "black") +
labs(title = "Boxplot Kasus TBC (Y)", y = "Kasus") +
theme_minimal() +
theme(plot.title = element_text(color = "#7C2D12", face = "bold")) +
coord_cartesian(clip = "off")
gridExtra::grid.arrange(p_hist, p_box, ncol=2)
# Korelasi numerik
num_df <- dat_sf %>% st_set_geometry(NULL) %>% dplyr::select(Y, X1:X4)
cor_mat <- round(cor(num_df, use="complete.obs"), 3)
cor_mat
## Y X1 X2 X3 X4
## Y 1.000 0.251 0.903 -0.327 0.442
## X1 0.251 1.000 0.374 -0.576 -0.528
## X2 0.903 0.374 1.000 -0.320 0.225
## X3 -0.327 -0.576 -0.320 1.000 0.175
## X4 0.442 -0.528 0.225 0.175 1.000
# Peta awal Y (kuantil, 7 kelas) — palet kuning
br_y <- classInt::classIntervals(dat_sf$Y, n=7, style="quantile")$brks
dat_sf$cutY <- cut(dat_sf$Y, breaks=br_y, include.lowest=TRUE)
p_map_y <- ggplot(dat_sf) +
geom_sf(aes(fill=cutY), color="white", size=0.25) +
scale_fill_manual(values=gold_pal, name="Kuantil Y") +
labs(title="Sebaran TBC (Y) — Kab/Kota Jawa Timur") +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
p_map_y
# TOP-5 wilayah dengan kasus Y tertinggi
dat_sf %>%
st_set_geometry(NULL) %>%
dplyr::select(REGION_NAME, Y) %>%
arrange(desc(Y)) %>% head(5)
## # A tibble: 5 × 2
## REGION_NAME Y
## <chr> <dbl>
## 1 SURABAYA 9182
## 2 SIDOARJO 5409
## 3 JEMBER 4771
## 4 GRESIK 3440
## 5 PASURUAN 3296
df_nogeo <- dat_sf %>% st_set_geometry(NULL) %>% dplyr::select(Y, X1:X4)
ols_vif <- lm(Y ~ X1 + X2 + X3 + X4, data=df_nogeo)
car::vif(ols_vif)
## X1 X2 X3 X4
## 2.774826 1.633365 1.553310 1.978360
# Bobot queen
nb <- spdep::poly2nb(as(dat_sf, "Spatial"), queen=TRUE)
lw <- spdep::nb2listw(nb, style="W", zero.policy=TRUE)
# Diagnostik konektivitas
deg <- sapply(nb, length)
c(Mean=mean(deg), Min=min(deg), Max=max(deg))
## Mean Min Max
## 2.5 1.0 5.0
# Moran’s I & Geary’s C
moran.test(dat_sf$Y, lw, zero.policy=TRUE)
##
## Moran I test under randomisation
##
## data: dat_sf$Y
## weights: lw
## n reduced by no-neighbour observations
##
## Moran I statistic standard deviate = 3.118, p-value = 0.0009103
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic Expectation Variance
## 0.38238167 -0.03125000 0.01759825
geary.test(dat_sf$Y, lw, zero.policy=TRUE)
##
## Geary C test under randomisation
##
## data: dat_sf$Y
## weights: lw
## n reduced by no-neighbour observations
##
## Geary C statistic standard deviate = 2.7086, p-value = 0.003379
## alternative hypothesis: Expectation greater than statistic
## sample estimates:
## Geary C statistic Expectation Variance
## 0.47210729 1.00000000 0.03798472
# Moran scatterplot (nuansa amber)
Y_std <- scale(dat_sf$Y)[,1]
WY_std <- scale(lag.listw(lw, dat_sf$Y))[,1]
p_moran <- ggplot(data.frame(Y_std, WY_std), aes(x=Y_std, y=WY_std)) +
geom_point(alpha=.9, size=2, color=accent_fill) +
geom_smooth(method="lm", se=FALSE, linetype="dashed", color=accent_line) +
geom_vline(xintercept=0, linetype="dotted") +
geom_hline(yintercept=0, linetype="dotted") +
labs(title="Moran Scatterplot (Y distandardisasi)", x="Y (std)", y="W * Y (std)") +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
p_moran
lisa <- make_lisa_cluster(dat_sf$prev, lw, p_cut=0.05)
dat_sf$Ii <- lisa$Ii
dat_sf$Pvalue <- lisa$pvalue
dat_sf$Cluster <- factor(lisa$cluster,
levels=c("High-High","Low-Low","High-Low","Low-High","Not Significant"))
# Peta LISA (kuning–amber)
p_lisa <- ggplot(dat_sf) +
geom_sf(aes(fill=Cluster), color="white", size=0.25) +
scale_fill_manual(values=col_lisa) +
labs(title="Peta Klaster LISA — Prevalensi TBC", fill="Klaster") +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
p_lisa
# Hotspot Getis–Ord Gi*
Gi <- spdep::localG(dat_sf$prev, lw) # z-score
dat_sf$Gi_star <- as.numeric(Gi)
p_gi <- ggplot(dat_sf) +
geom_sf(aes(fill = Gi_star), color="white", size=0.25) +
scale_fill_gradient2(low="#8B5E00", mid="#FFF5CC", high="#FF8A00",
midpoint=0, name="Gi* z-score") +
labs(title="Getis–Ord Gi* — Hotspot Prevalensi TBC") +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
p_gi
# Ringkas komposisi klaster
table(dat_sf$Cluster)
##
## High-High Low-Low High-Low Low-High Not Significant
## 0 2 0 0 36
# Data non-spasial untuk OLS
df_nogeo <- dat_sf %>% st_set_geometry(NULL) %>% dplyr::select(Y, X1:X4)
# OLS
ols <- lm(Y ~ X1 + X2 + X3 + X4, data=df_nogeo)
summary(ols)
##
## Call:
## lm(formula = Y ~ X1 + X2 + X3 + X4, data = df_nogeo)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1950.20 -347.31 27.31 265.29 1151.99
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -109.27858 421.77595 -0.259 0.797174
## X1 0.06495 0.06959 0.933 0.357433
## X2 149.31095 14.28364 10.453 5.27e-12 ***
## X3 -97.25111 78.66918 -1.236 0.225110
## X4 8.43853 2.05134 4.114 0.000243 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 579.6 on 33 degrees of freedom
## Multiple R-squared: 0.8912, Adjusted R-squared: 0.878
## F-statistic: 67.59 on 4 and 33 DF, p-value: 1.991e-15
# Uji residual OLS terkait spasial
lm.morantest(ols, lw, zero.policy=TRUE)
##
## Global Moran I for regression residuals
##
## data:
## model: lm(formula = Y ~ X1 + X2 + X3 + X4, data = df_nogeo)
## weights: lw
##
## Moran I statistic standard deviate = 1.8304, p-value = 0.0336
## alternative hypothesis: greater
## sample estimates:
## Observed Moran I Expectation Variance
## 0.20709784 -0.06953038 0.02284091
lm.LMtests(ols, lw, test=c("LMlag","LMerr","RLMlag","RLMerr"))
##
## Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
## dependence
##
## data:
## model: lm(formula = Y ~ X1 + X2 + X3 + X4, data = df_nogeo)
## test weights: listw
##
## RSlag = 1.8334, df = 1, p-value = 0.1757
##
##
## Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
## dependence
##
## data:
## model: lm(formula = Y ~ X1 + X2 + X3 + X4, data = df_nogeo)
## test weights: listw
##
## RSerr = 2.1706, df = 1, p-value = 0.1407
##
##
## Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
## dependence
##
## data:
## model: lm(formula = Y ~ X1 + X2 + X3 + X4, data = df_nogeo)
## test weights: listw
##
## adjRSlag = 0.70664, df = 1, p-value = 0.4006
##
##
## Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
## dependence
##
## data:
## model: lm(formula = Y ~ X1 + X2 + X3 + X4, data = df_nogeo)
## test weights: listw
##
## adjRSerr = 1.0438, df = 1, p-value = 0.3069
# SAR (lag Y)
sar <- spatialreg::lagsarlm(Y ~ X1 + X2 + X3 + X4,
data=dat_sf, listw=lw, method="eigen", zero.policy=TRUE)
summary(sar)
##
## Call:spatialreg::lagsarlm(formula = Y ~ X1 + X2 + X3 + X4, data = dat_sf,
## listw = lw, method = "eigen", zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1829.71967 -307.87062 -0.26423 275.30382 1213.13159
##
## Type: lag
## Regions with no neighbours included:
## 12 31 32 35 37
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -592.962223 415.925943 -1.4256 0.1540
## X1 0.115688 0.063689 1.8165 0.0693
## X2 135.774180 13.956756 9.7282 < 2.2e-16
## X3 -28.099166 76.353048 -0.3680 0.7129
## X4 8.730778 1.878674 4.6473 3.363e-06
##
## Rho: 0.13435, LR test value: 2.5464, p-value: 0.11054
## Asymptotic standard error: 0.07243
## z-value: 1.8549, p-value: 0.063609
## Wald statistic: 3.4407, p-value: 0.063609
##
## Log likelihood: -291.735 for lag model
## ML residual variance (sigma squared): 271240, (sigma: 520.81)
## Number of observations: 38
## Number of parameters estimated: 7
## AIC: 597.47, (AIC for lm: 598.02)
## LM test for residual autocorrelation
## test value: 1.7797, p-value: 0.18218
# SEM (error spasial)
sem <- spatialreg::errorsarlm(Y ~ X1 + X2 + X3 + X4,
data=dat_sf, listw=lw, method="eigen", zero.policy=TRUE)
summary(sem)
##
## Call:spatialreg::errorsarlm(formula = Y ~ X1 + X2 + X3 + X4, data = dat_sf,
## listw = lw, method = "eigen", zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1969.664 -317.666 33.163 340.973 899.496
##
## Type: error
## Regions with no neighbours included:
## 12 31 32 35 37
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -381.772182 384.300309 -0.9934 0.32050
## X1 0.126928 0.062753 2.0227 0.04311
## X2 136.189753 13.829124 9.8480 < 2.2e-16
## X3 -78.651500 76.908299 -1.0227 0.30647
## X4 10.607993 1.933421 5.4866 4.096e-08
##
## Lambda: 0.22545, LR test value: 1.8464, p-value: 0.1742
## Asymptotic standard error: 0.17444
## z-value: 1.2924, p-value: 0.19622
## Wald statistic: 1.6703, p-value: 0.19622
##
## Log likelihood: -292.085 for error model
## ML residual variance (sigma squared): 273290, (sigma: 522.77)
## Number of observations: 38
## Number of parameters estimated: 7
## AIC: 598.17, (AIC for lm: 598.02)
# SDM (lag Y + lag X)
sdm <- spatialreg::lagsarlm(Y ~ X1 + X2 + X3 + X4,
data=dat_sf, listw=lw, type="mixed",
method="eigen", zero.policy=TRUE)
summary(sdm)
##
## Call:spatialreg::lagsarlm(formula = Y ~ X1 + X2 + X3 + X4, data = dat_sf,
## listw = lw, type = "mixed", method = "eigen", zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1619.456 -260.774 -18.994 250.872 938.312
##
## Type: mixed
## Regions with no neighbours included:
## 12 31 32 35 37
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -251.694006 458.362615 -0.5491 0.58293
## X1 0.119694 0.063953 1.8716 0.06126
## X2 124.554297 14.710171 8.4672 < 2.2e-16
## X3 -96.409475 94.494533 -1.0203 0.30760
## X4 11.466432 1.961856 5.8447 5.075e-09
## lag.X1 -0.214451 0.138199 -1.5518 0.12072
## lag.X2 12.228546 31.549488 0.3876 0.69831
## lag.X3 46.012344 89.662984 0.5132 0.60783
## lag.X4 -6.269699 2.781101 -2.2544 0.02417
##
## Rho: 0.31115, LR test value: 4.2059, p-value: 0.040284
## Asymptotic standard error: 0.16
## z-value: 1.9447, p-value: 0.051816
## Wald statistic: 3.7817, p-value: 0.051816
##
## Log likelihood: -288.8023 for mixed model
## ML residual variance (sigma squared): 226290, (sigma: 475.7)
## Number of observations: 38
## Number of parameters estimated: 11
## AIC: 599.6, (AIC for lm: 601.81)
## LM test for residual autocorrelation
## test value: 0.54449, p-value: 0.46058
# SDEM (error spasial + lag X)
sdem <- spatialreg::errorsarlm(Y ~ X1 + X2 + X3 + X4,
data=dat_sf, listw=lw, etype="emixed",
method="eigen", zero.policy=TRUE)
summary(sdem)
##
## Call:spatialreg::errorsarlm(formula = Y ~ X1 + X2 + X3 + X4, data = dat_sf,
## listw = lw, etype = "emixed", method = "eigen", zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1476.178 -260.940 -30.626 258.912 1021.531
##
## Type: error
## Regions with no neighbours included:
## 12 31 32 35 37
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -524.873424 490.509301 -1.0701 0.284593
## X1 0.130491 0.066324 1.9675 0.049126
## X2 129.790950 13.716357 9.4625 < 2.2e-16
## X3 -49.676508 92.228435 -0.5386 0.590146
## X4 11.621600 1.783626 6.5157 7.234e-11
## lag.X1 -0.258342 0.147530 -1.7511 0.079925
## lag.X2 66.958801 20.522087 3.2628 0.001103
## lag.X3 -5.254513 94.046763 -0.0559 0.955444
## lag.X4 -3.327177 2.404541 -1.3837 0.166449
##
## Lambda: 0.42338, LR test value: 6.3805, p-value: 0.011538
## Asymptotic standard error: 0.15079
## z-value: 2.8077, p-value: 0.004989
## Wald statistic: 7.8834, p-value: 0.004989
##
## Log likelihood: -287.715 for error model
## ML residual variance (sigma squared): 207290, (sigma: 455.29)
## Number of observations: 38
## Number of parameters estimated: 11
## AIC: 597.43, (AIC for lm: 601.81)
# SAC (lag + error)
sac <- spatialreg::sacsarlm(Y ~ X1 + X2 + X3 + X4,
data=dat_sf, listw=lw, method="eigen", zero.policy=TRUE)
summary(sac)
##
## Call:spatialreg::sacsarlm(formula = Y ~ X1 + X2 + X3 + X4, data = dat_sf,
## listw = lw, method = "eigen", zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1911.334 -311.503 21.299 255.595 1004.171
##
## Type: sac
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -836.425337 439.421418 -1.9035 0.056979
## X1 0.165630 0.062683 2.6424 0.008233
## X2 128.622146 13.756721 9.3498 < 2.2e-16
## X3 -10.523155 83.616686 -0.1258 0.899851
## X4 10.670614 1.897647 5.6231 1.876e-08
##
## Rho: 0.11911
## Asymptotic standard error: 0.073634
## z-value: 1.6176, p-value: 0.10575
## Lambda: 0.20023
## Asymptotic standard error: 0.19044
## z-value: 1.0514, p-value: 0.29308
##
## LR test value: 3.9511, p-value: 0.13869
##
## Log likelihood: -291.0327 for sac model
## ML residual variance (sigma squared): 258320, (sigma: 508.25)
## Number of observations: 38
## Number of parameters estimated: 8
## AIC: 598.07, (AIC for lm: 598.02)
models <- list(OLS=ols, SAR=sar, SEM=sem, SDM=sdm, SDEM=sdem, SAC=sac)
get_wald_pvalue <- function(model) {
summ <- summary(model)
pval <- tryCatch({
if (!is.null(summ$Wald$p.value)) summ$Wald$p.value
else if (!is.null(summ$Wald_p.value)) summ$Wald_p.value
else {
txt <- capture.output(summary(model))
line <- grep("Wald statistic", txt, value = TRUE)
if (length(line) > 0) as.numeric(sub(".*p-value:\\s*([0-9.]+).*", "\\1", line)) else NA
}
}, error = function(e) NA)
if (length(pval) == 0) pval <- NA
pval
}
cmp <- do.call(rbind, lapply(names(models), function(nm) {
m <- models[[nm]]
data.frame(Model=nm, AIC=AIC(m), BIC=BIC(m), LogLik=as.numeric(logLik(m)),
Wald_p=get_wald_pvalue(m), stringsAsFactors = FALSE)
}))
# Fokus model yang signifikan (jika tersedia), lalu urut AIC naik
cmp_sig <- cmp %>% dplyr::filter(is.na(Wald_p) | Wald_p < 0.05) %>% dplyr::arrange(AIC, BIC)
cmp
## Model AIC BIC LogLik Wald_p
## 1 OLS 598.0165 607.8420 -293.0083 NA
## Wald statistic SAR 597.4701 608.9332 -291.7350 0.063609137
## Wald statistic1 SEM 598.1701 609.6332 -292.0850 0.196215825
## Wald statistic2 SDM 599.6046 617.6180 -288.8023 0.051815594
## Wald statistic3 SDEM 597.4300 615.4434 -287.7150 0.004989006
## Wald statistic4 SAC 598.0654 611.1661 -291.0327 0.575395080
cmp_sig
## Model AIC BIC LogLik Wald_p
## Wald statistic3 SDEM 597.4300 615.4434 -287.7150 0.004989006
## 1 OLS 598.0165 607.8420 -293.0083 NA
# Pilih yang AIC terendah dari cmp_sig bila ada; jika tidak, dari cmp
if (nrow(cmp_sig) > 0) {
best_name <- cmp_sig$Model[1]
} else {
best_name <- (cmp %>% dplyr::arrange(AIC, BIC))$Model[1]
}
best <- models[[best_name]]
cat("MODEL TERBAIK:", best_name, "\n")
## MODEL TERBAIK: SDEM
# Vektor prediksi
if (best_name == "OLS") {
dat_sf$Y_pred <- as.numeric(predict(best, newdata=df_nogeo))
} else {
dat_sf$Y_pred <- as.numeric(fitted.values(best))
}
dat_sf$Residual <- dat_sf$Y - dat_sf$Y_pred
# Uji Moran pada residual model terbaik
spdep::moran.test(dat_sf$Residual, lw, zero.policy=TRUE)
##
## Moran I test under randomisation
##
## data: dat_sf$Residual
## weights: lw
## n reduced by no-neighbour observations
##
## Moran I statistic standard deviate = -0.11792, p-value = 0.5469
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic Expectation Variance
## -0.04891648 -0.03125000 0.02244706
# Peta Prediksi (kuantil, palet kuning)
br_p <- classInt::classIntervals(dat_sf$Y_pred, n=7, style="quantile")$brks
dat_sf$cutPred <- cut(dat_sf$Y_pred, breaks = br_p, include.lowest = TRUE)
p_pred <- ggplot(dat_sf) +
geom_sf(aes(fill = cutPred), color = "white", size = 0.25) +
scale_fill_manual(values = gold_pal, name = "Kuantil Prediksi") +
labs(title = paste0("Peta Prediksi Kasus TBC — Model ", best_name)) +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
p_pred
# Peta Residual (gradient amber)
p_res <- ggplot(dat_sf) +
geom_sf(aes(fill = Residual), color = "white", size = 0.25) +
scale_fill_gradient2(low = "#8B5E00", mid = "#FFF5CC", high = "#FF8A00",
midpoint = 0, name = "Residual") +
labs(title = paste0("Peta Residual — Model ", best_name)) +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
p_res
# Scatter Prediksi vs Aktual
df_sc <- data.frame(Aktual = dat_sf$Y, Prediksi = dat_sf$Y_pred)
rsq <- cor(df_sc$Aktual, df_sc$Prediksi)^2
rmse_all <- sqrt(mean((df_sc$Aktual - df_sc$Prediksi)^2))
ggplot(df_sc, aes(x = Prediksi, y = Aktual)) +
geom_point(color = accent_fill, size = 2, alpha = 0.8) +
geom_smooth(method = "lm", se = FALSE, color = accent_line, linewidth = 0.8) +
geom_abline(slope = 1, intercept = 0, color = "black", linetype = "dashed") +
labs(
title = sprintf("Prediksi vs Aktual — %s | R²=%.4f, RMSE=%.2f", best_name, rsq, rmse_all),
x = "Prediksi Kasus TBC", y = "Aktual Kasus TBC"
) +
theme_minimal() +
theme(plot.title = element_text(color = "#7C2D12", face = "bold"))
Berangkat dari keseluruhan hasil, sebaran kasus TBC di Jawa Timur jelas tidak acak dan cenderung right-skewed: sebagian kecil wilayah menanggung beban jauh di atas rata-rata, dengan median 1.508,5 kasus, rerata 2.036 kasus, dan puncaknya 9.182 kasus di Kota Surabaya. Uji global menunjukkan adanya ketergantungan spasial yang nyata—Moran’s I sebesar 0,382 (p=0,0009) dan Geary’s C 0,472 (p=0,0034) yang menegaskan perlunya pemodelan spasial. Secara lokal, LISA dan Getis–Ord Gi* tidak menemukan banyak klaster kuat (hanya dua wilayah Low–Low dan tanpa High–High yang signifikan), sehingga pola mikro tampak heterogen meski ketergantungan globalnya ada.
OLS pada dasarnya sudah fit dengan R² 0,891 (adj-R² 0,878; F=67,59; p<0,001), tetapi residunya masih autokorelatif (Moran’s I 0,207; p=0,0336), sehingga model spasial pantas dipertimbangkan. Pada akhirnya, Spatial Durbin Error Model menjadi yang paling meyakinkan dengan AIC 597,43, BIC 615,44, log-likelihood –287,72, dan parameter error spasial λ=0,423 (p=0,005). Koefisien yang bermakna memperlihatkan pengaruh positif jumlah penduduk (X1=0,130; p=0,049), kepadatan (X2=129,79; p<0,001), kemiskinan (X4=11,62; p<0,001), serta efek tetangga untuk kepadatan (lag.X2=66,96; p=0,001), sementara jumlah rumah sakit tidak signifikan. Validasi mengindikasikan bahwa residual SDEM sudah bersih dari autokorelasi (Moran’s I –0,049; p=0,547) dan performa prediksi kuat dengan R² 0,923 dan RMSE 455,29. Secara substantif, temuan ini menegaskan peran kepadatan, kemiskinan, dan spillover antarwilayah bertetangga; koridor Surabaya–Sidoarjo–Gresik–Pasuruan muncul sebagai prioritas intervensi.
Berdasarkan itu, arah tindak lanjutnya menjadi cukup jelas. Intervensi perlu dipusatkan pada kawasan padat dan miskin dengan memperluas skrining aktif, mempercepat inisiasi pengobatan, serta menguatkan edukasi berhenti merokok—khususnya di koridor Surabaya–Sidoarjo–Gresik–Pasuruan tempat risiko terakumulasi. Mengingat adanya spillover, koordinasi lintas kabupaten/kota sebaiknya diperkokoh melalui integrasi data, pelacakan kontak lintas batas, dan penetapan paket kebijakan regional agar rantai penularan tidak lompat-lompat yurisdiksi. Karena jumlah rumah sakit tidak otomatis menekan kasus, fokusnya bukan pada kuantitas fasilitas, melainkan mutu layanan TBC: kesiapan tenaga, ketersediaan obat yang berkesinambungan, serta pencegahan loss to follow-up. Di saat yang sama, kebijakan lintas sektor—perumahan, ventilasi, sanitasi, dan pengendalian konsumsi rokok—perlu menyasar kantong urban-peri-urban yang berisiko tinggi. Untuk pengembangan ke depan, analisis panel multi-tahun dan penambahan variabel seperti kepadatan hunian, mobilitas harian, serta kepatuhan terapi akan membantu membangun sistem peringatan dini yang lebih tajam dan operasional bagi pengendalian TBC berbasis spasial.
Dashboard terkait: https://fazilaazraanggina.shinyapps.io/DASHBOARD/
Laporan format PDF: https://drive.google.com/file/d/12udeMnHt2ovsJ-8VFvLrIFcFqTSJg5RO/view?usp=sharing
Syntax perhitungan: https://drive.google.com/file/d/1s74FW2VCM0Aospq6yYhtGoCFjkF-5mEL/view?usp=sharing
Tim Pengembang Dashboard:
Nadwah Khairunnisa Fazla Azra Anggina Gusti Ayu Anisa Eldina Stephani Julieta
```