ANALISIS K-MEANS CLUSTERING KEPUASAN PENGGUNA WESGO

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/library/principal-component-methods
library(writexl)

set.seed(123)

============================================================

1. IMPOR DATA

============================================================

data_asli <- read_excel(
  "D:/STATISTIKA/SEM 5/Analisis Perancangan Survey/wesgo_dengan_skor_aspek.xlsx"
)

cat("\n========== INFORMASI DATA ==========\n")
## 
## ========== INFORMASI DATA ==========
cat("Dimensi data:", dim(data_asli), "\n")
## Dimensi data: 157 37
cat("\nNama kolom:\n")
## 
## Nama kolom:
print(names(data_asli))
##  [1] "Q1"                           "Q2"                          
##  [3] "Q4"                           "Q5"                          
##  [5] "Q6"                           "Q7"                          
##  [7] "Q9"                           "Q10"                         
##  [9] "Q11"                          "Q12"                         
## [11] "Q13"                          "Q14"                         
## [13] "Q15"                          "Q16"                         
## [15] "Q18"                          "Q19"                         
## [17] "Q20"                          "Q21"                         
## [19] "Q23"                          "Q24"                         
## [21] "Q25"                          "Q26"                         
## [23] "Q28"                          "Q29"                         
## [25] "Q30"                          "Skor_Usabilitas"             
## [27] "Skor_Ketersediaan_Akses"      "Skor_Keamanan_Berkendara"    
## [29] "Skor_Kenyamanan_Sepeda"       "Skor_Kecepatan_Respons"      
## [31] "Skor_Kepuasan_Keseluruhan"    "Skor_X1_Usabilitas"          
## [33] "Skor_X2_Ketersediaan_Akses"   "Skor_X3_Keamanan_Berkendara" 
## [35] "Skor_X4_Kenyamanan_Sepeda"    "Skor_X5_Kecepatan_Respons"   
## [37] "Skor_X6_Kepuasan_Keseluruhan"
cat("\nStruktur data:\n")
## 
## Struktur data:
str(data_asli)
## tibble [157 × 37] (S3: tbl_df/tbl/data.frame)
##  $ Q1                          : num [1:157] 4 3 4 3 3 3 4 3 3 3 ...
##  $ Q2                          : num [1:157] 5 3 4 4 3 3 4 3 4 3 ...
##  $ Q4                          : num [1:157] 5 4 3 3 3 3 3 4 4 3 ...
##  $ Q5                          : num [1:157] 4 3 5 3 2 4 5 4 5 4 ...
##  $ Q6                          : num [1:157] 4 4 4 3 2 3 3 4 3 4 ...
##  $ Q7                          : num [1:157] 4 4 4 4 2 3 3 5 3 4 ...
##  $ Q9                          : num [1:157] 4 2 5 4 4 4 4 4 3 2 ...
##  $ Q10                         : num [1:157] 5 4 3 2 3 3 3 4 2 4 ...
##  $ Q11                         : num [1:157] 4 4 5 3 3 5 4 5 4 5 ...
##  $ Q12                         : num [1:157] 4 4 5 2 3 4 5 5 4 4 ...
##  $ Q13                         : num [1:157] 5 5 4 3 3 5 5 4 3 4 ...
##  $ Q14                         : num [1:157] 4 4 5 4 4 4 4 5 3 5 ...
##  $ Q15                         : num [1:157] 4 4 5 3 3 3 5 4 3 5 ...
##  $ Q16                         : num [1:157] 4 5 3 4 3 5 3 4 5 3 ...
##  $ Q18                         : num [1:157] 4 5 4 3 2 4 4 3 4 4 ...
##  $ Q19                         : num [1:157] 3 3 4 3 3 3 3 3 3 3 ...
##  $ Q20                         : num [1:157] 4 4 4 3 2 5 4 3 3 5 ...
##  $ Q21                         : num [1:157] 4 3 4 2 3 4 4 4 4 4 ...
##  $ Q23                         : num [1:157] 3 3 5 2 3 4 5 4 3 3 ...
##  $ Q24                         : num [1:157] 4 3 3 4 3 3 4 3 4 5 ...
##  $ Q25                         : num [1:157] 5 4 4 3 4 3 3 3 4 3 ...
##  $ Q26                         : num [1:157] 5 3 4 3 3 4 4 4 4 3 ...
##  $ Q28                         : num [1:157] 5 4 5 2 3 4 4 4 4 4 ...
##  $ Q29                         : num [1:157] 4 4 3 3 4 4 4 3 3 4 ...
##  $ Q30                         : num [1:157] 4 5 3 4 3 3 4 3 4 4 ...
##  $ Skor_Usabilitas             : num [1:157] 4.5 3.25 4 3.25 2.75 3.25 4 3.5 4 3.25 ...
##  $ Skor_Ketersediaan_Akses     : num [1:157] 4.25 3.5 4 3.25 2.75 3.25 3.25 4.25 2.75 3.5 ...
##  $ Skor_Keamanan_Berkendara    : num [1:157] 4.2 4.2 4.8 3 3.2 4.2 4.6 4.6 3.4 4.6 ...
##  $ Skor_Kenyamanan_Sepeda      : num [1:157] 3.75 4.25 3.75 3.25 2.5 4.25 3.5 3.25 3.75 3.75 ...
##  $ Skor_Kecepatan_Respons      : num [1:157] 4 3.25 4 2.75 3.25 3.5 4 3.5 3.75 3.75 ...
##  $ Skor_Kepuasan_Keseluruhan   : num [1:157] 4.5 4 3.75 3 3.25 3.75 4 3.5 3.75 3.75 ...
##  $ Skor_X1_Usabilitas          : num [1:157] 4.5 3.25 4 3.25 2.75 3.25 4 3.5 4 3.25 ...
##  $ Skor_X2_Ketersediaan_Akses  : num [1:157] 4.25 3.5 4 3.25 2.75 3.25 3.25 4.25 2.75 3.5 ...
##  $ Skor_X3_Keamanan_Berkendara : num [1:157] 4.2 4.2 4.8 3 3.2 4.2 4.6 4.6 3.4 4.6 ...
##  $ Skor_X4_Kenyamanan_Sepeda   : num [1:157] 3.75 4.25 3.75 3.25 2.5 4.25 3.5 3.25 3.75 3.75 ...
##  $ Skor_X5_Kecepatan_Respons   : num [1:157] 4 3.25 4 2.75 3.25 3.5 4 3.5 3.75 3.75 ...
##  $ Skor_X6_Kepuasan_Keseluruhan: num [1:157] 4.5 4 3.75 3 3.25 3.75 4 3.5 3.75 3.75 ...
cat("\nEnam baris pertama:\n")
## 
## Enam baris pertama:
print(head(data_asli))
## # A tibble: 6 × 37
##      Q1    Q2    Q4    Q5    Q6    Q7    Q9   Q10   Q11   Q12   Q13   Q14   Q15
##   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1     4     5     5     4     4     4     4     5     4     4     5     4     4
## 2     3     3     4     3     4     4     2     4     4     4     5     4     4
## 3     4     4     3     5     4     4     5     3     5     5     4     5     5
## 4     3     4     3     3     3     4     4     2     3     2     3     4     3
## 5     3     3     3     2     2     2     4     3     3     3     3     4     3
## 6     3     3     3     4     3     3     4     3     5     4     5     4     3
## # ℹ 24 more variables: Q16 <dbl>, Q18 <dbl>, Q19 <dbl>, Q20 <dbl>, Q21 <dbl>,
## #   Q23 <dbl>, Q24 <dbl>, Q25 <dbl>, Q26 <dbl>, Q28 <dbl>, Q29 <dbl>,
## #   Q30 <dbl>, Skor_Usabilitas <dbl>, Skor_Ketersediaan_Akses <dbl>,
## #   Skor_Keamanan_Berkendara <dbl>, Skor_Kenyamanan_Sepeda <dbl>,
## #   Skor_Kecepatan_Respons <dbl>, Skor_Kepuasan_Keseluruhan <dbl>,
## #   Skor_X1_Usabilitas <dbl>, Skor_X2_Ketersediaan_Akses <dbl>,
## #   Skor_X3_Keamanan_Berkendara <dbl>, Skor_X4_Kenyamanan_Sepeda <dbl>, …

