K-MEANS CLUSTERING

K-Means Clustering merupakan salah satu metode analisis dalam teknik data mining dan machine learning yang digunakan untuk mengelompokkan data ke dalam beberapa kelompok (cluster) berdasarkan tingkat kemiripan karakteristik data. Metode ini termasuk dalam kategori unsupervised learning, yaitu teknik pembelajaran yang tidak menggunakan label atau kategori sebelumnya dalam proses pengelompokan data.K-Means Clustering adalah metode pengelompokan data yang membagi sekumpulan objek ke dalam sejumlah K kelompok dengan cara meminimalkan variasi atau jarak antar data dalam kelompok yang sama melalui penentuan titik pusat cluster (centroid) (MacQueen, 1967).

Sumber Data

Data yang digunakan pada penelitian in merupakan data sekunder yang diambil dari website resmi BPS Kota Yogyakarta (https://jogjakota.bps.go.id/id)

Variabel Penelitian

X1 Jumlah Pasar
X2 Jumlah Swalayan
X3 Jumlah Restoran
X4 Jumlah Akomodasi
X5 Jumlah Perbankan

Library Package

Berikut adalah daftar package yang akan digunakan pada analisis ini. Apabila belum memasang package dibawah silahkan pasang menggunakan fungsi install.packages()

library(readxl)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)
library(ggplot2)
library(cluster)
library(factoextra)
## Welcome to factoextra!
## Want to learn more? See two factoextra-related books at https://www.datanovia.com/en/product/practical-guide-to-principal-component-methods-in-r/
library(psych)
## 
## Attaching package: 'psych'
## The following objects are masked from 'package:ggplot2':
## 
##     %+%, alpha
library(sf)
## Linking to GEOS 3.13.1, GDAL 3.11.4, PROJ 9.7.0; sf_use_s2() is TRUE
library(tmap)
library(geodata)
## Loading required package: terra
## terra 1.8.93
## 
## Attaching package: 'terra'
## The following objects are masked from 'package:psych':
## 
##     describe, distance, rescale
## The following object is masked from 'package:tidyr':
## 
##     extract
library(ggrepel)
library(scales)
## 
## Attaching package: 'scales'
## The following object is masked from 'package:terra':
## 
##     rescale
## The following objects are masked from 'package:psych':
## 
##     alpha, rescale
library(knitr)
## 
## Attaching package: 'knitr'
## The following object is masked from 'package:terra':
## 
##     spin
library(kableExtra)
## 
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
## 
##     group_rows

Load Data

Untuk melakukan analisis, panggil data daai file lokal dan bersihkan data serta definisikan variabel.

set.seed(42)
path   <- "D:/SEMESTER 6/1. KP/INFRASTRUKTUR EKONOMI.xlsx"
id_col <- "KECAMATAN" 
k_min  <- 2
k_max  <- 5

df_raw <- read_excel(path)

df <- df_raw %>% 
  rename_with(toupper) %>%
  rename(
    X1 = PASAR,
    X2 = SWALAYAN,
    X3 = RESTORAN,
    X4 = AKOMODASI,
    X5 = PERBANKAN
  )

if (!id_col %in% names(df)) {
  stop(paste("Kolom", id_col, "tidak ditemukan! Periksa header Excel Anda."))
}

num_cols <- c("X1", "X2", "X3", "X4", "X5")

df <- df %>%
  mutate(across(all_of(num_cols), ~ replace_na(., median(., na.rm = TRUE))))

X <- df %>% select(all_of(num_cols)) %>% as.data.frame()

Standardiasi Data

Variabel infrastruktur ekonomi memiliki skala yang berbeda . Oleh karena itu, dilakukan Z-score Standardization agar semua variabel memiliki kontribusi yang setara dalam perhitungan jarak antar klaster.

