Latihan Interpolasi Spasial
Library
library(sp)
library(sf)
library(gstat)
library(terra)
library(ggplot2)
library(viridis)
library(dplyr)
library(tidyr)Data
data(meuse)
data(meuse.grid)
meuse <- st_as_sf(meuse, coords = c("x", "y"), crs = 28992)
meuse.grid <- st_as_sf(meuse.grid, coords = c("x", "y"), crs = 28992)
# Peubah yang diinterpolasi
meuse$vble <- log(meuse$zinc)
summary(meuse$zinc)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 113.0 198.0 326.0 469.7 674.5 1839.0
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 4.727 5.288 5.787 5.886 6.514 7.517
skew <- function(x) mean((x - mean(x))^3) / sd(x)^3
par(mfrow = c(1, 2))
hist(meuse$zinc, breaks = 20, col = "grey80", border = "white",
main = "zinc", xlab = "zinc (ppm)")
hist(meuse$vble, breaks = 20, col = "steelblue", border = "white",
main = "log(zinc)", xlab = "log(zinc)")Data terdiri atas 155 titik pengamatan, dan lokasi prediksi berupa
3103 titik grid (meuse.grid). Koefisien skewness
zinc sebesar 1.46, turun menjadi 0.32 setelah transformasi
log. Transformasi log membuat sebaran lebih simetris sehingga asumsi
stasioneritas pada kriging lebih masuk akal dan variogram tidak
didominasi nilai ekstrem. Seluruh RMSE di bawah berada pada
skala log(zinc).
Sebaran lokasi pengamatan dan lokasi prediksi
ggplot() +
geom_sf(data = meuse.grid, colour = "grey85", size = 0.3) +
geom_sf(data = meuse, aes(colour = vble), size = 2) +
scale_colour_viridis_c(name = "log(zinc)") +
labs(title = "Titik pengamatan (berwarna) dan grid prediksi (abu-abu)") +
theme_bw()Titik pengamatan tidak tersebar merata, mengumpul di sepanjang tepi sungai, sedangkan grid prediksi menutup seluruh dataran banjir. Ini berarti sebagian lokasi prediksi berada jauh dari titik pengamatan terdekat, dan di sanalah perbedaan perilaku antarmetode akan paling terlihat. Nilai log(zinc) yang tinggi cenderung mengelompok, indikasi awal adanya autokorelasi spasial.
# Wadah hasil prediksi pada grid
xy <- st_coordinates(meuse.grid)
pred <- data.frame(x = xy[, 1], y = xy[, 2])
# Fungsi bantu pemetaan
plot_pred <- function(df, kolom, judul, legenda = "log(zinc)") {
ggplot(df, aes(x, y, fill = .data[[kolom]])) +
geom_raster() +
scale_fill_viridis_c(name = legenda) +
coord_equal() +
labs(title = judul, x = NULL, y = NULL) +
theme_bw()
}Metode Deterministik
Closest Observation (Voronoi)
Setiap lokasi diberi nilai titik pengamatan terdekat. Wilayah yang dipengaruhi satu titik membentuk poligon Voronoi.
# Batas area
area <- st_convex_hull(st_union(c(st_geometry(meuse), st_geometry(meuse.grid))))
area <- st_buffer(area, 100)
v <- terra::voronoi(terra::vect(meuse), bnd = terra::vect(area))
v <- sf::st_as_sf(v)
ggplot() +
geom_sf(data = v, aes(fill = vble), colour = "white", linewidth = 0.2) +
geom_sf(data = meuse, size = 0.6) +
scale_fill_viridis_c(name = "log(zinc)") +
labs(title = "Diagram Voronoi") +
theme_bw()Setiap poligon memiliki satu nilai konstan dari titik pengamatan di dalamnya. Poligon di area padat titik berukuran kecil, sedangkan di area jarang titik berukuran besar, sehingga satu pengamatan “menguasai” wilayah luas.
idx <- st_nearest_feature(meuse.grid, meuse)
pred$closest <- meuse$vble[idx]
plot_pred(pred, "closest", "Closest Observation")Peta prediksi berbentuk blok dengan perubahan nilai yang tiba-tiba di batas poligon. Metode ini sederhana dan mempertahankan nilai asli data, tetapi tidak menghasilkan permukaan yang mulus dan tidak memakai informasi dari titik lain selain yang terdekat, sehingga rentan terhadap noise pada satu titik.
Inverse Distance Weighting (IDW)
g_idw <- gstat(formula = vble ~ 1, locations = meuse,
nmax = nrow(meuse), set = list(idp = 1))
pred$idw <- predict(g_idw, meuse.grid, debug.level = 0)$var1.pred
plot_pred(pred, "idw", "IDW (idp = 1, semua titik)")Permukaan lebih mulus dibanding Closest Observation karena prediksi
merupakan rata-rata berbobot dari banyak titik. Karena
idp = 1 relatif kecil dan semua titik ikut dihitung, titik
yang jauh tetap memberi pengaruh, sehingga prediksi cenderung tertarik
ke nilai rata-rata global (over-smoothing), terutama di area
yang jauh dari pengamatan. Menaikkan idp atau membatasi
nmax akan membuat prediksi lebih lokal.
Nearest Neighbors (k = 5)
g_nn <- gstat(formula = vble ~ 1, locations = meuse,
nmax = 5, set = list(idp = 0))
pred$nn <- predict(g_nn, meuse.grid, debug.level = 0)$var1.pred
plot_pred(pred, "nn", "Nearest Neighbors (k = 5)")Hasilnya lebih halus dibanding Closest Observation karena merata-ratakan 5 titik, tetapi tetap tampak “bertingkat” saat himpunan 5 tetangga berganti. Pemilihan k menentukan keseimbangan bias-varians: k kecil membuat prediksi mengikuti data lokal (varians tinggi), k besar membuatnya lebih halus (bias lebih tinggi). Pada latihan ini k tidak di-tuning.
Metode Stokastik: Ordinary Kriging
Variogram empiris
Variogram cloud menampilkan semivarians seluruh pasangan titik dan sangat berisik, sehingga sulit dibaca langsung. Variogram empiris merangkumnya per kelas jarak. Semivarians rendah pada jarak dekat dan naik seiring jarak, yang menandakan adanya struktur ketergantungan spasial, titik berdekatan lebih mirip daripada titik berjauhan.
Model variogram
## model psill range
## 1 Nug 0.05066017 0.0000
## 2 Sph 0.59060556 897.0044
Model spherical hasil fitting memiliki nugget 0.051, sill 0.641, dan range 897 meter.
- Nugget 0.051 adalah variasi mikro-spasial dan galat pengukuran.
- Sill 0.641 adalah total variasi spasial.
- Range 897 m berarti pasangan titik yang berjarak lebih dari itu dianggap tidak lagi berkorelasi.
- Rasio nugget terhadap sill adalah 7.9%, termasuk ketergantungan spasial kuat (kriteria umum: < 25% kuat, 25-75% sedang, > 75% lemah).
Kurva model mengikuti pola titik variogram empiris dengan baik secara
visual, tetapi kecocokan ini hanya berdasarkan inspeksi grafis dan nilai
SSErr fitting, belum diuji lebih jauh (misalnya terhadap
model eksponensial).
Prediksi Kriging
g_ok <- gstat(formula = vble ~ 1, data = meuse, model = fv)
kpred <- predict(g_ok, meuse.grid, debug.level = 0)
pred$kriging <- kpred$var1.pred
pred$kriging_var <- kpred$var1.var
plot_pred(pred, "kriging", "Ordinary Kriging prediksi log(zinc)")Permukaan prediksi kriging paling mulus di antara metode sejauh ini, karena bobot tiap titik ditentukan dari struktur korelasi spasial (variogram), bukan hanya jarak. Titik-titik yang saling mengelompok juga tidak “dihitung ganda” sebagaimana pada IDW.
Varians prediksi (0.085 sampai 0.5) kecil di sekitar titik pengamatan dan membesar di area yang jauh dari data, terutama di tepi grid. Inilah keunggulan utama kriging dibanding metode deterministik: kriging memberi ukuran ketidakpastian, bukan hanya prediksi titik.
Ensembel
Prediksi ensembel digabung dari empat metode (Closest Observation, IDW, NN, Kriging), melebihi syarat minimal tiga metode. Pada tahap pemetaan ini digunakan bobot sama (1/4 untuk tiap metode). Pembobotan berdasarkan kinerja diuji pada tahap cross-validation.
metode <- c("closest", "idw", "nn", "kriging")
pred$ensemble <- rowMeans(pred[, metode])
plot_pred(pred, "ensemble", "Ensembel (bobot sama)")## closest idw nn kriging
## closest 1.000 0.779 0.804 0.904
## idw 0.779 1.000 0.888 0.867
## nn 0.804 0.888 1.000 0.919
## kriging 0.904 0.867 0.919 1.000
Peta ensembel merupakan kompromi dari keempat peta, lebih halus dibanding Closest Observation dan NN, tetapi tidak sehalus kriging. Korelasi antarprediksi pada tabel di atas menunjukkan seberapa mirip metode-metode tersebut. Ensembel menguntungkan bila galat tiap metode tidak sepenuhnya searah, sedangkan jika korelasinya sangat tinggi, ensembel hanya menghasilkan sedikit perbaikan. Perlu diingat bahwa ensembel bobot sama juga ikut membawa metode yang lemah.
Perbandingan seluruh peta
lab <- c(closest = "Closest Observation",
idw = "IDW",
nn = "Nearest Neighbors (k = 5)",
kriging = "Ordinary Kriging",
ensemble = "Ensembel (bobot sama)")
pred_long <- pred %>%
select(x, y, all_of(names(lab))) %>%
pivot_longer(-c(x, y), names_to = "metode", values_to = "pred") %>%
mutate(metode = factor(metode, levels = names(lab), labels = lab))
ggplot(pred_long, aes(x, y, fill = pred)) +
geom_raster() +
facet_wrap(~ metode, ncol = 2) +
scale_fill_viridis_c(name = "log(zinc)") +
coord_equal() +
labs(x = NULL, y = NULL) +
theme_bw()Dengan skala warna yang sama, pola umum sebaran zinc (konsentrasi tinggi dekat sungai) tertangkap oleh semua metode. Perbedaannya ada pada kehalusan permukaan dan kekuatan kontras antarwilayah. Peta saja belum cukup untuk menentukan metode terbaik, sehingga diperlukan evaluasi kuantitatif.
Evaluasi Cross-Validation
Rancangan
\[\text{RMSE} = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(z_i - \hat{z}_i)^2}\]
Ada dua versi ensembel:
- Ensembel bobot sama: rata-rata sederhana keempat metode.
- Ensembel bobot 1/RMSE: bobot proporsional dengan kebalikan RMSE tiap metode, sehingga metode yang lebih akurat mendapat bobot lebih besar. Berbeda dengan contoh pada materi (yang menghitung bobot dari RMSE data uji pada lipatan yang sama, sehingga data uji ikut “bocor” ke proses pembobotan), bobot di sini dihitung hanya dari data latih lewat cross-validation bersarang (inner 5-fold).
rmse <- function(obs, pred) sqrt(mean((obs - pred)^2))
# Fitting variogram pada data latih
fit_vgm <- function(train) {
v_tr <- variogram(vble ~ 1, train)
fit <- tryCatch(
suppressWarnings(fit.variogram(v_tr, vinitial)),
error = function(e) NULL
)
if (is.null(fit)) vinitial else fit
}
# Prediksi keempat metode
predict_methods <- function(train, test) {
p_closest <- train$vble[st_nearest_feature(test, train)]
p_idw <- predict(
gstat(formula = vble ~ 1, locations = train,
nmax = nrow(train), set = list(idp = 1)),
test, debug.level = 0)$var1.pred
p_nn <- predict(
gstat(formula = vble ~ 1, locations = train,
nmax = 5, set = list(idp = 0)),
test, debug.level = 0)$var1.pred
p_ok <- predict(
gstat(formula = vble ~ 1, data = train, model = fit_vgm(train)),
test, debug.level = 0)$var1.pred
data.frame(closest = p_closest, idw = p_idw, nn = p_nn, kriging = p_ok)
}
# Bobot 1/RMSE dari data latih saja
inv_rmse_weights <- function(train, k = 5) {
kf_in <- dismo::kfold(nrow(train), k = k)
P <- matrix(NA, nrow(train), 4)
for (j in 1:k) {
P[kf_in == j, ] <- as.matrix(
predict_methods(train[kf_in != j, ], train[kf_in == j, ])
)
}
w <- 1 / apply(P, 2, function(p) rmse(train$vble, p))
w / sum(w)
}Menjalankan cross-validation
set.seed(123)
K <- 5
kf <- dismo::kfold(nrow(meuse), k = K)
res_rmse <- data.frame(fold = 1:K, closest = NA, idw = NA, nn = NA,
kriging = NA, ens_equal = NA, ens_weighted = NA)
w_store <- matrix(NA, K, 4, dimnames = list(paste0("fold", 1:K), metode))
for (k in 1:K) {
test <- meuse[kf == k, ]
train <- meuse[kf != k, ]
P <- predict_methods(train, test)
w <- inv_rmse_weights(train)
w_store[k, ] <- w
for (m in metode) res_rmse[k, m] <- rmse(test$vble, P[[m]])
res_rmse$ens_equal[k] <- rmse(test$vble, rowMeans(P))
res_rmse$ens_weighted[k] <- rmse(test$vble, as.numeric(as.matrix(P) %*% w))
}
round(res_rmse, 4)## fold closest idw nn kriging ens_equal ens_weighted
## 1 1 0.6327 0.5496 0.4958 0.4281 0.4508 0.4476
## 2 2 0.5987 0.5828 0.4943 0.4183 0.4492 0.4441
## 3 3 0.6328 0.7843 0.6516 0.5055 0.5995 0.5891
## 4 4 0.4874 0.6333 0.5316 0.4198 0.4633 0.4566
## 5 5 0.5593 0.6293 0.4791 0.3840 0.4463 0.4364
Tabel di atas memuat RMSE tiap metode pada setiap lipatan, dengan ukuran data uji sekitar 31 titik per lipatan. RMSE antarlipatan bervariasi karena sebagian lipatan kebetulan memuat titik yang lebih sulit diprediksi. Karena itu, perbandingan metode sebaiknya dilihat dari rata-rata dan konsistensi antarlipatan.
Ringkasan RMSE
lab_cv <- c(closest = "Closest Observation",
idw = "IDW",
nn = "Nearest Neighbors (k = 5)",
kriging = "Ordinary Kriging",
ens_equal = "Ensembel (bobot sama)",
ens_weighted = "Ensembel (bobot 1/RMSE)")
rmse_mean <- colMeans(res_rmse[, names(lab_cv)])
rmse_sd <- apply(res_rmse[, names(lab_cv)], 2, sd)
tab <- data.frame(
kode = names(lab_cv),
Metode = unname(lab_cv),
RMSE_rata2 = round(rmse_mean, 4),
SD_antar_fold = round(rmse_sd, 4),
row.names = NULL
)
tab <- tab[order(tab$RMSE_rata2), ]
tab$Peringkat <- seq_len(nrow(tab))
rownames(tab) <- NULL
tab[, c("Peringkat", "Metode", "RMSE_rata2", "SD_antar_fold")]## Peringkat Metode RMSE_rata2 SD_antar_fold
## 1 1 Ordinary Kriging 0.4312 0.0449
## 2 2 Ensembel (bobot 1/RMSE) 0.4748 0.0644
## 3 3 Ensembel (bobot sama) 0.4818 0.0661
## 4 4 Nearest Neighbors (k = 5) 0.5305 0.0704
## 5 5 Closest Observation 0.5822 0.0610
## 6 6 IDW 0.6358 0.0899
res_long <- res_rmse %>%
pivot_longer(-fold, names_to = "kode", values_to = "rmse") %>%
mutate(Metode = factor(lab_cv[kode], levels = tab$Metode))
ggplot() +
geom_col(data = tab %>% mutate(Metode = factor(Metode, levels = Metode)),
aes(Metode, RMSE_rata2), fill = "steelblue", alpha = 0.6) +
geom_point(data = res_long, aes(Metode, rmse),
position = position_jitter(width = 0.08, height = 0), size = 1.8) +
labs(x = NULL, y = "RMSE (skala log(zinc))",
title = "RMSE rata-rata (batang) dan RMSE tiap fold (titik)") +
theme_bw() +
theme(axis.text.x = element_text(angle = 25, hjust = 1))## closest idw nn kriging
## fold1 0.235 0.205 0.257 0.303
## fold2 0.217 0.208 0.270 0.305
## fold3 0.241 0.204 0.257 0.298
## fold4 0.216 0.203 0.260 0.320
## fold5 0.239 0.199 0.258 0.304
## closest idw nn kriging
## 0.230 0.204 0.260 0.306
Interpretasi.
- Metode dengan RMSE rata-rata terendah adalah Ordinary Kriging (0.4312), sedangkan yang tertinggi adalah IDW (0.6358). Selisihnya 32.2% terhadap yang tertinggi.
- Ordinary Kriging lebih akurat dibanding metode deterministik terbaik (RMSE rata-rata 0.4312 vs 0.5305), dan mengungguli ketiga metode deterministik pada 5 dari 5 lipatan.
- Ensembel bobot sama tidak lebih baik dibanding metode tunggal terbaik (0.4818 vs 0.4312). Ensembel bobot 1/RMSE menghasilkan 0.4748.
- Bobot rata-rata pada ensembel 1/RMSE: closest = 0.23, idw = 0.204, nn = 0.26, kriging = 0.306. Karena bobot ini proporsional dengan 1/RMSE, perbedaan antarmetode yang kecil menghasilkan bobot yang hampir sama, sehingga selisih kedua versi ensembel ikut kecil.
- Simpangan baku antarlipatan (kolom
SD_antar_fold) perlu dibandingkan dengan selisih antarmetode: bila selisih rata-rata antara dua metode lebih kecil dari simpangan baku tersebut, urutan keduanya sebaiknya tidak dianggap pasti.
Kesimpulan
- Berdasarkan 5-fold cross-validation pada log(zinc), metode dengan RMSE rata-rata terendah adalah Ordinary Kriging dengan RMSE 0.4312.
- Metode stokastik (Kriging) mengungguli metode deterministik pada rata-rata RMSE. Kriging juga satu-satunya yang menyediakan varians prediksi.
- Ensembel tidak memberi perbaikan dibanding metode tunggal terbaik pada data ini.