============================================================

2. MEMILIH ENAM SKOR ASPEK WESGO

============================================================

kolom_aspek <- c(
  "Skor_Usabilitas",
  "Skor_Ketersediaan_Akses",
  "Skor_Keamanan_Berkendara",
  "Skor_Kenyamanan_Sepeda",
  "Skor_Kecepatan_Respons",
  "Skor_Kepuasan_Keseluruhan"
)

if (!all(kolom_aspek %in% names(data_asli))) {
  stop(
    "Nama kolom skor aspek tidak sesuai. Periksa names(data_asli)."
  )
}

data_analisis <- data_asli %>%
  select(all_of(kolom_aspek)) %>%
  mutate(across(everything(), as.numeric))

cat("\n========== PEMERIKSAAN MISSING VALUE ==========\n")
## 
## ========== PEMERIKSAAN MISSING VALUE ==========
print(colSums(is.na(data_analisis)))
##           Skor_Usabilitas   Skor_Ketersediaan_Akses  Skor_Keamanan_Berkendara 
##                         0                         0                         0 
##    Skor_Kenyamanan_Sepeda    Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan 
##                         0                         0                         0
baris_lengkap <- complete.cases(data_analisis)
data_bersih <- data_analisis[baris_lengkap, , drop = FALSE]

# Menyimpan data asli yang barisnya sesuai dengan data clustering
data_lengkap_asli <- data_asli[
  baris_lengkap, ,
  drop = FALSE
]