X_scaled <- scale(X)
X_scaled
##               X1          X2          X3          X4         X5
##  [1,]  1.7702905 -0.27090233  0.95324111  1.20262529 -0.2934276
##  [2,] -0.2950484 -1.13286431 -1.10240354 -1.03916166 -1.0759012
##  [3,]  0.3933979 -0.09850994  1.08375823  0.96910582  0.2282215
##  [4,]  1.0818442  1.79780640  1.24690463  0.66553050  1.2063135
##  [5,] -0.2950484 -0.01231374 -1.00451570 -1.06251361 -0.1630153
##  [6,]  0.3933979  2.48737598  2.03000736  0.08173182  2.7712606
##  [7,] -0.9834947 -0.44329473 -0.35193009 -0.33860324 -0.4238399
##  [8,] -0.9834947 -1.04666811 -0.74348145 -0.71223440 -0.9454889
##  [9,]  1.7702905 -0.61568712 -0.84136930 -0.85234608  0.3586337
## [10,] -0.9834947 -0.44329473 -1.03714498 -0.82899413 -0.4890460
## [11,]  0.3933979 -0.18470614  0.20276767 -0.36195519 -0.1630153
## [12,] -0.9834947  0.33247105 -0.09089585  2.34687072 -0.6194583
## [13,] -0.2950484 -0.44329473 -0.64559361 -0.40865908  0.3586337
## [14,] -0.9834947  0.07388245  0.30065551  0.33860324 -0.7498705
## attr(,"scaled:center")
##        X1        X2        X3        X4        X5 
##  2.428571 15.142857 35.785714 61.500000 17.500000 
## attr(,"scaled:scale")
##        X1        X2        X3        X4        X5 
##  1.452546 11.601440 30.647320 42.822981 15.335981

Deteksi Outlier Multivariat (Mahalanobis Distance)

Outlier dapat menarik pusat klaster (centroid) secara ekstrem, sehingga hasil clustering menjadi tidak akurat. Menggunakan Jarak Mahalanobis yang mempertimbangkan korelasi antar variabel untuk mendeteksi kecamatan dengan karakteristik infrastruktur yang sangat tidak biasa dibandingkan wilayah lainnya.

center <- colMeans(X_scaled)
cov_matrix <- cov(X_scaled)

md <- mahalanobis(X_scaled, center = center, cov = cov_matrix)

alpha <- 0.05
cutoff <- qchisq(1 - alpha, df = ncol(X_scaled))

df$Mahalanobis_Distance <- md
df$Outlier <- ifelse(md > cutoff, "Outlier", "Tidak")

cat("=== HASIL DETEKSI OUTLIER ===\n")
## === HASIL DETEKSI OUTLIER ===
print(df[, c(id_col, "Mahalanobis_Distance", "Outlier")])
## # A tibble: 14 × 3
##    KECAMATAN     Mahalanobis_Distance Outlier
##    <chr>                        <dbl> <chr>  
##  1 MANTRIJERON                   6.40 Tidak  
##  2 KRATON                        2.26 Tidak  
##  3 MERGANGSAN                    4.82 Tidak  
##  4 UMBULHARJO                    5.77 Tidak  
##  5 KOTAGEDE                      3.49 Tidak  
##  6 GONDOKUSUMAN                  9.05 Tidak  
##  7 DANUREJAN                     1.44 Tidak  
##  8 PAKUALAMAN                    2.36 Tidak  
##  9 GONDOMANAN                    7.14 Tidak  
## 10 NGAMPILAN                     1.80 Tidak  
## 11 WIROBRAJAN                    1.38 Tidak  
## 12 GEDONG TENGEN                10.6  Tidak  
## 13 JETIS                         4.68 Tidak  
## 14 TEGALREJO                     3.86 Tidak
cat("\nJumlah Outlier:", sum(df$Outlier == "Outlier"), "\n")
## 
## Jumlah Outlier: 0

Uji Asumsi Analisis Clustering

Sebelum melakukan clustering, kami melakukan uji KMO (Kaiser-Meyer-Olkin) untuk memastikan kecukupan sampel dan uji VIF (Variance Inflation Factor) untuk mendeteksi multikolinearitas.

