# =============================================================================
# 1. FUNGSI
# =============================================================================

# --- kernel dan KDE ---
kernel_gaussian <- function(r2, d) (2 * pi)^(-d / 2) * exp(-0.5 * r2)
bobot_kernel    <- function(r2) exp(-0.5 * r2)

jarak2 <- function(A, B) {
  pmax(outer(rowSums(A^2), rowSums(B^2), "+") - 2 * A %*% t(B), 0)
}

bandwidth_kuantil <- function(X, q = 0.3) as.numeric(quantile(dist(X), q))

kde <- function(x, X, h) {
  n <- nrow(X); d <- ncol(X)
  r2 <- colSums((t(X) - x)^2) / h^2
  sum(kernel_gaussian(r2, d)) / (n * h^d)
}

kde_grid <- function(X, gx, gy, h) {
  n <- nrow(X); d <- ncol(X)
  sisa <- if (d > 2) rowSums(X[, -(1:2), drop = FALSE]^2) else rep(0, n)
  Kx <- exp(-0.5 * outer(gx, X[, 1], "-")^2 / h^2)
  Ky <- exp(-0.5 * outer(gy, X[, 2], "-")^2 / h^2)
  Kx <- sweep(Kx, 2, exp(-0.5 * sisa / h^2), "*")
  (Kx %*% t(Ky)) * (2 * pi)^(-d / 2) / (n * h^d)
}

# --- mean shift ---
toleransi <- function(X) 0.05 * mean(apply(X, 2, sd))

vektor_ms <- function(P, X, h) {
  W <- bobot_kernel(jarak2(P, X) / h^2)
  (W %*% X) / rowSums(W) - P
}

mean_shift <- function(X, h, eps = 1e-4, maxit = 500, tol = toleransi(X),
                       simpan = FALSE, verbose = FALSE) {
  P <- X; norma <- numeric(0)
  riwayat <- if (simpan) list(P) else NULL
  for (it in seq_len(maxit)) {
    m  <- vektor_ms(P, X, h)
    P0 <- P
    P  <- P0 + m
    norma[it] <- max(sqrt(rowSums((P - P0)^2)))
    if (simpan) riwayat[[it + 1]] <- P
    if (verbose)
      cat("iterasi", it, "| maks ||x - x0|| =", format(norma[it], digits = 3), "\n")
    if (norma[it] < eps) break
  }
  list(P = P, kl = cutree(hclust(dist(P), "single"), h = tol),
       iter = it, norma = norma, riwayat = riwayat)
}

# --- evaluasi ---
n_kluster <- function(kl) length(unique(kl))

pusat_mode <- function(P, kl) {
  do.call(rbind, lapply(split(as.data.frame(P), kl), colMeans))
}

purity <- function(kl, asal) sum(apply(table(kl, asal), 1, max)) / length(asal)

galat_mode <- function(r, pusat) {
  M <- pusat_mode(r$P, r$kl)
  mean(apply(pusat, 1, function(p) min(sqrt(colSums((t(M) - p)^2)))))
}

# --- bantuan tampilan ---
koma <- function(x) sub("\\.", ",", as.character(x))

atur_plot <- function(baris = 1, kolom = 1, mar = c(4.5, 4.5, 3.5, 1)) {
  par(mfrow = c(baris, kolom), mar = mar)
}

palet_kde <- colorRampPalette(c("#ffffff", "#c7e9c0", "#74c476", "#31a354", "#006d2c", "#00441b"))(80)

peta_kde <- function(gx, gy, f, ...) {
  image(gx, gy, f^0.6, col = palet_kde, ...)
  contour(gx, gy, f^0.6, add = TRUE, drawlabels = FALSE, nlevels = 8,
          col = adjustcolor("#00441b", 0.5))
}

lingkaran <- function(pusat, r, ...) {
  theta <- seq(0, 2 * pi, length.out = 200)
  lines(pusat[1] + r * cos(theta), pusat[2] + r * sin(theta), ...)
}