cat("\nJumlah responden awal:", nrow(data_analisis), "\n")
## 
## Jumlah responden awal: 157
cat("Jumlah responden dianalisis:", nrow(data_bersih), "\n")
## Jumlah responden dianalisis: 157
cat("Jumlah baris dikeluarkan:", sum(!baris_lengkap), "\n")
## Jumlah baris dikeluarkan: 0
if (nrow(data_bersih) < 3) {
  stop("Jumlah data lengkap tidak mencukupi.")
}

if (any(!is.finite(as.matrix(data_bersih)))) {
  stop("Terdapat nilai tidak valid dalam data.")
}

if (any(sapply(data_bersih, sd) == 0)) {
  stop("Ada aspek dengan varians nol.")
}

============================================================

3. STATISTIK DESKRIPTIF

============================================================

statistik_deskriptif <- data_bersih %>%
  summarise(
    across(
      everything(),
      list(
        N = ~ sum(!is.na(.x)),
        Mean = ~ mean(.x),
        SD = ~ sd(.x),
        Minimum = ~ min(.x),
        Median = ~ median(.x),
        Maksimum = ~ max(.x)
      ),
      .names = "{.col}_{.fn}"
    )
  ) %>%
  pivot_longer(
    cols = everything(),
    names_to = c("Aspek", ".value"),
    names_pattern =
      "(.+)_(N|Mean|SD|Minimum|Median|Maksimum)"
  )

cat("\n========== STATISTIK DESKRIPTIF ==========\n")
## 
## ========== STATISTIK DESKRIPTIF ==========
print(statistik_deskriptif)
## # A tibble: 6 × 7
##   Aspek                         N  Mean    SD Minimum Median Maksimum
##   <chr>                     <int> <dbl> <dbl>   <dbl>  <dbl>    <dbl>
## 1 Skor_Usabilitas             157  3.35 0.850    1      3.25        5
## 2 Skor_Ketersediaan_Akses     157  3.33 0.745    1.25   3.25        5
## 3 Skor_Keamanan_Berkendara    157  3.54 0.848    1.2    3.8         5
## 4 Skor_Kenyamanan_Sepeda      157  3.43 0.808    1      3.75        5
## 5 Skor_Kecepatan_Respons      157  3.31 0.792    1.25   3.25        5
## 6 Skor_Kepuasan_Keseluruhan   157  3.43 0.792    1      3.5         5
rata_rata_aspek <- data_bersih %>%
  summarise(across(everything(), mean)) %>%
  pivot_longer(
    cols = everything(),
    names_to = "Aspek",
    values_to = "Rata_rata"
  )

cat("\n========== RATA-RATA ASPEK ==========\n")
## 
## ========== RATA-RATA ASPEK ==========
print(rata_rata_aspek)
## # A tibble: 6 × 2
##   Aspek                     Rata_rata
##   <chr>                         <dbl>
## 1 Skor_Usabilitas                3.35
## 2 Skor_Ketersediaan_Akses        3.33
## 3 Skor_Keamanan_Berkendara       3.54
## 4 Skor_Kenyamanan_Sepeda         3.43
## 5 Skor_Kecepatan_Respons         3.31
## 6 Skor_Kepuasan_Keseluruhan      3.43
grafik_deskriptif <- ggplot(
  rata_rata_aspek,
  aes(
    x = reorder(Aspek, Rata_rata),
    y = Rata_rata
  )
) +
  geom_col(fill = "#2878B5", width = 0.7) +
  geom_text(
    aes(label = round(Rata_rata, 3)),
    hjust = -0.15,
    size = 3.5
  ) +
  coord_flip() +
  labs(
    title = "Rata-rata Penilaian Enam Aspek WESGO",
    x = "Aspek Penilaian",
    y = "Rata-rata"
  ) +
  expand_limits(
    y = max(rata_rata_aspek$Rata_rata) * 1.15
  ) +
  theme_minimal(base_size = 12)