#KMO
kmo_result <- KMO(X_scaled)
print(kmo_result)
## Kaiser-Meyer-Olkin factor adequacy
## Call: KMO(r = X_scaled)
## Overall MSA =  0.58
## MSA for each item = 
##   X1   X2   X3   X4   X5 
## 0.45 0.59 0.76 0.41 0.54
#UJI MULTIKOLINEARITAS
vif_values <- sapply(num_cols, function(var){
  y <- df[[var]]
  x_vars <- num_cols[num_cols != var]
  formula <- as.formula(
    paste(var, "~", paste(x_vars, collapse = "+"))
  )
  
  model <- lm(formula, data = df)
  r2 <- summary(model)$r.squared
  vif <- 1 / (1 - r2)
  return(vif)
})

vif_result <- data.frame(
  Variabel = num_cols,
  VIF = vif_values
)
cat("=== HASIL UJI MULTIKOLINEARITAS (VIF) ===\n")
## === HASIL UJI MULTIKOLINEARITAS (VIF) ===
print(vif_result %>% arrange(desc(VIF)))
##    Variabel      VIF
## X2       X2 7.787928
## X5       X5 7.572016
## X3       X3 4.766118
## X4       X4 2.488202
## X1       X1 1.739133
  • KMO > 0.5 : Menunjukkan bahwa data cukup layak untuk dianalisis lebih lanjut.
  • VIF < 10 : Menunjukkan tidak ada variabel yang sangat tumpang tindih informasinya, sehingga analisis dapat dilanjutkan.

Validasi k Optimal (Silhouette Coefficient)

Menggunakan metode Silhouette Coefficient untuk menentukan jumlah kelompok (k) terbaik. Metode ini mengukur seberapa dekat sebuah data dengan kelompoknya sendiri dibandingkan dengan kelompok lain.

fviz_nbclust(
  X_scaled,
  kmeans,
  method = "silhouette",
  k.max = k_max
) +
  geom_point(size = 3) +
  theme_minimal()

sil_results <- data.frame()

for(k in k_min:k_max){
  km <- kmeans(X_scaled, centers = k, nstart = 25)
  ss <- silhouette(km$cluster, dist(X_scaled))
  avg_sil <- mean(ss[, 3])
  sil_results <- rbind(
    sil_results,
    data.frame(
      k = k,
      avg_silhouette = avg_sil
    )
  )
}

sil_ranking <- sil_results %>%
  arrange(desc(avg_silhouette))

cat("=== RANKING NILAI SILHOUETTE ===\n")
## === RANKING NILAI SILHOUETTE ===
print(sil_ranking)
##   k avg_silhouette
## 1 2      0.3789327
## 2 3      0.3528985
## 3 4      0.3190462
## 4 5      0.3059025
k_terpilih <- sil_ranking$k[1]

cat("\nJumlah Klaster Optimal (Berdasarkan Silhouette Tertinggi):",
    k_terpilih, "\n")
## 
## Jumlah Klaster Optimal (Berdasarkan Silhouette Tertinggi): 2

Model K-Means Clustering

final_model <- kmeans(X_scaled, centers = k_terpilih, nstart = 25)
df$cluster <- factor(final_model$cluster)
cluster_colors <- hue_pal()(k_terpilih)

Profilisasi Cluster

profil_mean <- df %>%
  group_by(cluster) %>%
  summarise(across(all_of(num_cols), mean), .groups = "drop")

profil_long <- profil_mean %>%
  pivot_longer(cols = all_of(num_cols), 
               names_to = "Variabel", 
               values_to = "Rata_Rata")

variable_labels <- c(X1 = "Pasar", X2 = "Swalayan", X3 = "Restoran", 
                     X4 = "Akomodasi", X5 = "Perbankan")

