# install.packages("gifski") # untuk membuat GIF
library(gifski)
## Warning: package 'gifski' was built under R version 4.4.3
# GIF disimpan di folder working directory
OUT_DIR <- "hasil_gif"
dir.create(OUT_DIR, showWarnings = FALSE)
cat("GIF disimpan di:", normalizePath(OUT_DIR), "\n")
## GIF disimpan di: E:\Kampus\SMT5\Pembelajaran Mesin Statistika\Presentasi_grid based & mode seeking clustering\hasil_gif
# 1. Dataset dan preprocessing
# Data Gaussian Blobs
set.seed(42)
n_per_kelompok <- c(334, 333, 333)
pusat_asli <- rbind(c( 4.7, 2.0),
c(-2.6, 8.9),
c(-6.8, -6.8))
X <- do.call(rbind, lapply(1:3, function(k) {
cbind(rnorm(n_per_kelompok[k], mean = pusat_asli[k, 1], sd = 1),
rnorm(n_per_kelompok[k], mean = pusat_asli[k, 2], sd = 1))
}))
colnames(X) <- c("Fitur1", "Fitur2")
y_asli <- rep(1:3, times = n_per_kelompok)
warna_kelompok <- hsv(h = c(0, 1/3, 2/3), s = 0.45, v = 1)
# Ukuran dan struktur dataset
cat("\n=== 1.1 Ukuran dan struktur ===\n")
##
## === 1.1 Ukuran dan struktur ===
cat("Dimensi (baris x kolom):", dim(X), "\n")
## Dimensi (baris x kolom): 1000 2
cat("Kelas objek:", class(X), "| tipe data:", typeof(X), "\n")
## Kelas objek: matrix array | tipe data: double
str(X)
## num [1:1000, 1:2] 6.07 4.14 5.06 5.33 5.1 ...
## - attr(*, "dimnames")=List of 2
## ..$ : NULL
## ..$ : chr [1:2] "Fitur1" "Fitur2"
print(head(X))
## Fitur1 Fitur2
## [1,] 6.070958 2.9885968
## [2,] 4.135302 1.9265417
## [3,] 5.063128 0.6129734
## [4,] 5.332863 0.6933241
## [5,] 5.104268 1.2316047
## [6,] 4.593875 1.4728919
print(table(Kelompok_Asli = y_asli))
## Kelompok_Asli
## 1 2 3
## 334 333 333
# 1.2 Eksplorasi sederhana (EDA)
cat("\n=== 1.2 EDA ===\n")
##
## === 1.2 EDA ===
print(summary(X))
## Fitur1 Fitur2
## Min. :-9.414 Min. :-9.150
## 1st Qu.:-6.137 1st Qu.:-6.087
## Median :-2.608 Median : 1.983
## Mean :-1.581 Mean : 1.357
## 3rd Qu.: 4.045 3rd Qu.: 8.255
## Max. : 7.402 Max. :11.534
cat("\nSimpangan baku:\n"); print(round(apply(X, 2, sd), 3))
##
## Simpangan baku:
## Fitur1 Fitur2
## 4.851 6.501
cat("\nRentang (maks - min):\n"); print(round(apply(X, 2, function(v) diff(range(v))), 3))
##
## Rentang (maks - min):
## Fitur1 Fitur2
## 16.816 20.684
cat("\nKorelasi antarfitur:\n"); print(round(cor(X), 3))
##
## Korelasi antarfitur:
## Fitur1 Fitur2
## Fitur1 1.000 0.409
## Fitur2 0.409 1.000
# Rata-rata dan simpangan baku per kelompok
ringkas_kelompok <- aggregate(as.data.frame(X), by = list(Kelompok = y_asli),
FUN = function(v) c(mean = mean(v), sd = sd(v)))
cat("\nRata-rata dan sd per kelompok asli:\n"); print(ringkas_kelompok)
##
## Rata-rata dan sd per kelompok asli:
## Kelompok Fitur1.mean Fitur1.sd Fitur2.mean Fitur2.sd
## 1 1 4.6777384 0.9629144 1.9885700 1.0163206
## 2 2 -2.6367759 1.0365894 8.8876647 0.9848974
## 3 3 -6.8022651 0.9626058 -6.8083516 1.0063183
# Visualisasi:
par(mfrow = c(1, 3), mar = c(4, 4, 3, 1))
hist(X[, 1], breaks = 30, col = "lightblue", border = "white",
main = "Histogram Fitur 1", xlab = "Fitur 1")
hist(X[, 2], breaks = 30, col = "lightblue", border = "white",
main = "Histogram Fitur 2", xlab = "Fitur 2")
boxplot(X, col = "lightblue", main = "Boxplot kedua fitur")