print(grafik_deskriptif)

# ============================================================ # 4. STANDARDISASI DATA # ============================================================

data_scaled <- scale(data_bersih)

cat("\n========== PEMERIKSAAN STANDARDISASI ==========\n")
## 
## ========== PEMERIKSAAN STANDARDISASI ==========
print(head(round(data_scaled, 3)))
##      Skor_Usabilitas Skor_Ketersediaan_Akses Skor_Keamanan_Berkendara
## [1,]           1.349                   1.237                    0.782
## [2,]          -0.122                   0.231                    0.782
## [3,]           0.760                   0.902                    1.489
## [4,]          -0.122                  -0.105                   -0.634
## [5,]          -0.710                  -0.776                   -0.398
## [6,]          -0.122                  -0.105                    0.782
##      Skor_Kenyamanan_Sepeda Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan
## [1,]                  0.398                  0.876                     1.355
## [2,]                  1.017                 -0.070                     0.724
## [3,]                  0.398                  0.876                     0.408
## [4,]                 -0.221                 -0.701                    -0.539
## [5,]                 -1.149                 -0.070                    -0.223
## [6,]                  1.017                  0.245                     0.408
cat("\nRata-rata setelah standardisasi:\n")
## 
## Rata-rata setelah standardisasi:
print(round(colMeans(data_scaled), 4))
##           Skor_Usabilitas   Skor_Ketersediaan_Akses  Skor_Keamanan_Berkendara 
##                         0                         0                         0 
##    Skor_Kenyamanan_Sepeda    Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan 
##                         0                         0                         0
cat("\nStandar deviasi setelah standardisasi:\n")
## 
## Standar deviasi setelah standardisasi:
print(round(apply(data_scaled, 2, sd), 4))
##           Skor_Usabilitas   Skor_Ketersediaan_Akses  Skor_Keamanan_Berkendara 
##                         1                         1                         1 
##    Skor_Kenyamanan_Sepeda    Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan 
##                         1                         1                         1

============================================================

5. ELBOW METHOD

============================================================

maks_k <- min(8, nrow(data_scaled) - 1)
nilai_k <- 2:maks_k

hasil_elbow <- data.frame(
  K = nilai_k,
  WSS = sapply(nilai_k, function(k) {
    kmeans(
      data_scaled,
      centers = k,
      nstart = 100,
      iter.max = 200
    )$tot.withinss
  })
)

cat("\n========== HASIL ELBOW METHOD ==========\n")
## 
## ========== HASIL ELBOW METHOD ==========
print(hasil_elbow)
##   K      WSS
## 1 2 423.4824
## 2 3 321.0110
## 3 4 271.5573
## 4 5 229.6379
## 5 6 194.9078
## 6 7 174.6631
## 7 8 162.6832
grafik_elbow <- ggplot(
  hasil_elbow,
  aes(x = K, y = WSS)
) +
  geom_line(color = "#2878B5", linewidth = 1) +
  geom_point(color = "#D9534F", size = 3) +
  scale_x_continuous(breaks = nilai_k) +
  labs(
    title = "Elbow Method untuk Menentukan Jumlah Cluster",
    x = "Jumlah Cluster (K)",
    y = "Within-Cluster Sum of Squares (WSS)"
  ) +
  theme_minimal(base_size = 12)

print(grafik_elbow)

============================================================

6. SILHOUETTE COEFFICIENT

============================================================

jarak_data <- dist(data_scaled)

hasil_silhouette <- sapply(nilai_k, function(k) {
  
  model <- kmeans(
    data_scaled,
    centers = k,
    nstart = 100,
    iter.max = 200
  )
  
  sil <- silhouette(model$cluster, jarak_data)
  mean(sil[, "sil_width"])
})

tabel_silhouette <- data.frame(
  K = nilai_k,
  Silhouette = hasil_silhouette
)

cat("\n========== HASIL SILHOUETTE ==========\n")
## 
## ========== HASIL SILHOUETTE ==========
print(tabel_silhouette)
##   K Silhouette
## 1 2  0.4638061
## 2 3  0.3458680
## 3 4  0.3247253
## 4 5  0.2922781
## 5 6  0.3134016
## 6 7  0.3077925
## 7 8  0.3074307
grafik_silhouette <- ggplot(
  tabel_silhouette,
  aes(x = K, y = Silhouette)
) +
  geom_line(color = "#2E8B57", linewidth = 1) +
  geom_point(color = "#D9534F", size = 3) +
  scale_x_continuous(breaks = nilai_k) +
  labs(
    title = "Perbandingan Silhouette Coefficient",
    x = "Jumlah Cluster (K)",
    y = "Rata-rata Silhouette"
  ) +
  theme_minimal(base_size = 12)