ggplot(profil_long, aes(x = Variabel, y = Rata_Rata, fill = cluster)) +
  geom_bar(stat = "identity", position = "dodge", color = "white") +
  scale_x_discrete(labels = variable_labels) +
  scale_fill_manual(values = cluster_colors) + 
  theme_minimal() +
  labs(
    title = "Perbandingan Infrastruktur Ekonomi antar Klaster",
    x = "Jenis Infrastruktur",
    y = "Rata-rata Jumlah (Unit)",
    fill = "Klaster"
  ) +
  geom_text(aes(label = round(Rata_Rata, 1)), 
            position = position_dodge(width = 0.9), 
            vjust = -0.5, size = 3)

df <- df %>%
  mutate(Nama_Klaster = case_when(
    cluster == 1 ~ "Ekonomi Lokal",
    cluster == 2 ~ "Pusat Ekonomi & Wisata"
  ))

anggota_klaster <- df %>%
  group_by(cluster) %>%
  summarise(
    Jumlah_Kecamatan = n(),
    Daftar_Kecamatan = paste(KECAMATAN, collapse = ", "),
    .groups = "drop"
  )

profil_mean <- df %>%
  group_by(cluster) %>%
  summarise(across(all_of(num_cols), mean), .groups = "drop")

anggota <- df %>%
  group_by(cluster) %>%
  summarise(
    Jumlah = n(),
    Daftar_Kecamatan = paste(KECAMATAN, collapse = ", "),
    .groups = "drop"
  )

tabel_final <- profil_mean %>%
  left_join(anggota, by = "cluster") %>%
  rename(
    Klaster = cluster,
    `X1` = X1,
    `X2` = X2,
    `X3` = X3,
    `X4` = X4,
    `X5` = X5
  )

tabel_final %>%
  kable(
    caption = "Profil Infrastruktur Ekonomi dan Keanggotaan Klaster",
    digits = 2,
    align = "c"
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = F,
    position = "center"
  ) %>%
  column_spec(1, bold = T, border_right = T) %>%
  column_spec(8, width = "30em", italic = T) 
Profil Infrastruktur Ekonomi dan Keanggotaan Klaster
Klaster X1 X2 X3 X4 X5 Jumlah Daftar_Kecamatan
1 2.0 9.67 18.0 36.44 11.89 9 KRATON, KOTAGEDE, DANUREJAN, PAKUALAMAN, GONDOMANAN, NGAMPILAN, WIROBRAJAN, JETIS, TEGALREJO
2 3.2 25.00 67.8 106.60 27.60 5 MANTRIJERON, MERGANGSAN, UMBULHARJO, GONDOKUSUMAN, GEDONG TENGEN
  • Cluster 1 memiliki karakteristik sebagai wilayah infrastruktur ekonomi relatif rendah. Wilayah ini cenderung memiliki intensitas aktivitas perdagangan dan jasa yang lebih rendah, sehingga dapat menjadi prioritas dalam upaya pemerataan pembangunan dan penguatan infrastruktur ekonomi guna mengurangi kesenjangan antar kecamatan di Kota Yogyakarta.

  • Cluster 2 memiliki karakteristik sebagai wilayah infrastruktur ekonomi yang tinggi. Tingginya konsentrasi fasilitas perdagangan, jasa, serta layanan keuangan tersebut menunjukkan bahwa kecamatan dalam cluster ini merupakan wilayah dengan tingkat aktivitas ekonomi yang relatif lebih intensif dan terpusat. Ketersediaan infrastruktur yang lebih lengkap mencerminkan peran wilayah ini sebagai pusat distribusi barang dan jasa, sekaligus sebagai simpul utama dalam dinamika perekonomian kota.

Visualisasi K-Means Clustering

Berikut merupakan visualisasi cluster yang dapat digunakan untuk melihat lebih jelas tentang hasil cluster kecamatan di Kota Yogyakarta berdasarkan infrastruktur ekonomi pada tahun 2025 dengan 2 cluster.

pca <- prcomp(X_scaled, center = TRUE, scale. = FALSE)
var_pct <- (pca$sdev^2) / sum(pca$sdev^2) * 100

plot_df <- data.frame(
  Dim1 = pca$x[, 1],
  Dim2 = pca$x[, 2],
  cluster = factor(final_model$cluster),
  Kecamatan = df[[id_col]]
)

