Praktikum 10 Interpolasi Titik
Tugas
Buat interpolasi menggunakan data meuse, dengan menggunakan seluruh metode interpolasi, yaitu Closest Observation, NN, IDW, Ensembel (Bebas menggunakan metode interpolasi apa saja yang ingin di ensemble, minimal 3), dan Kriging. setelah itu bandingkan RMSE dengan menggunakan Cross Validation
Persiapan Data
Data yang dipakai adalah meuse (155 titik pengamatan)
dan meuse.grid (3103 lokasi prediksi). Peubah yang
diinterpolasi adalah log(zinc), sama seperti contoh Kriging di
modul.
library(sp)
library(sf)
library(terra)
library(gstat)
library(dplyr)
library(ggplot2)
library(viridis)
data(meuse)
data(meuse.grid)
# titik pengamatan, vble = log(zinc)
d <- st_as_sf(meuse, coords = c("x", "y"), crs = 28992) %>%
transmute(vble = log(zinc))
# lokasi yang akan diprediksi (grid 40 m x 40 m)
coop <- st_as_sf(meuse.grid, coords = c("x", "y"), crs = 28992, remove = FALSE)
# batas wilayah untuk diagram Voronoi
map <- st_sf(geometry = st_buffer(
st_convex_hull(st_union(c(st_geometry(d), st_geometry(coop)))), 100))
c(jumlah_titik = nrow(d), jumlah_lokasi_prediksi = nrow(coop))## jumlah_titik jumlah_lokasi_prediksi
## 155 3103
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 4.727 5.288 5.787 5.886 6.514 7.517
ggplot() +
geom_tile(data = meuse.grid, aes(x, y), fill = "grey90", width = 40, height = 40) +
geom_sf(data = d, aes(color = vble), size = 2) +
scale_color_viridis(name = "log(zinc)") +
theme_bw() + labs(title = "Sebaran titik pengamatan log(zinc)")Titik dengan nilai tinggi dan rendah terlihat mengelompok, artinya ada pola spasial.
Fungsi Prediksi Tiap Metode
Setiap metode dibuat dalam bentuk fungsi dengan input data latih
(train) dan lokasi prediksi (test), supaya
bisa dipakai lagi saat cross validation.
RMSE <- function(observed, predicted) sqrt(mean((observed - predicted)^2))
# 1. Closest Observation: ambil nilai titik terdekat (poligon Voronoi)
closest_obs <- function(train, test) {
v <- st_as_sf(terra::voronoi(terra::vect(train), bnd = terra::vect(map)))
st_crs(v) <- st_crs(train)
p <- st_join(test, v["vble"], join = st_within, suffix = c("", ".v"))
p[[if ("vble.v" %in% names(p)) "vble.v" else "vble"]]
}
# 2. IDW: semua titik ikut, pangkat jarak (idp) = 1
idw_p <- function(train, test) {
g <- gstat(formula = vble ~ 1, locations = train, nmax = nrow(train),
set = list(idp = 1, debug = 0))
predict(g, test)$var1.pred
}
# 3. Nearest Neighbors: rata-rata 5 titik terdekat, bobot sama (idp = 0)
nn_p <- function(train, test) {
g <- gstat(formula = vble ~ 1, locations = train, nmax = 5,
set = list(idp = 0, debug = 0))
predict(g, test)$var1.pred
}
# 4. Ordinary Kriging: variogram spherical, nilai awal sama seperti di modul
fit_vgm <- function(train) {
v <- variogram(vble ~ 1, train)
fit.variogram(v, vgm(psill = 0.5, model = "Sph", range = 900, nugget = 0.1))
}
kr_p <- function(train, test) {
g <- gstat(formula = vble ~ 1, data = train, model = fit_vgm(train),
set = list(debug = 0))
predict(g, test)$var1.pred
}Closest Observation
Nilai prediksi diambil dari titik pengamatan yang paling dekat, dengan bantuan poligon Voronoi.
v <- st_as_sf(terra::voronoi(terra::vect(d), bnd = terra::vect(map)))
st_crs(v) <- st_crs(d)
ggplot() +
geom_sf(data = v, aes(fill = vble), alpha = 0.7, color = "white", linewidth = 0.1) +
geom_sf(data = d, size = 0.6) +
scale_fill_viridis(name = "log(zinc)") +
theme_bw() + labs(title = "Diagram Voronoi")Inverse Distance Weighting (IDW)
Bobot berbanding terbalik dengan jarak (idp = 1) dan
semua titik pengamatan dipakai. Petanya ada di bagian Peta Hasil
Interpolasi.
Nearest Neighbors (NN)
Prediksi adalah rata-rata 5 titik terdekat dengan bobot sama
(nmax = 5, idp = 0). Petanya juga ada di
bagian Peta Hasil Interpolasi.
Kriging
Semivariogram
Semivariogram empiris dihitung dulu dari data, lalu dicocokkan dengan
model spherical. Nilai awalnya sama dengan modul
(psill = 0.5, range = 900,
nugget = 0.1).
## model psill range
## 1 Nug 0.05066017 0.0000
## 2 Sph 0.59060556 897.0044
Hasilnya nugget 0.051, partial sill 0.591, dan range 897 meter.
Prediksi dan Varians
k <- gstat(formula = vble ~ 1, data = d, model = fv, set = list(debug = 0))
kpred <- predict(k, coop)
ggplot() + geom_sf(data = kpred, aes(color = var1.pred)) + geom_sf(data = d, size = 0.5) +
scale_color_viridis(name = "log(zinc)") + theme_bw() +
labs(title = "Prediksi Ordinary Kriging")ggplot() + geom_sf(data = kpred, aes(color = var1.var)) + geom_sf(data = d, size = 0.5) +
scale_color_viridis(name = "varians") + theme_bw() +
labs(title = "Varians Kriging")Varians paling kecil di sekitar titik pengamatan dan makin besar di area yang jauh dari titik.
Ensembel
Ada dua ensembel:
- Ensembel A: rata-rata Closest Observation, IDW, dan NN dengan bobot sama (1/3 masing-masing).
- Ensembel B: Closest Observation, IDW, NN, dan Kriging dengan bobot invers RMSE (RMSE makin kecil, bobot makin besar).
Bobot Ensembel B dihitung dari cross validation di dalam data latih, bukan dari data uji seperti di modul, supaya data uji tidak ikut menentukan bobot.
metode <- function(train, test) {
data.frame(closest = closest_obs(train, test), idw = idw_p(train, test),
nn = nn_p(train, test), krig = kr_p(train, test))
}
# bobot invers RMSE dari 5-fold CV di dalam data latih
bobot_cv <- function(train, k = 5) {
kf <- dismo::kfold(nrow(train), k)
hasil <- do.call(rbind, lapply(1:k, function(i) {
te <- train[kf == i, ]
cbind(obs = te$vble, metode(train[kf != i, ], te))
}))
r <- sapply(hasil[, -1], function(p) RMSE(hasil$obs, p))
w <- 1 / r
w / sum(w)
}Peta Hasil Interpolasi
Peta dibuat dari seluruh 155 titik pengamatan.
set.seed(123)
w_all <- bobot_cv(d)
P <- metode(d, coop)
P$ens_sama <- (P$closest + P$idw + P$nn) / 3
P$ens_bobot <- as.numeric(as.matrix(P[, c("closest", "idw", "nn", "krig")]) %*% w_all)
label <- c(closest = "Closest Observation", idw = "IDW", nn = "NN (k = 5)",
ens_sama = "Ensembel A (bobot sama)", krig = "Kriging",
ens_bobot = "Ensembel B (bobot RMSE)")
urut <- c("closest", "idw", "nn", "ens_sama", "krig", "ens_bobot")
peta <- do.call(rbind, lapply(urut, function(m)
data.frame(x = coop$x, y = coop$y, pred = P[[m]], metode = label[m])))
peta$metode <- factor(peta$metode, levels = label[urut])
ggplot(peta, aes(x, y, fill = pred)) + geom_tile(width = 40, height = 40) +
facet_wrap(~metode, ncol = 3) + coord_equal() +
scale_fill_viridis(name = "log(zinc)") + theme_bw() +
theme(axis.text = element_blank(), axis.ticks = element_blank(),
axis.title = element_blank(), strip.text = element_text(size = 8))Bobot Ensembel B pada peta di atas:
knitr::kable(data.frame(Metode = c("Closest Observation", "IDW", "NN", "Kriging"),
Bobot = round(w_all, 3)), row.names = FALSE)| Metode | Bobot |
|---|---|
| Closest Observation | 0.229 |
| IDW | 0.209 |
| NN | 0.251 |
| Kriging | 0.310 |
Cross Validation
Data dibagi menjadi 5 lipatan dengan set.seed(123). Pada
tiap lipatan, model dibangun dari 4 lipatan lalu dipakai memprediksi 1
lipatan sisanya, kemudian RMSE dihitung. Variogram Kriging dicocokkan
ulang pada setiap data latih.
cv_run <- function(seed, k = 5) {
set.seed(seed)
kf <- dismo::kfold(nrow(d), k)
do.call(rbind, lapply(1:k, function(i) {
tr <- d[kf != i, ]; te <- d[kf == i, ]
p <- metode(tr, te)
w <- bobot_cv(tr)
p$ens_sama <- (p$closest + p$idw + p$nn) / 3
p$ens_bobot <- as.numeric(as.matrix(p[, c("closest", "idw", "nn", "krig")]) %*% w)
r <- sapply(p[urut], function(x) RMSE(te$vble, x))
data.frame(fold = i, t(r))
}))
}
hasil <- cv_run(123)
names(hasil)[-1] <- label[urut]
knitr::kable(hasil, digits = 4, row.names = FALSE, caption = "RMSE tiap lipatan (skala log)")| fold | Closest Observation | IDW | NN (k = 5) | Ensembel A (bobot sama) | Kriging | Ensembel B (bobot RMSE) |
|---|---|---|---|---|---|---|
| 1 | 0.6327 | 0.5496 | 0.4958 | 0.4707 | 0.4281 | 0.4476 |
| 2 | 0.5987 | 0.5828 | 0.4943 | 0.4733 | 0.4183 | 0.4441 |
| 3 | 0.6328 | 0.7843 | 0.6516 | 0.6379 | 0.5055 | 0.5891 |
| 4 | 0.4874 | 0.6333 | 0.5316 | 0.4915 | 0.4198 | 0.4566 |
| 5 | 0.5593 | 0.6293 | 0.4791 | 0.4820 | 0.3840 | 0.4364 |
rata <- colMeans(hasil[, -1])
tab <- data.frame(Metode = names(rata), RMSE_rata_rata = round(rata, 4),
Peringkat = rank(rata), row.names = NULL)
knitr::kable(tab[order(tab$Peringkat), ], row.names = FALSE,
caption = "Rata-rata RMSE 5 lipatan")| Metode | RMSE_rata_rata | Peringkat |
|---|---|---|
| Kriging | 0.4312 | 1 |
| Ensembel B (bobot RMSE) | 0.4748 | 2 |
| Ensembel A (bobot sama) | 0.5111 | 3 |
| NN (k = 5) | 0.5305 | 4 |
| Closest Observation | 0.5822 | 5 |
| IDW | 0.6358 | 6 |
tab$Metode <- factor(tab$Metode, levels = tab$Metode[order(tab$RMSE_rata_rata)])
ggplot(tab, aes(Metode, RMSE_rata_rata, fill = Metode)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = sprintf("%.3f", RMSE_rata_rata)), vjust = -0.4) +
scale_fill_viridis_d() + theme_bw() +
labs(y = "RMSE rata-rata", x = NULL) +
theme(axis.text.x = element_text(angle = 20, hjust = 1))Ulang dengan Seed Lain
Pembagian lipatan bersifat acak, jadi 5-fold CV diulang dengan seed 1 sampai 20 untuk melihat apakah urutan metodenya berubah.
ulang <- t(sapply(1:20, function(s) colMeans(cv_run(s)[, -1])))
colnames(ulang) <- label[urut]
ringkas <- data.frame(
Metode = colnames(ulang),
Rata_rata = apply(ulang, 2, mean),
SD = apply(ulang, 2, sd),
Terbaik = as.integer(table(factor(colnames(ulang)[apply(ulang, 1, which.min)],
levels = colnames(ulang)))),
row.names = NULL)
knitr::kable(ringkas[order(ringkas$Rata_rata), ], digits = 4, row.names = FALSE,
caption = "RMSE rata-rata dari 20 ulangan 5-fold CV (Terbaik = jumlah ulangan dengan RMSE terkecil)")| Metode | Rata_rata | SD | Terbaik |
|---|---|---|---|
| Kriging | 0.4041 | 0.0127 | 20 |
| Ensembel B (bobot RMSE) | 0.4531 | 0.0152 | 0 |
| NN (k = 5) | 0.4881 | 0.0140 | 0 |
| Ensembel A (bobot sama) | 0.4928 | 0.0168 | 0 |
| Closest Observation | 0.5699 | 0.0290 | 0 |
| IDW | 0.6385 | 0.0067 | 0 |
Kesimpulan
Kriging memiliki RMSE terkecil, yaitu 0.431 pada seed 123, dan terbaik pada 20 dari 20 ulangan seed. Urutan dari RMSE terkecil: Kriging (0.431), Ensembel B (bobot RMSE) (0.475), Ensembel A (bobot sama) (0.511), NN (k = 5) (0.530), Closest Observation (0.582), IDW (0.636).
IDW memiliki RMSE paling besar karena semua titik ikut dihitung, sehingga prediksinya mendekati rata-rata dan variasi lokal tidak tertangkap (terlihat juga pada petanya). Closest Observation dan NN lebih baik karena hanya memakai titik di sekitar lokasi.
Ensembel A lebih baik daripada Closest Observation dan IDW, tetapi dibanding NN hasilnya tidak konsisten. Pada seed 123 Ensembel A lebih kecil, sedangkan pada rata-rata 20 ulangan NN (0.488) lebih kecil daripada Ensembel A (0.493). Ensembel B tidak mengalahkan Kriging karena sekitar 69% bobotnya ada pada tiga metode lain yang RMSE-nya lebih besar.
Semua RMSE dihitung pada skala log.