print(grafik_silhouette)

k_terbaik <- tabel_silhouette$K[
  which.max(tabel_silhouette$Silhouette)
]

cat("\nK dengan Silhouette tertinggi:", k_terbaik, "\n")
## 
## K dengan Silhouette tertinggi: 2

============================================================

7. MODEL K-MEANS FINAL

============================================================

K final mengikuti Silhouette tertinggi.

Tetap periksa kesesuaian dengan grafik Elbow.

K_final <- k_terbaik

set.seed(123)

model_kmeans <- kmeans(
  data_scaled,
  centers = K_final,
  nstart = 100,
  iter.max = 200
)

cat("\n========== HASIL K-MEANS FINAL ==========\n")
## 
## ========== HASIL K-MEANS FINAL ==========
print(model_kmeans)
## K-means clustering with 2 clusters of sizes 104, 53
## 
## Cluster means:
##   Skor_Usabilitas Skor_Ketersediaan_Akses Skor_Keamanan_Berkendara
## 1       0.5200947               0.4534167                0.5410415
## 2      -1.0205633              -0.8897234               -1.0616664
##   Skor_Kenyamanan_Sepeda Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan
## 1              0.5526983              0.5121284                 0.5718814
## 2             -1.0845401             -1.0049312                -1.1221824
## 
## Clustering vector:
##   [1] 1 1 1 2 2 1 1 1 1 1 1 2 2 1 1 1 1 2 1 2 2 1 1 2 2 2 1 1 1 1 2 1 1 1 2 1 2
##  [38] 2 2 1 1 1 1 1 1 2 2 1 1 1 1 2 2 2 1 2 1 1 2 2 1 1 1 1 1 1 1 2 1 2 2 2 1 1
##  [75] 1 1 1 2 1 1 2 1 2 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 2 2 2 1 2 2 1 1 1 1 2 1 1
## [112] 1 1 1 1 1 2 1 1 2 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1 2 2 2 1 1 1 1 2 2 2 1 2
## [149] 2 2 1 1 1 1 2 1 2
## 
## Within cluster sum of squares by cluster:
## [1] 268.8701 154.6124
##  (between_SS / total_SS =  54.8 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
cat("\nJumlah cluster:", K_final, "\n")
## 
## Jumlah cluster: 2
cat("Total WSS:", model_kmeans$tot.withinss, "\n")
## Total WSS: 423.4824
cat("Between-cluster SS:", model_kmeans$betweenss, "\n")
## Between-cluster SS: 512.5176
cat("Proporsi variasi antarcluster:",
    round(model_kmeans$betweenss /
            model_kmeans$totss, 4), "\n")
## Proporsi variasi antarcluster: 0.5476
cat("\nPusat cluster terstandardisasi:\n")
## 
## Pusat cluster terstandardisasi:
print(round(model_kmeans$centers, 3))
##   Skor_Usabilitas Skor_Ketersediaan_Akses Skor_Keamanan_Berkendara
## 1           0.520                   0.453                    0.541
## 2          -1.021                  -0.890                   -1.062
##   Skor_Kenyamanan_Sepeda Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan
## 1                  0.553                  0.512                     0.572
## 2                 -1.085                 -1.005                    -1.122

============================================================

8. DISTRIBUSI ANGGOTA CLUSTER

============================================================

hasil_cluster <- data_bersih %>%
  mutate(
    Cluster = factor(
      model_kmeans$cluster,
      levels = seq_len(K_final),
      labels = paste("Cluster", seq_len(K_final))
    )
  )

# Menambahkan cluster ke data asli yang barisnya lengkap
data_lengkap_dengan_cluster <- data_lengkap_asli %>%
  mutate(Cluster = hasil_cluster$Cluster)

distribusi_cluster <- hasil_cluster %>%
  count(Cluster, name = "Jumlah") %>%
  mutate(
    Persentase = round(100 * Jumlah / sum(Jumlah), 2)
  )