lingkaran_ganda <- function(pusat, r) {
  lingkaran(pusat, r, col = "black", lwd = 4)
  lingkaran(pusat, r, col = "white", lwd = 2, lty = 2)
}

label_bg <- function(x, y, teks, cex = 0.72) {
  w <- strwidth(teks, cex = cex); hh <- strheight(teks, cex = cex)
  rect(x - w / 2 - 0.05 * u, y - hh / 2 - 0.05 * u,
       x + w / 2 + 0.05 * u, y + hh / 2 + 0.05 * u,
       col = adjustcolor("white", 0.9), border = "grey40")
  text(x, y, teks, cex = cex, font = 2)
}

# peta KDE + data + mode + lingkaran bandwidth
plot_peta <- function(Xd, r, h2, judul, gx, gy, normalisasi = FALSE) {
  f <- kde_grid(Xd, gx, gy, h2)
  if (normalisasi) f <- f / max(f)
  peta_kde(gx, gy, f, asp = 1, xlab = "x1", ylab = "x2", main = judul, cex.main = 0.95)
  points(Xd[, 1], Xd[, 2], pch = 21, bg = "white", col = "#08519c", cex = 0.4, lwd = 0.5)
  points(pusat_mode(r$P[, 1:2], r$kl), pch = 24, bg = "#ffd400", cex = 1.6)
  lingkaran_ganda(c(0, 0), h2)
}

# =============================================================================
# 2. DATA
# =============================================================================
set.seed(42)
asal       <- rep(1:3, times = c(167, 167, 166))
pusat_asli <- rbind(c(0, 0), c(10, 0), c(5, 9))

# E = sebaran standar tiap blob 
E <- rbind(cbind(rnorm(167), rnorm(167)),
           cbind(rnorm(167), rnorm(167)),
           cbind(rnorm(166), rnorm(166)))
X <- E + pusat_asli[asal, ]

n   <- nrow(X); d <- ncol(X)
u   <- mean(apply(X, 2, sd))
eps <- 1e-4
tol <- toleransi(X)

warna <- c("#e05a5a", "#5fd35f", "#7b7bd8")
tepi  <- c("#a33636", "#2a8a2a", "#44449c")
print(table(Kelompok = asal))
## Kelompok
##   1   2   3 
## 167 167 166
atur_plot()
plot(X, pch = 21, cex = 0.9, lwd = 0.6, bg = adjustcolor(warna[asal], 0.8), col = tepi[asal],
     xlab = "x1", ylab = "x2", main = "Scatter plot Gaussian Blobs")
legend("topright", legend = paste("Kelompok", 1:3), pch = 21, pt.bg = warna, col = tepi, bty = "n")

# =============================================================================
# 3. EKSPLORASI DATA
# =============================================================================
cat("\n=== 1. VALIDASI DATA ===\n")
## 
## === 1. VALIDASI DATA ===
cat("Struktur   : matriks", nrow(X), "x", ncol(X), "| tipe:", typeof(X), "\n")
## Struktur   : matriks 500 x 2 | tipe: double
cat("Missing    :", sum(is.na(X)), "\n")
## Missing    : 0
cat("Duplikat   :", sum(duplicated(X)), "\n")
## Duplikat   : 0
cat("Tak hingga :", sum(is.infinite(X)), "\n")
## Tak hingga : 0
cat("\n=== 2. PEMERIKSAAN PENCILAN (1,5 x IQR) ===\n")
## 
## === 2. PEMERIKSAAN PENCILAN (1,5 x IQR) ===
pencilan <- sapply(1:ncol(X), function(j) {
  q <- quantile(X[, j], c(0.25, 0.75)); batas <- 1.5 * (q[2] - q[1])
  sum(X[, j] < q[1] - batas | X[, j] > q[2] + batas)
})
names(pencilan) <- c("x1", "x2")
cat("Jumlah pencilan per fitur:\n"); print(pencilan)
## Jumlah pencilan per fitur:
## x1 x2 
##  0  0
atur_plot()
boxplot(X, names = c("x1", "x2"), col = c("#e05a5a", "#7b7bd8"),
        main = "Boxplot (aturan 1,5 x IQR)", ylab = "Nilai")

