library(readr)
## Warning: package 'readr' was built under R version 4.5.3
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'forcats' was built under R version 4.5.3
## Warning: package 'lubridate' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ purrr 1.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.1 ✔ tibble 3.3.0
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(flexclust)
## Warning: package 'flexclust' was built under R version 4.5.3
library(dbscan)
## Warning: package 'dbscan' was built under R version 4.5.3
##
## Attaching package: 'dbscan'
##
## The following object is masked from 'package:stats':
##
## as.dendrogram
library(meanShiftR)
library(e1071)
## Warning: package 'e1071' was built under R version 4.5.3
##
## Attaching package: 'e1071'
##
## The following object is masked from 'package:flexclust':
##
## bclust
##
## The following object is masked from 'package:ggplot2':
##
## element
library(cluster)
## Warning: package 'cluster' was built under R version 4.5.3
library(fpc)
## Warning: package 'fpc' was built under R version 4.5.3
##
## Attaching package: 'fpc'
##
## The following object is masked from 'package:dbscan':
##
## dbscan
library(mclust)
## Warning: package 'mclust' was built under R version 4.5.3
## Package 'mclust' version 6.1.2
## Type 'citation("mclust")' for citing this R package in publications.
##
## Attaching package: 'mclust'
##
## The following object is masked from 'package:dplyr':
##
## count
##
## The following object is masked from 'package:purrr':
##
## map
library(corrplot)
## corrplot 0.95 loaded
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
df <- read_csv("SeoulBikeData.csv", locale = locale(encoding = "latin1"))
## Rows: 8760 Columns: 14
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): Date, Seasons, Holiday, Functioning Day
## dbl (10): Rented Bike Count, Hour, Temperature(°C), Humidity(%), Wind speed ...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(df)
## # A tibble: 6 × 14
## Date `Rented Bike Count` Hour `Temperature(°C)` `Humidity(%)`
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 01/12/2017 254 0 -5.2 37
## 2 01/12/2017 204 1 -5.5 38
## 3 01/12/2017 173 2 -6 39
## 4 01/12/2017 107 3 -6.2 40
## 5 01/12/2017 78 4 -6 36
## 6 01/12/2017 100 5 -6.4 37
## # ℹ 9 more variables: `Wind speed (m/s)` <dbl>, `Visibility (10m)` <dbl>,
## # `Dew point temperature(°C)` <dbl>, `Solar Radiation (MJ/m2)` <dbl>,
## # `Rainfall(mm)` <dbl>, `Snowfall (cm)` <dbl>, Seasons <chr>, Holiday <chr>,
## # `Functioning Day` <chr>
Dipilih 10 fitur numerik utama (kolom 2 sampai 11) yang bersifat kontinu, yaitu dari Rented Bike Count hingga Snowfall. Pemilihan ini dilakukan karena metode clustering berbasis jarak seperti K-Means dan K-Medians bekerja lebih baik pada data dengan nilai yang bertahap, bukan data diskrit.
Fitur-fitur tersebut mewakili kondisi lingkungan seperti suhu, kelembapan, dan radiasi matahari, serta kondisi cuaca seperti hujan dan salju, yang dapat memengaruhi keputusan seseorang untuk menyewa sepeda.
Sementara itu, data kategorikal dan biner seperti Seasons, Holiday, dan Functioning Day tidak digunakan agar tidak memengaruhi perhitungan jarak. Sehingga, hasil pengelompokan lebih fokus pada pola aktivitas dan kondisi lingkungan yang dapat diukur.
df_selected <- df[, c("Rented Bike Count", "Hour", "Temperature(°C)", "Humidity(%)",
"Wind speed (m/s)", "Visibility (10m)", "Dew point temperature(°C)",
"Solar Radiation (MJ/m2)", "Rainfall(mm)", "Snowfall (cm)")]
Diambil 2000 data di awal agar mengurangi beban komputasi
set.seed(123)
df_sample_raw <- df_selected[sample(1:nrow(df_selected), 2000), ]
str(df_sample_raw)
## tibble [2,000 × 10] (S3: tbl_df/tbl/data.frame)
## $ Rented Bike Count : num [1:2000] 705 289 126 378 236 ...
## $ Hour : num [1:2000] 14 14 5 9 17 10 8 1 20 18 ...
## $ Temperature(°C) : num [1:2000] 18.3 12.1 1.8 10.4 2.4 18.2 21.1 18.9 10.7 22.2 ...
## $ Humidity(%) : num [1:2000] 43 97 34 81 17 36 70 55 70 96 ...
## $ Wind speed (m/s) : num [1:2000] 2.2 2 0.9 1.4 3.1 1 0.3 2.3 2.8 1.2 ...
## $ Visibility (10m) : num [1:2000] 1509 177 1990 2000 1990 ...
## $ Dew point temperature(°C): num [1:2000] 5.4 11.6 -12.4 7.2 -20.2 2.8 15.4 9.6 5.4 21.5 ...
## $ Solar Radiation (MJ/m2) : num [1:2000] 2.43 0.2 0 0.36 0.78 1.99 0.9 0 0 0.09 ...
## $ Rainfall(mm) : num [1:2000] 0 0 0 0 0 0 0 0 0 3.5 ...
## $ Snowfall (cm) : num [1:2000] 0 0 0 0 0 0 0 0 0 0 ...
Variabel Terikat (Rented Bike Count): Data menunjukkan variasi yang cukup besar pada jumlah sepeda yang disewa, dengan nilai pada sampel berada di kisaran ratusan (misalnya 705, 289, 126). Hal ini menunjukkan adanya perbedaan penggunaan yang cukup jelas pada waktu tertentu.
Variabel Waktu (Hour): Data mencakup waktu selama 24 jam (0–23), sehingga dapat menunjukkan perbedaan antara jam sibuk dan jam sepi.
Kondisi Termal (Temperature & Dew Point): Suhu pada data memiliki rentang yang cukup luas, dari dingin (1.8°C) hingga hangat (22.2°C). Nilai Temperature dan Dew Point terlihat mirip, yang menunjukkan adanya hubungan yang kuat antar kedua variabel.
Kondisi Atmosferik (Humidity & Visibility): Kelembapan udara berubah cukup besar, dari 17% hingga 97%. Sementara itu, sebagian besar data jarak pandang berada di nilai tinggi (2000 m), tetapi ada juga yang rendah (177 m) yang kemungkinan terkait dengan kondisi lembap atau hujan.
Faktor Cuaca Ekstrem (Rainfall & Snowfall): Sebagian besar data bernilai 0, yang berarti hujan dan salju jarang terjadi. Variabel ini kemungkinan akan membentuk kelompok khusus atau dianggap sebagai data pencilan (noise) pada metode seperti DBSCAN.
summary(df_sample_raw)
## Rented Bike Count Hour Temperature(°C) Humidity(%)
## Min. : 0.0 Min. : 0.00 Min. :-17.50 Min. : 0.00
## 1st Qu.: 188.8 1st Qu.: 5.00 1st Qu.: 3.50 1st Qu.:43.00
## Median : 480.0 Median :11.00 Median : 13.90 Median :57.00
## Mean : 691.4 Mean :11.47 Mean : 12.94 Mean :58.34
## 3rd Qu.:1052.0 3rd Qu.:17.00 3rd Qu.: 22.60 3rd Qu.:74.00
## Max. :3556.0 Max. :23.00 Max. : 39.30 Max. :98.00
## Wind speed (m/s) Visibility (10m) Dew point temperature(°C)
## Min. :0.000 Min. : 27.0 Min. :-30.600
## 1st Qu.:1.000 1st Qu.: 977.8 1st Qu.: -4.325
## Median :1.500 Median :1705.0 Median : 5.400
## Mean :1.727 Mean :1446.5 Mean : 4.193
## 3rd Qu.:2.300 3rd Qu.:1999.2 3rd Qu.: 15.000
## Max. :7.400 Max. :2000.0 Max. : 26.800
## Solar Radiation (MJ/m2) Rainfall(mm) Snowfall (cm)
## Min. :0.0000 Min. : 0.0000 Min. :0.0000
## 1st Qu.:0.0000 1st Qu.: 0.0000 1st Qu.:0.0000
## Median :0.0100 Median : 0.0000 Median :0.0000
## Mean :0.5601 Mean : 0.1212 Mean :0.0676
## 3rd Qu.:0.9200 3rd Qu.: 0.0000 3rd Qu.:0.0000
## Max. :3.5200 Max. :29.5000 Max. :8.8000
Volume Penyewaan (Rented Bike Count): Rata-rata penyewaan sepeda adalah 691,4 unit, tetapi nilai median hanya 480 unit. Perbedaan ini menunjukkan bahwa data tidak merata dan cenderung menceng ke kanan. Hal ini berarti ada waktu tertentu dengan lonjakan penyewaan yang sangat tinggi, terlihat juga dari nilai maksimum yang mencapai 3.556.
Variabel Lingkungan Utama (Suhu dan Jam): Suhu memiliki rentang yang sangat luas, dari -17,5°C hingga 39,3°C, yang menunjukkan data mencakup semua musim di Seoul. Sementara itu, variabel jam memiliki nilai rata-rata dan median sekitar 11, yang menunjukkan data tersebar merata sepanjang hari.
Kondisi Atmosferik: Kelembapan memiliki rata-rata sekitar 58,34%, dengan nilai minimum 0% yang kemungkinan menunjukkan kondisi sangat kering atau data yang tidak biasa. Jarak pandang sebagian besar berada pada kondisi baik, terlihat dari nilai median 1.705 m dan Q3 sebesar 1.999 m.
Faktor Penghambat (Curah Hujan dan Salju): Variabel hujan dan salju sebagian besar bernilai 0, yang berarti lebih dari 75% data berada pada kondisi tanpa hujan atau salju. Nilai rata-rata yang sangat kecil menunjukkan bahwa hujan merupakan kondisi yang jarang terjadi dan dapat dianggap sebagai data pencilan.
korelasi <- cor(df_sample_raw)
corrplot(korelasi, method = "color", type = "upper",
tl.col = "black", addCoef.col = "black", tl.cex = 0.7, number.cex =0.5)
- Hubungan Terkuat dengan Jumlah Penyewaan: Variabel Temperature (°C)
memiliki korelasi positif paling kuat terhadap Rented Bike Count sebesar
0.55. Hal ini menunjukkan bahwa peningkatan suhu udara secara signifikan
mendorong masyarakat Seoul untuk menggunakan layanan sepeda publik.
Selain suhu, variabel Hour (0.42) dan Dew point temperature (0.39) juga
menunjukkan pengaruh positif yang cukup berarti.
Korelasi Negatif Kelembapan: Variabel Humidity (%) memiliki korelasi negatif terhadap jumlah penyewaan sebesar -0.20. Artinya, ketika kelembapan udara meningkat (kondisi udara yang lebih lembap atau mendekati hujan), jumlah penyewaan cenderung mengalami penurunan.
Indikasi Multikolinearitas: Terdapat hubungan yang sangat kuat antara Temperature (°C) dan Dew point temperature (°C) dengan nilai korelasi mencapai 0.92. Dalam analisis multivariat, korelasi yang sangat tinggi ini menunjukkan bahwa kedua variabel tersebut membawa informasi yang hampir serupa.
Hubungan Kelembapan dan Jarak Pandang: Terlihat korelasi negatif yang cukup kuat antara Humidity (%) dan Visibility (10m) sebesar -0.54. Hal ini logis secara meteorologis, di mana tingkat kelembapan yang tinggi seringkali dibarengi dengan berkurangnya jarak pandang (seperti kondisi berkabut atau mendung).
Variabel dengan Pengaruh Lemah: Variabel cuaca ekstrem seperti Rainfall (mm) (-0.11) dan Snowfall (cm) (-0.14) menunjukkan korelasi negatif yang lemah terhadap penyewaan pada tingkat sampel ini. Hal ini kemungkinan disebabkan oleh sifat datanya yang didominasi oleh nilai nol (kondisi cuaca cerah).
sum(is.na(df_sample_raw))
## [1] 0
df_scaled <- as.data.frame(scale(df_sample_raw))
set.seed(123)
wss <- sapply(1:10, function(k) {
kmeans(df_scaled, centers = k, nstart = 20)$tot.withinss
})
par(mfrow = c(1, 1))
plot(1:10, wss, type = "b", pch = 19, frame = FALSE,
xlab = "Number of clusters K",
ylab = "Total within-clusters sum of squares",
main = "Elbow Method (Sample 2000)")
##### Silhouette Analysis(dengan sample)
set.seed(123)
avg_sil_values <- sapply(2:10, function(k) {
km_res <- kmeans(df_scaled, centers = k, nstart = 25)
ss <- silhouette(km_res$cluster, dist(df_scaled))
mean(ss[, 3])
})
## Warning: did not converge in 10 iterations
plot(2:10, avg_sil_values, type = "b", pch = 19, frame = FALSE,
xlab = "Number of clusters K",
ylab = "Average Silhouette Width",
main = "Silhouette Analysis (Sample 2000)")
Terdapat perbedaan dalam penentuan jumlah klaster optimal antara Elbow
Method (k=3) dan Silhouette Analysis (k=7). Hal ini wajar terjadi pada
data yang memiliki banyak variabel dan sebaran yang cukup padat. Elbow
Method menunjukkan penurunan nilai yang paling jelas sampai k=3,
sedangkan Silhouette memberikan nilai terbaik pada k=7. Namun, untuk
menjaga keseimbangan antara hasil yang baik dan kemudahan dalam memahami
hasilnya, dipilih k=3 sebagai jumlah klaster akhir.
Pemilihan ini dilakukan karena tiga kelompok utama yaiturendah, sedang, dan tinggi sehinggalebih mudah dipahami dan lebih berguna untuk kebutuhan operasional. Jika menggunakan tujuh klaster, hasilnya akan terlalu detail dan sulit dibedakan secara jelas dalam praktik.
set.seed(123)
k_fix <- 3
km_res <- kmeans(df_scaled, centers = k_fix, nstart = 25)
kmed_res <- flexclust::kcca(df_scaled, k = k_fix, family = kccaFamily("kmedians"))
## Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
## Also defined by 'kernlab'
## Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
## Also defined by 'kernlab'
db_res <- dbscan(df_scaled, eps = 1.1, MinPts = 9)
bw_vec <- rep(1.2, ncol(df_scaled))
ms_res <- meanShift(as.matrix(df_scaled), bandwidth = bw_vec)
fcm_res <- cmeans(df_scaled, centers = k_fix, m = 2)
par(mfrow = c(2, 3), mar = c(4, 4, 2, 1))
plot(df_scaled[,3], df_scaled[,1], col = km_res$cluster, main = "K-means", pch=19, cex=0.5)
plot(df_scaled[,3], df_scaled[,1], col = clusters(kmed_res), main = "K-medians", pch=19, cex=0.5)
plot(df_scaled[,3], df_scaled[,1], col = db_res$cluster + 1L, main = "DBSCAN", pch=19, cex=0.5)
plot(df_scaled[,3], df_scaled[,1], col = ms_res$assignment, main = "Mean Shift", pch=19, cex=0.5)
plot(df_scaled[,3], df_scaled[,1], col = fcm_res$cluster, main = "Fuzzy C-means", pch=19, cex=0.5)
plot(df_scaled[,3], df_scaled[,1], col = as.numeric(cut(df_scaled[,1], 3)), main = "Pseudo-Original", pch=19, cex=0.5)
K-Means dan K-Medians Menghasilkan pembagian data yang jelas menjadi tiga kelompok (rendah, sedang, tinggi). K-Medians lebih tahan terhadap data pencilan karena menggunakan median.
DBSCAN dan Mean Shift Lebih fokus pada kepadatan data, tidak membagi secara merata. DBSCAN cenderung membentuk satu klaster utama dan menganggap data lain sebagai noise, sedangkan Mean Shift menghasilkan klaster yang menyebar tanpa batas jelas.
Fuzzy C-Means Menunjukkan klaster yang saling tumpang tindih karena satu data bisa masuk ke lebih dari satu kelompok, sehingga batas klaster lebih fleksibel.
Pseudo-Original Data awal bersifat kontinu tanpa batas yang jelas, sehingga beberapa metode menghasilkan klaster yang tidak tegas.
Berdasarkan hasil pengujian:
Stabilitas :K-Means dan K-Medians memiliki hasil paling stabil dengan nilai ARI sebesar 0,8232.
Kualitas Klaster :Nilai Silhouette Score sebesar 0,2313 menunjukkan pemisahan klaster yang cukup baik.
Perbandingan Metode :K-Means paling mendekati pola data asli dalam membagi tiga kelompok.
DBSCAN dan Mean Shift kurang optimal karena menghasilkan klaster yang kurang jelas atau terlalu menyatu, sehingga kurang efektif untuk kebutuhan operasional.
Karena tidak tersedia label asli (ground truth), maka evaluasi tidak dapat menggunakan Adjusted Rand Index (ARI). Oleh karena itu, penilaian dilakukan menggunakan metrik internal, yaitu Silhouette dan Dunn Index.
# Hitung Jarak (Distance Matrix)
d_mat <- dist(df_scaled)
# Mengukur seberapa baik sebuah objek cocok dengan klasternya (Range -1 ke 1)
sil_val <- mean(silhouette(km_res$cluster, d_mat)[,3])
print(paste("Average Silhouette Score:", round(sil_val, 4)))
## [1] "Average Silhouette Score: 0.2313"
Nilai ini berada di sekitar 0,2 yang menunjukkan bahwa struktur klaster berada pada tingkat sedang. Pada data nyata seperti penyewaan sepeda, nilai yang tidak mendekati 1 merupakan hal yang wajar karena masih terdapat beberapa data antar klaster yang saling berdekatan atau sedikit tumpang tindih. Meskipun demikian, nilai sebesar 0,23 sudah menunjukkan bahwa hasil pengelompokan memiliki pola yang jelas dan tidak terbentuk secara acak.
Menggunakan stats yang sudah dihitung dari cluster.stats
stats <- fpc::cluster.stats(d_mat, km_res$cluster)
print(paste("Dunn Index:", round(stats$dunn, 6)))
## [1] "Dunn Index: 0.011662"
print(paste("Within-cluster SS:", round(km_res$tot.withinss, 2)))
## [1] "Within-cluster SS: 13392.79"
Dunn Index: 0.011662 Nilai ini menunjukkan perbandingan antara jarak antar klaster dan kepadatan dalam klaster. Meskipun nilainya kecil (sekitar 0,01), hal ini wajar pada dataset dengan banyak fitur dan jumlah data yang besar. Yang penting, nilainya positif, yang berarti tetap terdapat pemisahan antar klaster.
Within-cluster SS: 13392.79 Nilai ini menunjukkan total jarak kuadrat antara data dengan pusat klasternya masing-masing. Semakin kecil nilainya, semakin padat klaster yang terbentuk. Nilai 13.392 untuk 2.000 data menunjukkan bahwa klaster yang dihasilkan sudah cukup baik dan cukup rapat.
Karena tidak tersedia ground truth (label asli), evaluasi dilakukan dengan membandingkan hasil K-Means dan K-Medians untuk melihat tingkat konsistensi antar algoritma.
ari_score <- mclust::adjustedRandIndex(km_res$cluster, clusters(kmed_res))
print(paste("Adjusted Rand Index (KM vs KMed):", round(ari_score, 4)))
## [1] "Adjusted Rand Index (KM vs KMed): 0.8232"
Nilai ini tergolong sangat tinggi (sekitar 82%), yang menunjukkan bahwa hasil pengelompokan dari kedua metode sangat mirip. Meskipun K-Means menggunakan rata-rata dan K-Medians menggunakan nilai tengah, keduanya menghasilkan kelompok yang hampir sama. Hal ini menunjukkan bahwa hasil clustering cukup stabil dan dapat dipercaya.
Pada tahap ini dilakukan perhitungan rata-rata setiap variabel pada masing-masing klaster untuk melihat perbedaan pola penyewaan berdasarkan kondisi lingkungan.
df_cek <- as.data.frame(df_scaled)
df_cek$cluster <- as.factor(km_res$cluster)
profil_klaster <- df_cek %>%
group_by(cluster) %>%
summarise(across(everything(), mean))
print(profil_klaster)
## # A tibble: 3 × 11
## cluster `Rented Bike Count` Hour `Temperature(°C)` `Humidity(%)`
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 1 -0.580 0.0838 -1.16 -0.852
## 2 2 -0.510 -0.605 0.0154 0.881
## 3 3 0.982 0.604 0.825 -0.349
## # ℹ 6 more variables: `Wind speed (m/s)` <dbl>, `Visibility (10m)` <dbl>,
## # `Dew point temperature(°C)` <dbl>, `Solar Radiation (MJ/m2)` <dbl>,
## # `Rainfall(mm)` <dbl>, `Snowfall (cm)` <dbl>
Klaster 1 (Kondisi Cuaca Dingin dan Kering (Penyewaan Rendah)): Klaster ini mencerminkan kondisi lingkungan dengan suhu udara yang sangat rendah (ekstrem dingin). Meskipun tingkat kelembapan udara berada di bawah rata-rata (kondisi kering/cerah), suhu yang terlalu rendah menjadi faktor penghambat utama yang menurunkan minat masyarakat untuk bersepeda. Kondisi ini umumnya merepresentasikan periode musim dingin atau waktu dini hari di mana mobilitas sangat terbatas.
Klaster 2 (Kondisi Lembap dan Pasif (Penyewaan Rendah)): Klaster ini didominasi oleh tingkat kelembapan udara yang tinggi, yang secara signifikan berhubungan dengan kondisi cuaca kurang nyaman seperti mendung, berkabut, atau pasca hujan. Terjadi umumnya di luar jam sibuk, kelompok ini menunjukkan penurunan aktivitas bersepeda karena faktor kenyamanan lingkungan yang rendah, meskipun suhu udara berada pada level normal.
Klaster 3 (Kondisi Ideal dan Jam Sibuk (Penyewaan Tinggi)): Klaster ini merupakan kelompok dengan tingkat penggunaan layanan paling maksimal. Karakteristik utamanya adalah suhu udara yang hangat, cuaca cerah (radiasi matahari tinggi), dan terjadi pada jam-jam sibuk (peak hours). Kombinasi antara kenyamanan termal dan kebutuhan mobilitas yang tinggi mendorong peningkatan jumlah penyewaan sepeda secara signifikan di atas rata-rata.
Analisis pusat massa atau centroid dilakukan untuk mengetahui titik tengah yang mewakili setiap klaster dalam skala standar (scaled). Nilai centroid menunjukkan perbedaan karakteristik tiap klaster dibandingkan dengan rata-rata keseluruhan data (nilai 0). Nilai positif berarti berada di atas rata-rata, sedangkan nilai negatif berarti berada dibawah rata-rata
centroids <- km_res$centers
print(centroids)
## Rented Bike Count Hour Temperature(°C) Humidity(%) Wind speed (m/s)
## 1 -0.5804902 0.08376289 -1.16030782 -0.8519040 0.3938794
## 2 -0.5103838 -0.60488776 0.01537817 0.8808072 -0.4905899
## 3 0.9822748 0.60388213 0.82547176 -0.3494200 0.2531425
## Visibility (10m) Dew point temperature(°C) Solar Radiation (MJ/m2)
## 1 0.5324904 -1.3012897 -0.1957037
## 2 -0.6726482 0.3460486 -0.5245874
## 3 0.3525709 0.5644592 0.7185320
## Rainfall(mm) Snowfall (cm)
## 1 -0.1163938 0.07753084
## 2 0.1781552 0.09169443
## 3 -0.1112689 -0.15704678
Klaster 1: Memiliki nilai suhu udara paling rendah (-1.16) dan titik embun terendah (-1.30). Hal ini menunjukkan bahwa pusat kelompok ini berada pada kondisi suhu yang sangat dingin, sehingga jumlah penyewaan juga berada di bawah rata-rata (- 0.58).
Klaster 2: Ditandai dengan nilai kelembapan paling tinggi (0.88) dan radiasi matahari paling rendah (-0.52). Hal ini menunjukkan bahwa klaster ini berada pada kondisi cuaca yang kurang mendukung seperti mendung atau hujan, sehingga jumlah penyewaan berada di bawah rata-rata (-0.51).
Klaster 3: Memiliki nilai tertinggi pada jumlah penyewaan (0.98), suhu udara (0.82), dan jam sibuk (0.60). Hal ini menunjukkan bahwa klaster ini berada pada kondisi yang paling mendukung, yaitu cuaca hangat dan waktu sibuk, sehingga jumlah penyewaan berada di atas rata-rata.
Analisis ini bertujuan untuk melihat sebaran 2.000 data ke dalam tiga klaster untuk memahami pola penggunaan sepeda di Seoul
jumlah_anggota <- table(km_res$cluster)
persentase_anggota <- prop.table(jumlah_anggota) * 100
distribusi_klaster <- rbind(Jumlah = jumlah_anggota, Persentase = round(persentase_anggota, 2))
print(distribusi_klaster)
## 1 2 3
## Jumlah 514.0 778.0 708.0
## Persentase 25.7 38.9 35.4
Dominasi Klaster 2 (Kondisi Lembap): Klaster 2 merupakan kelompok terbesar dengan 778 data (38,9%). Hal ini menunjukkan bahwa sebagian besar waktu penyewaan terjadi pada kondisi lembap atau di luar jam sibuk, dengan tingkat penggunaan yang cenderung rendah hingga sedang.
Potensi Klaster 3 (Kondisi Ideal): Klaster 3 memiliki 708 data (35,4%) dan menunjukkan tingkat penyewaan tertinggi. Hal ini menandakan adanya aktivitas penggunaan yang tinggi pada kondisi cuaca hangat dan jam sibuk.
Klaster 1 (Cuaca Dingin): Klaster ini merupakan yang paling kecil dengan 514 data (25,7%), yang menggambarkan kondisi cuaca dingin atau waktu dini hari dengan tingkat penggunaan rendah.
Dapat disimpulkan secara umum, pembagian data cukup merata antar klaster. Hal ini menunjukkan bahwa K-Means mampu mengelompokkan data dengan baik berdasarkan kondisi lingkungan, bukan hanya memisahkan data ekstrem.
noise_idx <- which(db_res$cluster == 0)
length(noise_idx)
## [1] 416
length(noise_idx) / nrow(df_scaled)
## [1] 0.208
Berdasarkan hasil DBSCAN diperoleh jumlah data noise 416 dan proporsi noise 0,208, artinya sekitar 20,8% (416 data) tidak termasuk kedalam cluster manapun karena berada pada pola dengan kepadatan rendah, sehingga di kategorikan sebagai noise/anomali.
df_sample_raw$db_cluster <- db_res$cluster
aggregate(. ~ db_cluster, data = df_sample_raw, mean)
## db_cluster Rented Bike Count Hour Temperature(°C) Humidity(%)
## 1 0 705.8197 12.75000 12.50072 61.33413
## 2 1 686.8794 11.12000 13.02083 57.61206
## 3 2 825.1111 12.55556 18.97778 48.22222
## Wind speed (m/s) Visibility (10m) Dew point temperature(°C)
## 1 2.347837 1178.5505 4.323317
## 2 1.560825 1522.0216 4.139175
## 3 2.022222 607.7778 7.555556
## Solar Radiation (MJ/m2) Rainfall(mm) Snowfall (cm)
## 1 0.7461058 0.546153846 0.314182692
## 2 0.5015492 0.009650794 0.002857143
## 3 2.1977778 0.000000000 0.000000000
pca <- prcomp(df_scaled)
plot(pca$x[,1:2],
col = ifelse(db_res$cluster == 0, "red", "black"),
pch = 16,
main = "Noise Detection(DBSCAN)")
Titik merah : noise Titik Hitam : cluster utama
Grafik menujukkan bahwa noise tersebar di area pinggir dan luar kepadatan utama, dan beberapa titik berada jauh dari pusat distribusi. Noise ini merepresentasikan kondisi penyewaan sepeda yang yang terjadi pada kondisi tidak normal. Kondisi tersebut berkaitan dengan cuaca ekstrem, visibilitas rendah, waktu tidak umum.
membership <- fcm_res$membership
head(membership)
## 1 2 3
## [1,] 0.1993205 0.5301231 0.2705565
## [2,] 0.5307653 0.2417271 0.2275076
## [3,] 0.2477291 0.1735400 0.5787308
## [4,] 0.5239964 0.2184852 0.2575184
## [5,] 0.1848194 0.2440939 0.5710867
## [6,] 0.3121784 0.3914054 0.2964162
Tabel ini menunjukkan hubungan setiap data dengan klaster. Jika sebuah data memiliki nilai yang hampir sama atau sama besar di dua/lebih klaster, maka data tersebut sangat ambigu.
max_membership <- apply(membership, 1, max)
summary(max_membership)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.3356 0.4573 0.5240 0.5233 0.5803 0.8125
max_membership untuk mengetahui seberapa yakin model mengelompokkan data. Jika rata-rata (mean) mendekati 1.0, berarti sebagian besar data terklasifikasi dengan sangat jelas.
ambiguous_idx <- which(max_membership < 0.6)
length(ambiguous_idx)
## [1] 1623
length(ambiguous_idx) / nrow(df_scaled)
## [1] 0.8115
Jumlah data ambigu diperoleh sebanyak 1.623 data atau sekitar 81,15% dari total data termasuk dalam kategori ambigu (memiliki nilai maximum membership < 0,6). Proporsi ini menunjukkan bahwa mayoritas data berada pada kondisi yang tidak dapat diklasifikasikan secara tegas ke dalam satu klaster.
ambiguous_data <- df_sample_raw[ambiguous_idx, ]
head(ambiguous_data)
## # A tibble: 6 × 11
## `Rented Bike Count` Hour `Temperature(°C)` `Humidity(%)` `Wind speed (m/s)`
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 705 14 18.3 43 2.2
## 2 289 14 12.1 97 2
## 3 126 5 1.8 34 0.9
## 4 378 9 10.4 81 1.4
## 5 236 17 2.4 17 3.1
## 6 708 10 18.2 36 1
## # ℹ 6 more variables: `Visibility (10m)` <dbl>,
## # `Dew point temperature(°C)` <dbl>, `Solar Radiation (MJ/m2)` <dbl>,
## # `Rainfall(mm)` <dbl>, `Snowfall (cm)` <dbl>, db_cluster <dbl>
pca <- prcomp(df_scaled)
plot(pca$x[,1:2],
col = ifelse(max_membership < 0.6, "orange", "blue"),
pch = 16,
main = "Ambiguity in Fuzzy C-Means")
Titik bitu : data ambigu Ttitik Oranye : data jelas
Visualisasi menunjukkan bahwa data dengan tingkat ambiguitas tinggi cenderung berada di area tengah distribusi, yaitu pada wilayah perpotongan antar klaster. Hal ini berbeda dengan hasil DBSCAN, di mana data anomali (noise) berada di area pinggir distribusi.
Ambiguitas ini mencerminkan kondisi transisi dalam pola penyewaan sepeda. Kondisi tersebut umumnya terjadi pada: 1. Peralihan waktu, seperti dari jam sibuk ke jam sepi atau sebaliknya 2. Kondisi cuaca moderat, seperti suhu sedang dan kelembapan yang tidak ekstrem 3. Situasi lingkungan yang tidak sepenuhnya mendukung maupun menghambat aktivitas bersepeda
Temuan ini menunjukkan bahwa pola penyewaan sepeda di Seoul tidak sepenuhnya terbagi secara diskrit ke dalam kelompok rendah, sedang, dan tinggi, melainkan bersifat kontinu. Dengan demikian, metode Fuzzy C-Means memberikan representasi yang lebih fleksibel dalam menangkap dinamika data, khususnya dalam kondisi transisi yang tidak dapat dijelaskan secara tegas oleh metode clustering konvensional.
fuzzy_cluster <- apply(membership, 1, which.max)
table(fuzzy_cluster)
## fuzzy_cluster
## 1 2 3
## 731 691 578
Hasil penentuan cluster dominan pada metode Fuzzy C-Means menunjukkan bahwa sebanyak 731 data termasuk dalam cluster 1, 691 data pada cluster 2, dan 578 data pada cluster 3. Distribusi ini relatif seimbang, yang menunjukkan bahwa pola penyewaan sepeda tersebar cukup merata pada ketiga kelompok. Namun, jika dikaitkan dengan nilai membership sebelumnya, sebagian besar data memiliki tingkat ambiguitas yang tinggi, sehingga meskipun dapat diklasifikasikan ke dalam cluster dominan, batas antar klaster tidak bersifat tegas.