cat("\n========== DISTRIBUSI CLUSTER ==========\n")
## 
## ========== DISTRIBUSI CLUSTER ==========
print(distribusi_cluster)
## # A tibble: 2 × 3
##   Cluster   Jumlah Persentase
##   <fct>      <int>      <dbl>
## 1 Cluster 1    104       66.2
## 2 Cluster 2     53       33.8
grafik_distribusi <- ggplot(
  distribusi_cluster,
  aes(x = Cluster, y = Jumlah, fill = Cluster)
) +
  geom_col(width = 0.65, show.legend = FALSE) +
  geom_text(
    aes(label = paste0(Jumlah, " (", Persentase, "%)")),
    vjust = -0.4
  ) +
  labs(
    title = "Distribusi Anggota Cluster WESGO",
    x = "Cluster",
    y = "Jumlah responden"
  ) +
  expand_limits(y = max(distribusi_cluster$Jumlah) * 1.15) +
  theme_minimal(base_size = 12)

print(grafik_distribusi)

============================================================

9. FINAL CLUSTER CENTERS PADA SKALA ASLI

============================================================

final_cluster_centers <- hasil_cluster %>%
  group_by(Cluster) %>%
  summarise(
    across(all_of(kolom_aspek), mean),
    .groups = "drop"
  )

cat("\n========== FINAL CLUSTER CENTERS ==========\n")
## 
## ========== FINAL CLUSTER CENTERS ==========
print(final_cluster_centers)
## # A tibble: 2 × 7
##   Cluster   Skor_Usabilitas Skor_Ketersediaan_Akses Skor_Keamanan_Berkendara
##   <fct>               <dbl>                   <dbl>                    <dbl>
## 1 Cluster 1            3.80                    3.67                     4.00
## 2 Cluster 2            2.49                    2.67                     2.64
## # ℹ 3 more variables: Skor_Kenyamanan_Sepeda <dbl>,
## #   Skor_Kecepatan_Respons <dbl>, Skor_Kepuasan_Keseluruhan <dbl>
# Pusat cluster dalam skala standardisasi
centers_scaled <- as.data.frame(model_kmeans$centers)
centers_scaled$Cluster <- paste("Cluster", seq_len(K_final))
centers_scaled <- centers_scaled %>%
  select(Cluster, everything())

cat("\nPusat cluster pada skala standardisasi:\n")
## 
## Pusat cluster pada skala standardisasi:
print(centers_scaled)
##     Cluster Skor_Usabilitas Skor_Ketersediaan_Akses Skor_Keamanan_Berkendara
## 1 Cluster 1       0.5200947               0.4534167                0.5410415
## 2 Cluster 2      -1.0205633              -0.8897234               -1.0616664
##   Skor_Kenyamanan_Sepeda Skor_Kecepatan_Respons Skor_Kepuasan_Keseluruhan
## 1              0.5526983              0.5121284                 0.5718814
## 2             -1.0845401             -1.0049312                -1.1221824

============================================================

10. JARAK ANTARPUSAT CLUSTER

============================================================

Jarak Euclidean antara centroid berdasarkan skala

standardisasi yang dipakai oleh model K-Means.

jarak_centroid <- as.matrix(
  dist(model_kmeans$centers)
)

rownames(jarak_centroid) <- paste("Cluster", seq_len(K_final))
colnames(jarak_centroid) <- paste("Cluster", seq_len(K_final))

cat("\n========== JARAK ANTARPUSAT CLUSTER ==========\n")
## 
## ========== JARAK ANTARPUSAT CLUSTER ==========
print(round(jarak_centroid, 4))
##           Cluster 1 Cluster 2
## Cluster 1    0.0000    3.8208
## Cluster 2    3.8208    0.0000

============================================================

11. PROFIL DAN VISUALISASI CLUSTER

============================================================

profil_long <- final_cluster_centers %>%
  pivot_longer(
    cols = all_of(kolom_aspek),
    names_to = "Aspek",
    values_to = "Rata_rata"
  )

grafik_profil <- ggplot(
  profil_long,
  aes(
    x = Aspek,
    y = Rata_rata,
    color = Cluster,
    group = Cluster
  )
) +
  geom_line(linewidth = 1) +
  geom_point(size = 3) +
  labs(
    title = "Profil Rata-rata Enam Aspek WESGO",
    x = "Aspek",
    y = "Rata-rata skor",
    color = "Cluster"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    axis.text.x = element_text(angle = 35, hjust = 1)
  )

print(grafik_profil)