hulls <- plot_df %>%
  group_by(cluster) %>%
  slice(chull(Dim1, Dim2))

cluster_colors <- scales::hue_pal()(k_terpilih)

ggplot(plot_df, aes(Dim1, Dim2, color = cluster)) +
  geom_polygon(
    data = hulls,
    aes(fill = cluster),
    alpha = 0.2,
    color = NA
  ) +
  geom_point(size = 3) +
  geom_text(aes(label = Kecamatan), vjust = -0.6, size = 3) +
  scale_color_manual(values = cluster_colors)+
  scale_fill_manual(values = cluster_colors)+
  theme_minimal() +
  labs(
    title = "Visualisasi Klaster K-Means",
    x = paste0("Dimensi 1 (", round(var_pct[1], 1), "%)"),
    y = paste0("Dimensi 2 (", round(var_pct[2], 1), "%)")
  ) +
  guides(fill = "none")

Visualisasi Peta Kota Yogyakarta

Berikut merupakan visualisasi peta Kota yogyakrta yang dapat digunakan untuk melihat lebih jelas tentang hasil cluster kecamatan di Kota Yogyakarta berdasarkan infrastruktur ekonomi pada tahun 2025.

path_rds <- "D:/SEMESTER 6/1. KP/yogyakarta_map.rds"

if (file.exists(path_rds)) {
  cat("Memuat data peta dari penyimpanan lokal...\n")
  kota_yk <- readRDS(path_rds)
} else {
  cat("Mengunduh data peta baru dari GADM...\n")
  tryCatch({
    indo <- geodata::gadm(country = "IDN", level = 3, path = tempdir(), version = "latest")
    indo_sf <- sf::st_as_sf(indo)
    
    kota_yk <- indo_sf %>%
      dplyr::filter(NAME_1 == "Yogyakarta", 
                    NAME_2 == "Kota Yogyakarta") %>%
      dplyr::rename(Kecamatan = NAME_3)
    
    saveRDS(kota_yk, path_rds)
    cat("Data peta berhasil disimpan ke lokal.\n")
    
  }, error = function(e) {
    stop("Gagal mendapatkan data peta. Periksa koneksi internet.")
  })
}
## Memuat data peta dari penyimpanan lokal...
df$Kecamatan <- toupper(trimws(df[[id_col]]))
kota_yk$Kecamatan <- toupper(trimws(kota_yk$Kecamatan))

map_data <- kota_yk %>%
  left_join(df, by = "Kecamatan")

centroids <- st_centroid(map_data)
## Warning: st_centroid assumes attributes are constant over geometries
coords <- centroids %>%
  mutate(
    X = st_coordinates(.)[,1],
    Y = st_coordinates(.)[,2]
  ) %>%
  st_drop_geometry()

ggplot(data = map_data) +
  geom_sf(aes(fill = cluster), color = "white", size = 0.3) +
  geom_text_repel(
    data = coords,
    aes(x = X, y = Y, label = Kecamatan),
    size = 2.8,           # Ukuran teks
    fontface = "bold",    # Tebal
    box.padding = 0.5,    # Jarak antar teks
    max.overlaps = Inf    # Tampilkan semua nama kecamatan
  ) +
  scale_fill_manual(values = cluster_colors, na.value = "grey80") +
  theme_minimal() +
  labs(
    title = "Peta Distribusi Klaster Infrastruktur Ekonomi",
    subtitle = "Kota Yogyakarta - Tahun 2025",
    fill = "Klaster"
  ) +
  theme(
    axis.text = element_blank(),
    axis.title = element_blank(),
    panel.grid = element_blank(),
    legend.position = "right"
  )

Secara keseluruhan, hasil pengelompokan ini menunjukkan bahwa setiap cluster memiliki karakteristik dan peran yang berbeda dalam pembangunan infrastruktur ekonomi Kota Yogyakarta. Informasi ini dapat menjadi dasar dalam perumusan kebijakan pembangunan infrastruktur ekonomi yang lebih terarah dan berbasis karakteristik wilayah.