par(mfrow = c(1, 1), mar = c(5, 4, 4, 2) + 0.1)
plot(X[, 1], X[, 2], pch = 21, bg = warna_kelompok[y_asli], col = "gray40", cex = 1.1,
xlab = "Fitur 1", ylab = "Fitur 2", main = "Scatter plot Gaussian Blobs")
grid(col = "gray90")
legend("topright", legend = paste("Kelompok", 1:3), pch = 21, pt.bg = warna_kelompok,
col = "gray40", bty = "n")

# 1.3 Missing values, duplikat, dan nilai tak hingga
cat("\n=== 1.3 Pemeriksaan data ===\n")
##
## === 1.3 Pemeriksaan data ===
cat("Missing value per kolom:\n"); print(colSums(is.na(X)))
## Missing value per kolom:
## Fitur1 Fitur2
## 0 0
cat("Total missing value :", sum(is.na(X)), "\n")
## Total missing value : 0
cat("Nilai tak hingga :", sum(!is.finite(X[!is.na(X)])), "\n")
## Nilai tak hingga : 0
cat("Baris duplikat :", sum(duplicated(X)), "\n")
## Baris duplikat : 0
# 1.4 Distribusi: kemencengan, normalitas, pencilan
cat("\n=== 1.4 Distribusi ===\n")
##
## === 1.4 Distribusi ===
kemencengan <- function(v) mean((v - mean(v))^3) / mean((v - mean(v))^2)^1.5
cat("Kemencengan (skewness):", round(apply(X, 2, kemencengan), 3), "\n")
## Kemencengan (skewness): 0.298 -0.143
# Shapiro-Wilk: data gabungan vs per kelompok
cat("Shapiro-Wilk data gabungan, p-value:",
signif(apply(X, 2, function(v) shapiro.test(v)$p.value), 3), "\n")
## Shapiro-Wilk data gabungan, p-value: 7.8e-26 1.8e-26
p_kelompok <- sapply(1:3, function(k) apply(X[y_asli == k, ], 2,
function(v) shapiro.test(v)$p.value))
colnames(p_kelompok) <- paste("Kelompok", 1:3)
cat("Shapiro-Wilk per kelompok, p-value:\n"); print(round(p_kelompok, 3))
## Shapiro-Wilk per kelompok, p-value:
## Kelompok 1 Kelompok 2 Kelompok 3
## Fitur1 0.921 0.533 0.612
## Fitur2 0.732 0.913 0.412
# Pencilan: aturan 1,5 x IQR
jumlah_pencilan <- apply(X, 2, function(v) {
q <- quantile(v, c(0.25, 0.75)); iqr <- q[2] - q[1]
sum(v < q[1] - 1.5 * iqr | v > q[2] + 1.5 * iqr)
})
cat("Pencilan (aturan 1,5 x IQR):", jumlah_pencilan, "\n")
## Pencilan (aturan 1,5 x IQR): 0 0
# 1.5 Skala fitur
cat("\n=== 1.5 Skala fitur ===\n")
##
## === 1.5 Skala fitur ===
rasio_sd <- sd(X[, 1]) / sd(X[, 2])
cat("Rasio sd Fitur1 / Fitur2:", round(rasio_sd, 3), "\n")
## Rasio sd Fitur1 / Fitur2: 0.746
# 1.6 Preprocessing
cat("\n=== 1.6 Preprocessing ===\n")
##
## === 1.6 Preprocessing ===
# (a) Pembersihan: buang baris tidak lengkap
lengkap <- complete.cases(X)
X <- X[lengkap, , drop = FALSE]
y_asli <- y_asli[lengkap]
cat("Baris dibuang karena missing value:", sum(!lengkap), "\n")
## Baris dibuang karena missing value: 0
# (b) Skala: Mean Shift memakai satu bandwidth h untuk semua dimensi (jarak Euclid),
if (rasio_sd > 0.5 && rasio_sd < 2) {
cat("Skala sebanding -> standarisasi tidak diperlukan, data dipakai apa adanya\n")
} else {
cat("Skala berbeda mencolok -> pertimbangkan X <- scale(X) (h dan batas plot ikut disesuaikan)\n")
}
## Skala sebanding -> standarisasi tidak diperlukan, data dipakai apa adanya
# (c) Format: matriks numerik, baris = titik, kolom = fitur
stopifnot(is.matrix(X), is.numeric(X))
n <- nrow(X)
d <- ncol(X)
cat("Data siap dipakai: n =", n, "| d =", d, "\n")
## Data siap dipakai: n = 1000 | d = 2
# 2. rumus
# Rumus 7.1: estimasi kepadatan kernel
# f_K(x) = 1/(n h^d) * sum K((x - x_i)/h)
K_gauss <- function(U) {
# Kernel Gaussian ternormalisasi: (2*pi)^(-d/2) * exp(-|u|^2 / 2)
if (is.null(dim(U))) U <- matrix(U, nrow = 1)
dim_u <- ncol(U)
(2 * pi)^(-dim_u / 2) * exp(-0.5 * rowSums(U^2))
}
f_K <- function(x, data, h) {
U <- sweep(data, 2, x) / h
sum(K_gauss(U)) / (nrow(data) * h^ncol(data))
}
h0 <- 1.9
x0 <- colMeans(X)
rumus_71 <- f_K(x0, X, h0)
pembanding <- mean(dnorm(x0[1], X[, 1], h0) * dnorm(x0[2], X[, 2], h0))
cat("\nx0 =", round(x0, 4), "\n")
##
## x0 = -1.5808 1.3566
cat(sprintf("f_K(x0) rumus 7.1 = %.8f\n", rumus_71))
## f_K(x0) rumus 7.1 = 0.00017042
cat(sprintf("f(x0) pembanding = %.8f\n", pembanding))
## f(x0) pembanding = 0.00017042
cat("sama:", isTRUE(all.equal(rumus_71, pembanding)), "\n")
## sama: TRUE
# syarat kernel dan kernel Gaussian
# kernel Gaussian sebagai 1/(2*pi^(d/2)) * exp(-|x|^2/2).
# Bentuk ternormalisasi untuk semua dimensi: 1/(2*pi)^(d/2) * exp(-|x|^2/2).
K_buku <- function(U) {
if (is.null(dim(U))) U <- matrix(U, nrow = 1)
dim_u <- ncol(U)
1 / (2 * pi^(dim_u / 2)) * exp(-0.5 * rowSums(U^2))
}
# Syarat integral K = 1 (jumlah Riemann pada grid)
g1 <- matrix(seq(-10, 10, length.out = 4001), ncol = 1)
a <- seq(-8, 8, length.out = 801)
g2 <- as.matrix(expand.grid(a, a))
dx <- g1[2, 1] - g1[1, 1]
da <- a[2] - a[1]
hasil72 <- data.frame(
dimensi = c("d = 1", "d = 2", "d = 3 (analitik)"),
integral_K_gauss = c(sum(K_gauss(g1)) * dx,
sum(K_gauss(g2)) * da^2,
1),
integral_K_buku = c(sum(K_buku(g1)) * dx,
sum(K_buku(g2)) * da^2,
2^(3 / 2 - 1))
)
cat("\nSyarat 7.2 (integral kernel):\n")
##
## Syarat 7.2 (integral kernel):
print(format(hasil72, digits = 4), row.names = FALSE)
## dimensi integral_K_gauss integral_K_buku
## d = 1 1 0.7071
## d = 2 1 1.0000
## d = 3 (analitik) 1 1.4142
# Syarat K(x) = K(|x|), nilai kernel hanya bergantung pada jarak dari pusat
x <- c(1.3, -0.7)
t_putar <- 40 * pi / 180
R_putar <- matrix(c(cos(t_putar), sin(t_putar),
-sin(t_putar), cos(t_putar)), nrow = 2)
cat("\nK(x) =", K_gauss(x), "\n")
##
## K(x) = 0.05351052
cat("K(-x) =", K_gauss(-x), "\n")
## K(-x) = 0.05351052
cat("K(Rx) dgn |Rx|=|x| =", K_gauss(as.numeric(R_putar %*% x)), "\n")
## K(Rx) dgn |Rx|=|x| = 0.05351052
cat("radial:", isTRUE(all.equal(c(K_gauss(-x), K_gauss(as.numeric(R_putar %*% x))),
rep(K_gauss(x), 2))), "\n")
## radial: TRUE
# Rumus: gradien dan vektor pergeseran ---------
# kernel Gaussian K(x) = k(|x|^2)
c_k <- (2 * pi)^(-d / 2)
x0 <- c(1, 1)
h0 <- 1.9
s <- rowSums((sweep(X, 2, x0) / h0)^2)
g <- 0.5 * c_k * exp(-s / 2)
# Rumus vektor pergeseran m(x)
m <- colSums(g * X) / sum(g) - x0
# Rumus gradien dari rumus
grad_rumus <- 2 / (n * h0^(d + 2)) * sum(g) * m
# Gradien numerik dari f_K
eps <- 1e-5
grad_num <- sapply(1:d, function(j) {
e <- numeric(d); e[j] <- eps
(f_K(x0 + e, X, h0) - f_K(x0 - e, X, h0)) / (2 * eps)
})
kosinus <- sum(grad_rumus * grad_num) /
(sqrt(sum(grad_rumus^2)) * sqrt(sum(grad_num^2)))
cat("\nm(x0) =", round(m, 6), "\n")
##
## m(x0) = 2.936148 0.821226
cat("gradien dari rumus 7.5 =", grad_rumus, "\n")
## gradien dari rumus 7.5 = 0.001886652 0.0005276867
cat("gradien numerik =", grad_num, "\n")
## gradien numerik = 0.001886652 0.0005276867
cat(sprintf("kemiripan arah (cosine) = %.8f\n", kosinus))
## kemiripan arah (cosine) = 1.00000000
cat("sama:", isTRUE(all.equal(as.numeric(grad_rumus), as.numeric(grad_num), tolerance = 1e-4)), "\n")
## sama: TRUE
# Pembaruan: x_baru = x + m(x)
x_baru <- x0 + m
w <- exp(-rowSums(sweep(X, 2, x0)^2) / (2 * h0^2))
x_kode <- colSums(w * X) / sum(w)
cat("\nx_baru = x + m(x) =", round(x_baru, 6), "\n")
##
## x_baru = x + m(x) = 3.936148 1.821226
cat("rata-rata berbobot kode =", round(x_kode, 6), "\n")
## rata-rata berbobot kode = 3.936148 1.821226
cat("sama:", isTRUE(all.equal(x_baru, x_kode)), "\n")
## sama: TRUE
# 3. Mean Shift kernel Gaussian
mean_shift_riwayat <- function(data, h, max_iter = 150, tol = 1e-4) {
P <- data
riwayat <- list(P) # riwayat[[1]] = iterasi ke-0
for (it in 1:max_iter) {
d2 <- outer(P[, 1], data[, 1], "-")^2 + outer(P[, 2], data[, 2], "-")^2
w <- exp(-d2 / (2 * h^2))
P_baru <- (w %*% data) / rowSums(w)
geser <- max(abs(P_baru - P))
P <- P_baru
riwayat[[length(riwayat) + 1]] <- P
if (geser < tol) break
}
riwayat
}
# 4. Persiapan animasi untuk satu nilai h
batas_x <- c(-11, 8.5)
batas_y <- c(-12, 14)
siapkan_animasi <- function(h, max_iter = 150) {
riwayat_asli <- mean_shift_riwayat(X, h, max_iter = max_iter)
akhir <- riwayat_asli[[length(riwayat_asli)]]
iterasi <- length(riwayat_asli) - 1
# frame antara agar gerakan halus bila konvergen cepat
sub <- if (length(riwayat_asli) < 20) 4 else 1
riwayat <- list()
for (i in 1:(length(riwayat_asli) - 1)) {
a_ <- riwayat_asli[[i]]; b_ <- riwayat_asli[[i + 1]]
for (j in 0:(sub - 1)) riwayat[[length(riwayat) + 1]] <- a_ + (b_ - a_) * j / sub
}
riwayat[[length(riwayat) + 1]] <- akhir
nf <- length(riwayat)
# label cluster: single linkage, dipotong pada jarak 0.2
label <- cutree(hclust(dist(akhir), method = "single"), h = 0.2)
k <- max(label)
# warna
hue <- seq(0, 1, length.out = k + 1)[-(k + 1)]
pastel <- hsv(h = hue, s = 0.45, v = 1)
tua <- hsv(h = hue, s = 0.85, v = 0.8)
warna <- pastel[label]
pusat <- t(sapply(1:k, function(cl) colMeans(akhir[label == cl, , drop = FALSE])))
if (k == 1) pusat <- matrix(pusat, nrow = 1)
colnames(pusat) <- c("Fitur1", "Fitur2")
# permukaan kepadata
gx <- seq(batas_x[1], batas_x[2], length.out = 130)
gy <- seq(batas_y[1], batas_y[2], length.out = 130)
ex <- exp(-outer(gx, X[, 1], "-")^2 / (2 * h^2))
ey <- exp(-outer(gy, X[, 2], "-")^2 / (2 * h^2))
kde <- ex %*% t(ey) # baris = gx, kolom = gy
# lintasan setiap titik ke-12
trail_idx <- seq(1, n, by = 12)
jx <- sapply(riwayat, function(P) P[trail_idx, 1])
jy <- sapply(riwayat, function(P) P[trail_idx, 2])
# frame yang ditampilkan
langkah <- max(1, ceiling(nf / 45))
frames <- sort(unique(c(seq(0, nf - 1, by = langkah), nf - 1)))
frames <- c(frames, rep(nf - 1, 10))
list(h = h, riwayat = riwayat, nf = nf, sub = sub, iterasi = iterasi,
k = k, warna = warna, pusat = pusat, tua = tua,
gx = gx, gy = gy, kde = kde, jx = jx, jy = jy, frames = frames)
}
# Menggambar satu frame
gambar_frame <- function(ob, f) {
par(mar = c(4.2, 4.2, 3.6, 1))
terakhir <- (f == ob$nf - 1)
judul <- paste0("Mean Shift (kernel Gaussian), h = ", ob$h,
"\nIterasi ke-", min(f %/% ob$sub, ob$iterasi),
if (terakhir) paste0(" | ", ob$k, " cluster") else "")
plot(NA, xlim = batas_x, ylim = batas_y, xlab = "Fitur 1", ylab = "Fitur 2",
main = judul, font.main = 2, cex.main = 1)
# kontur kepadatan
tingkat <- seq(min(ob$kde), max(ob$kde), length.out = 15)
.filled.contour(ob$gx, ob$gy, ob$kde, levels = tingkat,
col = adjustcolor(colorRampPalette(c("#f7fbff", "#08306b"))(14),
alpha.f = 0.35))
contour(ob$gx, ob$gy, ob$kde, levels = tingkat, add = TRUE, drawlabels = FALSE,
col = adjustcolor("white", 0.6), lwd = 0.4)
# data asli (diam) dan lintasan
points(X[, 1], X[, 2], pch = 16, cex = 0.5, col = "#bdbdbd")
if (f >= 1) {
matlines(t(ob$jx[, 1:(f + 1)]), t(ob$jy[, 1:(f + 1)]),
lty = 1, lwd = 0.6, col = adjustcolor("gray40", 0.55))
}
# titik yang bergeser
P <- ob$riwayat[[f + 1]]
points(P[, 1], P[, 2], pch = 21, bg = ob$warna, col = "#555555", cex = 0.9, lwd = 0.5)
isi_legend <- c("Data asli (diam)", "Titik yang bergeser")
pch_l <- c(16, 21); bg_l <- c(NA, "gray85"); col_l <- c("#bdbdbd", "#555555")
if (terakhir) {
points(ob$pusat[, 1], ob$pusat[, 2], pch = 23, bg = ob$tua, col = "black",
cex = 2.2, lwd = 1.4)
isi_legend <- c(isi_legend, "Pusat cluster")
pch_l <- c(pch_l, 23); bg_l <- c(bg_l, "gray50"); col_l <- c(col_l, "black")
}
legend("topright", legend = isi_legend, pch = pch_l, pt.bg = bg_l, col = col_l,
cex = 0.75, bg = adjustcolor("white", 0.85))
}
# 5. Fungsi pembuat GIF
buat_gif <- function(h, nama_file, max_iter = 150) {
ob <- siapkan_animasi(h, max_iter)
save_gif({
for (f in ob$frames) gambar_frame(ob, f)
}, gif_file = nama_file, width = 648, height = 558,
delay = 1 / 7, res = 90, progress = FALSE)
ob
}
# 6. BAGIAN A: Mean Shift untuk h = 1.9 (dijalankan lebih dulu)
H_UTAMA <- 1.9
MAX_ITER <- 150
path_utama <- file.path(OUT_DIR, paste0("meanshift_h", format(H_UTAMA, nsmall = 1), ".gif"))
ob_utama <- buat_gif(H_UTAMA, path_utama, max_iter = MAX_ITER)
ob_utama$path <- path_utama
hasil_utama <- list()
hasil_utama[[as.character(H_UTAMA)]] <- ob_utama
cat(sprintf("selesai h = %s: %d iterasi, %d cluster\n", H_UTAMA, ob_utama$iterasi, ob_utama$k))
## selesai h = 1.9: 8 iterasi, 3 cluster
# Ringkasan hasil h = 1.9
ringkas_utama <- data.frame(
h = H_UTAMA,
iterasi = ob_utama$iterasi,
jumlah_cluster = ob_utama$k,
konvergen = if (ob_utama$iterasi < MAX_ITER) "ya" else "belum (batas iterasi)",
row.names = NULL
)
print(ringkas_utama)
## h iterasi jumlah_cluster konvergen
## 1 1.9 8 3 ya
cat(sprintf("\nh = %s: %d iterasi, %d cluster\n", ob_utama$h, ob_utama$iterasi, ob_utama$k))
##
## h = 1.9: 8 iterasi, 3 cluster
if (ob_utama$k <= 6) {
cat("pusat cluster:\n")
print(round(ob_utama$pusat, 3))
}
## pusat cluster:
## Fitur1 Fitur2
## [1,] 4.681 1.979
## [2,] -2.644 8.894
## [3,] -6.799 -6.830
par(mfrow = c(1, 1))
gambar_frame(ob_utama, ob_utama$nf - 1)