# Diagram Final Cluster Centers dalam bentuk batang
grafik_centers <- ggplot(
  profil_long,
  aes(x = Cluster, y = Rata_rata, fill = Aspek)
) +
  geom_col(position = position_dodge(width = 0.8), width = 0.7) +
  labs(
    title = "Diagram Final Cluster Centers WESGO",
    x = "Cluster",
    y = "Rata-rata skor",
    fill = "Aspek"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

print(grafik_centers)

# Visualisasi dua dimensi hasil K-Means
grafik_cluster <- fviz_cluster(
  model_kmeans,
  data = data_scaled,
  geom = "point",
  ellipse.type = "norm",
  main = paste("K-Means Clustering WESGO, K =", K_final)
)

print(grafik_cluster)

============================================================

12. EVALUASI SILHOUETTE MODEL FINAL

============================================================

sil_final <- silhouette(
  model_kmeans$cluster,
  jarak_data
)

nilai_sil_final <- mean(sil_final[, "sil_width"])

cat("\n========== EVALUASI MODEL FINAL ==========\n")
## 
## ========== EVALUASI MODEL FINAL ==========
cat("Rata-rata Silhouette:", round(nilai_sil_final, 4), "\n")
## Rata-rata Silhouette: 0.4638
grafik_sil_final <- fviz_silhouette(sil_final) +
  labs(title = "Silhouette Plot Model K-Means Final") +
  theme_minimal()
##   cluster size ave.sil.width
## 1       1  104          0.48
## 2       2   53          0.44
print(grafik_sil_final)

============================================================

13. UJI ANOVA ANTARCLUSTER

============================================================

Catatan:

Variabel yang digunakan untuk membentuk cluster juga diuji

di sini. Karena itu, ANOVA ini bersifat eksploratif/deskriptif

dan bukan validasi independen terhadap kualitas cluster.

hasil_anova <- lapply(kolom_aspek, function(aspek) {
  
  model_anova <- aov(
    hasil_cluster[[aspek]] ~ hasil_cluster$Cluster
  )
  
  tabel <- summary(model_anova)[[1]]
  
  data.frame(
    Aspek = aspek,
    F_hitung = unname(tabel[1, "F value"]),
    P_value = unname(tabel[1, "Pr(>F)"])
  )
})

hasil_anova <- bind_rows(hasil_anova) %>%
  mutate(
    Keputusan = ifelse(
      P_value < 0.05,
      "Signifikan",
      "Tidak signifikan"
    )
  )

cat("\n========== HASIL ANOVA ==========\n")
## 
## ========== HASIL ANOVA ==========
print(hasil_anova)
##                       Aspek F_hitung      P_value  Keputusan
## 1           Skor_Usabilitas 177.7552 1.682201e-27 Signifikan
## 2   Skor_Ketersediaan_Akses 105.9434 2.925627e-19 Signifikan
## 3  Skor_Keamanan_Berkendara 212.3749 7.547496e-31 Signifikan
## 4    Skor_Kenyamanan_Sepeda 235.6899 6.276687e-33 Signifikan
## 5    Skor_Kecepatan_Respons 166.5453 2.431225e-26 Signifikan
## 6 Skor_Kepuasan_Keseluruhan 282.6912 9.113736e-37 Signifikan

============================================================

14. CASE PROCESSING SUMMARY

============================================================

case_summary <- data.frame(
  Keterangan = c(
    "Jumlah data awal",
    "Data lengkap dan dianalisis",
    "Data dikeluarkan karena missing value"
  ),
  Jumlah = c(
    nrow(data_analisis),
    nrow(data_bersih),
    sum(!baris_lengkap)
  )
) %>%
  mutate(
    Persentase = round(
      100 * Jumlah / nrow(data_analisis),
      2
    )
  )

cat("\n========== CASE PROCESSING SUMMARY ==========\n")
## 
## ========== CASE PROCESSING SUMMARY ==========
print(case_summary)
##                              Keterangan Jumlah Persentase
## 1                      Jumlah data awal    157        100
## 2           Data lengkap dan dianalisis    157        100
## 3 Data dikeluarkan karena missing value      0          0

============================================================

15. CROSSTAB DAN CHI-SQUARE

============================================================

Jurnal menggunakan FITUR, DRIVER, dan LAYANAN sebagai

variabel kategorik. Nama kolom tersebut belum tentu ada

pada data WESGO.

Bagian ini hanya berjalan jika ketiga variabel tersedia.

Variabel kategorik harus berasal dari kuesioner yang jelas.

variabel_kategorik <- c("FITUR", "DRIVER", "LAYANAN")

hasil_crosstab <- list()
hasil_chisq_list <- list()

if (all(variabel_kategorik %in% names(data_lengkap_dengan_cluster))) {
  
  for (v in variabel_kategorik) {
    
    cat("\n========================================\n")
    cat("CROSSTAB CLUSTER x", v, "\n")
    cat("========================================\n")
    
    data_uji <- data_lengkap_dengan_cluster %>%
      filter(!is.na(.data[[v]]))
    
    tab <- table(
      Cluster = data_uji$Cluster,
      Penilaian = data_uji[[v]]
    )
    
    print(tab)
    
    hasil_crosstab[[v]] <- as.data.frame.matrix(tab)
    
    if (nrow(tab) >= 2 && ncol(tab) >= 2) {
      
      uji_chisq <- suppressWarnings(
        chisq.test(tab, correct = FALSE)
      )
      
      cat("\nHasil uji Chi-Square:\n")
      print(uji_chisq)
      
      cat("\nFrekuensi harapan:\n")
      print(round(uji_chisq$expected, 3))
      
      cat(
        "\nSel dengan frekuensi harapan < 5:",
        sum(uji_chisq$expected < 5),
        "dari", length(uji_chisq$expected), "sel\n"
      )
      
      hasil_chisq_list[[v]] <- data.frame(
        Variabel = v,
        Chi_square = unname(uji_chisq$statistic),
        df = unname(uji_chisq$parameter),
        P_value = uji_chisq$p.value,
        Sel_expected_di_bawah_5 =
          sum(uji_chisq$expected < 5)
      )
      
      data_grafik <- as.data.frame(tab)
      
      grafik_crosstab <- ggplot(
        data_grafik,
        aes(
          x = Penilaian,
          y = Freq,
          fill = Cluster
        )
      ) +
        geom_col(position = "dodge") +
        labs(
          title = paste("Distribusi", v, "menurut Cluster"),
          x = "Kategori penilaian",
          y = "Jumlah responden",
          fill = "Cluster"
        ) +
        theme_minimal(base_size = 12) +
        theme(
          axis.text.x = element_text(
            angle = 30,
            hjust = 1
          )
        )
      
      print(grafik_crosstab)
      
    } else {
      cat("Chi-Square tidak dijalankan: tabel kurang dari 2 baris atau kolom.\n")
    }
  }
  
} else {
  
  cat(
    "\nCrosstab dan Chi-Square belum dijalankan.\n",
    "Kolom FITUR, DRIVER, dan LAYANAN tidak seluruhnya tersedia.\n",
    "Periksa nama variabel kategorik pada data WESGO sebelum melanjutkan.\n",
    sep = ""
  )
}
## 
## Crosstab dan Chi-Square belum dijalankan.
## Kolom FITUR, DRIVER, dan LAYANAN tidak seluruhnya tersedia.
## Periksa nama variabel kategorik pada data WESGO sebelum melanjutkan.
if (length(hasil_chisq_list) > 0) {
  ringkasan_chisq <- bind_rows(hasil_chisq_list)
} else {
  ringkasan_chisq <- data.frame(
    Keterangan = "Tidak ada hasil Chi-Square yang dihitung."
  )
}

============================================================

16. RINGKASAN MODEL

============================================================

ringkasan_model <- data.frame(
  Jumlah_Responden = nrow(data_bersih),
  Jumlah_Aspek = ncol(data_bersih),
  K_Final = K_final,
  WSS_Final = model_kmeans$tot.withinss,
  Between_SS = model_kmeans$betweenss,
  Silhouette_Final = nilai_sil_final
)

cat("\n========== RINGKASAN MODEL ==========\n")
## 
## ========== RINGKASAN MODEL ==========
print(ringkasan_model)
##   Jumlah_Responden Jumlah_Aspek K_Final WSS_Final Between_SS Silhouette_Final
## 1              157            6       2  423.4824   512.5176        0.4638061
cat("\nAspek dengan rata-rata tertinggi:\n")
## 
## Aspek dengan rata-rata tertinggi:
print(
  rata_rata_aspek[
    which.max(rata_rata_aspek$Rata_rata),
  ]
)
## # A tibble: 1 × 2
##   Aspek                    Rata_rata
##   <chr>                        <dbl>
## 1 Skor_Keamanan_Berkendara      3.54
cat("\nAspek dengan rata-rata terendah:\n")
## 
## Aspek dengan rata-rata terendah:
print(
  rata_rata_aspek[
    which.min(rata_rata_aspek$Rata_rata),
  ]
)
## # A tibble: 1 × 2
##   Aspek                  Rata_rata
##   <chr>                      <dbl>
## 1 Skor_Kecepatan_Respons      3.31
cat("\n========================================\n")
## 
## ========================================
cat("SELURUH ANALISIS SELESAI\n")
## SELURUH ANALISIS SELESAI
cat("========================================\n")
## ========================================