cat("\n=== 3. SKALA FITUR ===\n")
## 
## === 3. SKALA FITUR ===
sdv <- apply(X, 2, sd)
cat("SD x1 =", round(sdv[1], 3), "| SD x2 =", round(sdv[2], 3),
    "| Rasio SD =", round(min(sdv) / max(sdv), 3), "(tanpa standardisasi)\n")
## SD x1 = 4.206 | SD x2 = 4.406 | Rasio SD = 0.955 (tanpa standardisasi)
# =============================================================================
# 4. MEAN SHIFT PADA DATA UTAMA
# =============================================================================
h1 <- bandwidth_kuantil(X, 0.3)
cat("\nBandwidth h =", round(h1, 4), "\n")
## 
## Bandwidth h = 3.0726
ms    <- mean_shift(X, h1, eps = eps, tol = tol, maxit = 200, simpan = TRUE, verbose = TRUE)
## iterasi 1 | maks ||x - x0|| = 3.22 
## iterasi 2 | maks ||x - x0|| = 1.26 
## iterasi 3 | maks ||x - x0|| = 0.475 
## iterasi 4 | maks ||x - x0|| = 0.114 
## iterasi 5 | maks ||x - x0|| = 0.0244 
## iterasi 6 | maks ||x - x0|| = 0.00507 
## iterasi 7 | maks ||x - x0|| = 0.00105 
## iterasi 8 | maks ||x - x0|| = 0.000218 
## iterasi 9 | maks ||x - x0|| = 4.5e-05
P     <- ms$P
kl    <- ms$kl
riw   <- ms$riwayat
norma <- ms$norma
Tit   <- ms$iter

cat("Konvergen:", Tit, "iterasi | kluster:", n_kluster(kl), "\n")
## Konvergen: 9 iterasi | kluster: 3
print(table(kl))
## kl
##   1   2   3 
## 167 167 166
sel <- sort(unique(pmin(c(0, 1, 2, 3, 4, Tit), Tit)))
atur_plot(2, 3, mar = c(4, 4, 3, 1))
for (it in sel) {
  plot(X, pch = 16, cex = 0.4, col = "grey85", xlab = "x1", ylab = "x2",
       main = if (it == 0) "Iterasi 0: data awal" else
         paste0("Iterasi ", it, " (maks ||x - x0|| = ", format(norma[it], digits = 2), ")"))
  if (it > 0)
    segments(riw[[it]][, 1], riw[[it]][, 2], riw[[it + 1]][, 1], riw[[it + 1]][, 2],
             col = adjustcolor("red3", 0.2))
  points(riw[[it + 1]], pch = 16, cex = 0.5, col = adjustcolor(kl + 1, 0.7))
}

par(mfrow = c(1, 2))
plot(norma, type = "b", pch = 19, col = "blue", log = "y", xlab = "Iterasi",
     ylab = "maks ||x - x0|| (skala log)", main = "Cek konvergensi")
abline(h = eps, col = "red", lty = 2, lwd = 2)
plot(X, col = kl + 1, pch = 16, cex = 0.5, xlab = "x1", ylab = "x2",
     main = paste0("Hasil: ", n_kluster(kl), " kluster"))
points(pusat_mode(P, kl), pch = 24, bg = "gold", cex = 2)

gx <- seq(min(X[, 1]) - 0.5 * u, max(X[, 1]) + 0.5 * u, length.out = 120)
gy <- seq(min(X[, 2]) - 0.5 * u, max(X[, 2]) + 0.5 * u, length.out = 120)
f  <- kde_grid(X, gx, gy, h1)

mode_k <- pusat_mode(P, kl)
f_mode <- apply(mode_k, 1, function(p) kde(p, X, h1))
jauh   <- sqrt(rowSums((X - P)^2))
rep_i  <- which.max(jauh)
rep_k  <- sapply(sort(unique(kl)), function(k) { idx <- which(kl == k); idx[which.max(jauh[idx])] })
lintasan <- function(i) t(sapply(riw, function(M) M[i, ]))
jejak  <- lintasan(rep_i)
p0 <- jejak[1, ]; p1 <- jejak[2, ]; pT <- jejak[Tit + 1, ]
m1 <- p1 - p0