# GIF yang bergerak untuk h = 1.9
# Fungsi menampilkan GIF yang BERGERAK
tampilkan_gif <- function(daftar_path) {
tmp <- file.path(tempdir(), "gif_meanshift")
dir.create(tmp, showWarnings = FALSE)
file.copy(daftar_path, tmp, overwrite = TRUE)
img <- sprintf('<div style="width:49%%;display:inline-block"><img src="%s" style="width:100%%"></div>',
basename(daftar_path))
html <- file.path(tmp, "index.html")
writeLines(c("<html><body style='margin:4px;font-family:sans-serif'>", img,
"</body></html>"), html)
viewer <- getOption("viewer")
if (!is.null(viewer)) viewer(html) else browseURL(html)
}
tampilkan_gif(path_utama)
# 7. BAGIAN B: Mean Shift untuk h = 0.3, 0.4, 1.9, 6.0 (perbandingan, butuh beberapa menit)
H_LIST <- c(0.3, 0.4, 1.9, 6.0)
hasil <- list()
for (h in H_LIST) {
if (h == H_UTAMA) {
ob <- ob_utama # h = 1.9 sudah dihitung di bagian A
} else {
path <- file.path(OUT_DIR, paste0("meanshift_h", format(h, nsmall = 1), ".gif"))
ob <- buat_gif(h, path, max_iter = MAX_ITER)
ob$path <- path
}
hasil[[as.character(h)]] <- ob
cat(sprintf("selesai h = %s: %d iterasi, %d cluster\n", h, ob$iterasi, ob$k))
}
## selesai h = 0.3: 140 iterasi, 17 cluster
## selesai h = 0.4: 69 iterasi, 5 cluster
## selesai h = 1.9: 8 iterasi, 3 cluster
## selesai h = 6: 50 iterasi, 1 cluster
# Ringkasan hasil semua h
ringkas <- data.frame(
h = H_LIST,
iterasi = sapply(hasil, function(v) v$iterasi),
jumlah_cluster = sapply(hasil, function(v) v$k),
konvergen = sapply(hasil, function(v)
if (v$iterasi < MAX_ITER) "ya" else "belum (batas iterasi)"),
row.names = NULL
)
print(ringkas)
## h iterasi jumlah_cluster konvergen
## 1 0.3 140 17 ya
## 2 0.4 69 5 ya
## 3 1.9 8 3 ya
## 4 6.0 50 1 ya
# Hasil setiap h
for (v in hasil) {
cat(sprintf("\nh = %s: %d iterasi, %d cluster\n", v$h, v$iterasi, v$k))
if (v$k <= 6) {
cat("pusat cluster:\n")
print(round(v$pusat, 3))
}
}
##
## h = 0.3: 140 iterasi, 17 cluster
##
## h = 0.4: 69 iterasi, 5 cluster
## pusat cluster:
## Fitur1 Fitur2
## [1,] 4.531 1.800
## [2,] 4.773 4.948
## [3,] -2.726 8.971
## [4,] -7.754 -3.223
## [5,] -6.891 -6.862
##
## h = 1.9: 8 iterasi, 3 cluster
## pusat cluster:
## Fitur1 Fitur2
## [1,] 4.681 1.979
## [2,] -2.644 8.894
## [3,] -6.799 -6.830
##
## h = 6: 50 iterasi, 1 cluster
## pusat cluster:
## Fitur1 Fitur2
## [1,] 0.823 4.598
par(mfrow = c(2, 2))
for (v in hasil) gambar_frame(v, v$nf - 1)

par(mfrow = c(1, 1))
# GIF yang bergerak untuk semua h
tampilkan_gif(sapply(hasil, function(v) v$path))