cat("\n--- NOMINAL UNTUK ANOTASI ---\n")
## 
## --- NOMINAL UNTUK ANOTASI ---
cat("1. KDE f_K(mode)           :", sprintf("%.4f", f_mode), "\n")
## 1. KDE f_K(mode)           : 0.0052 0.0052 0.0051
cat("2. Kernel Gaussian K(0)    :", sprintf("%.4f", kernel_gaussian(0, d)), "\n")
## 2. Kernel Gaussian K(0)    : 0.1592
cat("3. m(x) titik contoh       :", sprintf("(%.4f, %.4f), ||m|| = %.4f", m1[1], m1[2], sqrt(sum(m1^2))), "\n")
## 3. m(x) titik contoh       : (-3.0464, -1.0552), ||m|| = 3.2239
cat("4. x_baru = x + m(x)       :", Tit, "iterasi | dari", sprintf("(%.3f, %.3f)", p0[1], p0[2]),
    "ke", sprintf("(%.3f, %.3f)", pT[1], pT[2]), "\n")
## 4. x_baru = x + m(x)       : 9 iterasi | dari (8.211, 10.245) ke (4.806, 8.977)
cat("5. Bandwidth h = Q_0,3     :", sprintf("%.4f", h1), "\n")
## 5. Bandwidth h = Q_0,3     : 3.0726
cat("6. Kriteria henti          :", sprintf("maks ||x - x0|| = %.2e < eps = %g", norma[Tit], eps), "\n")
## 6. Kriteria henti          : maks ||x - x0|| = 4.50e-05 < eps = 0.0001
col_titik <- "#08519c"; col_jejak <- "#e31a1c"; col_eps <- "#7b2cbf"; col_box <- "#08519c"
xl <- c(min(gx) - 3.4 * u, max(gx) + 3.4 * u)
yl <- c(min(gy) - 1 * u,   max(gy) + 0.3 * u)

atur_plot(mar = c(4.5, 4.5, 4, 1))
peta_kde(gx, gy, f, xlim = xl, ylim = yl, xlab = "x1", ylab = "x2",
         main = sprintf("Mean Shift | h = %.3f | %d kluster", h1, n_kluster(kl)))
points(X, pch = 21, bg = "white", col = col_titik, lwd = 0.6, cex = 0.5)
for (i in rep_k) {
  jj <- lintasan(i)
  q0 <- jj[1, ]; q1 <- jj[2, ]; qT <- jj[Tit + 1, ]
  lines(jj, col = col_jejak, lwd = 3)
  points(jj, pch = 21, bg = col_jejak, col = "white", cex = 1.1)
  lingkaran(q0, h1, lty = 2, lwd = 2.5, col = "black")
  arrows(q0[1], q0[2], q1[1], q1[2], col = col_jejak, lwd = 4, length = 0.12)
  points(q0[1], q0[2], pch = 21, bg = "white", col = "black", cex = 1.8, lwd = 2.5)
  lingkaran(qT, 0.28 * u, col = col_eps, lwd = 3.5)
}
points(mode_k, pch = 24, bg = "#ffd400", col = "black", cex = 2.6, lwd = 1.5)
for (i in seq_len(nrow(mode_k)))
  label_bg(mode_k[i, 1], mode_k[i, 2] - 0.55 * u,
           sprintf("(%.2f, %.2f)  f = %.3f", mode_k[i, 1], mode_k[i, 2], f_mode[i]))

kotak <- function(x, y, no, teks, target, kanan = FALSE) {
  w <- strwidth(teks, cex = 0.75); hh <- strheight(teks, cex = 0.75); pad <- 0.1 * u
  x0 <- if (kanan) x - w else x
  xc <- x0 - pad - 0.18 * u
  segments(if (kanan) xc else x0 + w + pad, y, target[1], target[2], col = "grey20", lwd = 1.3)
  rect(x0 - pad, y - hh / 2 - pad, x0 + w + pad, y + hh / 2 + pad,
       col = "#fffbe6", border = col_box, lwd = 2)
  text(x0, y, teks, adj = c(0, 0.5), cex = 0.75)
  points(xc, y, pch = 21, bg = col_box, col = "white", cex = 2.6)
  text(xc, y, no, col = "white", font = 2, cex = 0.85)
}
ys <- seq(max(gy) - 0.2 * u, min(gy) + 1.0 * u, length.out = 3)
xk <- xl[1] + 0.45 * u; xr <- xl[2] - 0.35 * u

kotak(xk, ys[1], 1, sprintf("Kepadatan (KDE)\nf_K(mode) = %s",
                            paste(sprintf("%.3f", f_mode), collapse = ", ")),
      mode_k[kl[rep_i], ])
kotak(xk, ys[2], 2, sprintf("Kernel Gaussian (radial)\nd = %d, K(0) = %.4f",
                            d, kernel_gaussian(0, d)), p0)
kotak(xk, ys[3], 3, sprintf("Vektor mean shift m(x)\nm = (%.3f, %.3f)\n||m|| = %.3f",
                            m1[1], m1[2], sqrt(sum(m1^2))), (p0 + p1) / 2)
kotak(xr, ys[1], 4, sprintf("x_baru = x + m(x)\n%d iterasi\n(%.2f, %.2f) -> (%.2f, %.2f)",
                            Tit, p0[1], p0[2], pT[1], pT[2]),
      jejak[min(3, Tit + 1), ], kanan = TRUE)
kotak(xr, ys[2], 5, sprintf("Bandwidth h = Q0,3(dist(X'))\nh = %.4f (radius)", h1),
      p0 + h1 * c(cos(pi / 4), sin(pi / 4)), kanan = TRUE)
kotak(xr, ys[3], 6, sprintf("Kriteria henti\n||x - x0|| = %.1e\n< eps = %g",
                            norma[Tit], eps), pT, kanan = TRUE)
legend("bottom", ncol = 3, bty = "o", bg = "white", cex = 0.8,
       legend = c("titik data", "mode (puncak)", "jejak iterasi", "radius h", "vektor m(x)", "lingkaran eps (diperbesar)"),
       pch = c(21, 24, 21, NA, NA, NA), lty = c(NA, NA, 1, 2, 1, 1), lwd = c(NA, NA, 3, 2.5, 4, 3.5),
       col = c(col_titik, "black", col_jejak, "black", col_jejak, col_eps),
       pt.bg = c("white", "#ffd400", col_jejak, NA, NA, NA), pt.cex = c(1, 1.6, 1, NA, NA, NA))

# =============================================================================
# 5. PENGARUH BANDWIDTH
# =============================================================================
dst <- dist(X)

qs <- c(0.05, 0.1, 0.2, 0.3, 0.5, 0.7)
cand <- setNames(sapply(qs, function(q) as.numeric(quantile(dst, q))), paste0("Q", koma(qs)))
print(round(cand, 3))
##  Q0,05   Q0,1   Q0,2   Q0,3   Q0,5   Q0,7 
##  0.808  1.198  1.914  3.073  9.361 10.514
ms_cand <- lapply(cand, function(h2) mean_shift(X, h2, eps = eps, tol = tol))
ringkas <- data.frame(
  sumber     = names(cand), h = round(cand, 3),
  iterasi    = sapply(ms_cand, `[[`, "iter"),
  kluster    = sapply(ms_cand, function(r) n_kluster(r$kl)),
  purity     = sapply(ms_cand, function(r) round(purity(r$kl, asal), 3)),
  galat_mode = sapply(ms_cand, function(r) round(galat_mode(r, pusat_asli), 3)),
  silhouette = sapply(ms_cand, function(r)
    if (n_kluster(r$kl) < 2) NA else
      round(mean(cluster::silhouette(r$kl, dst)[, 3]), 3)),
  row.names = NULL
)
print(ringkas)
##   sumber      h iterasi kluster purity galat_mode silhouette
## 1  Q0,05  0.808      34       3  1.000      0.155       0.82
## 2   Q0,1  1.198      15       3  1.000      0.111       0.82
## 3   Q0,2  1.914       9       3  1.000      0.099       0.82
## 4   Q0,3  3.073       9       3  1.000      0.167       0.82
## 5   Q0,5  9.361       9       1  0.334      5.888         NA
## 6   Q0,7 10.514       8       1  0.334      5.888         NA
atur_plot(2, 3, mar = c(4, 4, 4, 1))
for (i in seq_along(cand))
  plot_peta(X, ms_cand[[i]], cand[[i]],
            sprintf("%s\nh = %.2f | %d kluster", names(cand)[i], cand[[i]], n_kluster(ms_cand[[i]]$kl)),
            gx, gy)

# =============================================================================
# 6. PENGARUH DIMENSI DATA
# =============================================================================
dims <- c(2, 3, 5, 10, 20, 50)
jmin <- 10   # jarak antar-pusat terdekat: (0,0) ke (10,0)

set.seed(123)
Xd_list <- list(); ms_dim <- list(); hasil_d <- list(); h_dim <- numeric(length(dims))
for (i in seq_along(dims)) {
  dd <- dims[i]
  Eb <- if (dd == 2) E else cbind(E, matrix(rnorm(nrow(E) * (dd - 2)), nrow(E)))
  pus <- cbind(pusat_asli, matrix(0, 3, dd - 2))
  Xd  <- Eb + pus[asal, ]
  h2  <- bandwidth_kuantil(Xd, 0.3)
  wkt <- system.time(r <- mean_shift(Xd, h2, eps = eps))[["elapsed"]]
  Xd_list[[i]] <- Xd; ms_dim[[i]] <- r; h_dim[i] <- h2
  hasil_d[[i]] <- data.frame(d = dd, h = round(h2, 2), h_per_jarak = round(h2 / jmin, 2),
                             iterasi = r$iter, kluster = n_kluster(r$kl),
                             purity = round(purity(r$kl, asal), 3), detik = round(wkt, 2))
}
dat_d <- do.call(rbind, hasil_d)
print(dat_d)
##    d     h h_per_jarak iterasi kluster purity detik
## 1  2  3.07        0.31       9       3  1.000  0.63
## 2  3  3.54        0.35      13       3  1.000  0.94
## 3  5  4.35        0.43     164       1  0.334  4.94
## 4 10  5.65        0.56      20       1  0.334  0.43
## 5 20  7.55        0.76      11       1  0.334  0.33
## 6 50 11.18        1.12       8       1  0.334  0.31
gx7 <- seq(-6, 16, length.out = 100)
gy7 <- seq(-6, 15, length.out = 100)
atur_plot(2, 3, mar = c(4, 4, 4, 1))
for (i in seq_along(dims))
  plot_peta(Xd_list[[i]], ms_dim[[i]], h_dim[i],
            sprintf("d = %d | h = %.2f\nh/jarak = %.2f | %d kluster",
                    dims[i], h_dim[i], h_dim[i] / jmin, n_kluster(ms_dim[[i]]$kl)),
            gx7, gy7, normalisasi = TRUE)

atur_plot(1, 2)
plot(dat_d$d, dat_d$h_per_jarak, type = "b", pch = 21, cex = 1.6, log = "x",
     bg = c("#d73027", "#1a9850")[(dat_d$kluster == 3) + 1],
     xlab = "Dimensi (d)", ylab = "h / jarak antar-pusat", main = "Rasio h/jarak vs dimensi")
abline(h = 0.4, lty = 2)
legend("topleft", legend = c("3 kluster", "bukan 3"), pt.bg = c("#1a9850", "#d73027"),
       pch = 21, bty = "n")
plot(dat_d$d, dat_d$iterasi, type = "b", pch = 19, col = "blue", log = "x",
     xlab = "Dimensi (d)", ylab = "Iterasi sampai konvergen", main = "Iterasi vs dimensi")