Dalam konteks evaluasi akademik, mahasiswa tidak selalu hanya berbeda pada nilai ujian, tetapi juga pada pola kehadiran, partisipasi praktikum, dan performa akademik sebelumnya. Oleh karena itu, pendekatan clustering dapat digunakan untuk mengidentifikasi kelompok mahasiswa dengan karakteristik yang mirip, sehingga dapat membantu memahami pola performa belajar secara lebih menyeluruh.
Analisis ini bertujuan untuk mengelompokkan mahasiswa berdasarkan karakteristik performa akademik dan partisipasi pembelajaran, menggunakan beberapa metode clustering agar dapat dibandingkan metode mana yang paling sesuai dengan struktur data.
# Cek apakah file sudah ada
if (!file.exists("student_data.csv")) {
file_id <- "1Mzqami9CEzT8nGVMV323fmk0Q-mYKCpd"
url <- paste0("https://drive.google.com/uc?id=", file_id)
download.file(url, destfile = "student_data.csv", mode = "wb")
}
# Load data
df <- read.csv("student_data.csv")
# Validasi
if (nrow(df) == 0) {
stop("Data gagal dimuat!")
}
head(df)
## student_id name age gender quiz1_marks quiz2_marks quiz3_marks
## 1 1 Kristina Vaughan 19 Male 8.0 5.7 7.4
## 2 2 Rodney Daniels 21 Male 10.0 7.9 4.1
## 3 3 Jose Nash 19 Female 7.5 1.2 0.3
## 4 4 Nicole Martin 21 Male 5.2 2.5 9.9
## 5 5 Shelby Smith 21 Female 5.9 6.3 2.0
## 6 6 Austin Griffin 22 Male 5.8 10.0 5.5
## total_assignments assignments_submitted midterm_marks final_marks
## 1 5 NA 30.0 36.5
## 2 5 NA 25.4 33.0
## 3 5 NA 14.4 24.8
## 4 5 NA 17.7 41.0
## 5 5 NA 23.8 31.0
## 6 5 NA 9.4 50.0
## previous_gpa total_lectures lectures_attended total_lab_sessions
## 1 2.57 12 4 6
## 2 2.40 12 1 6
## 3 2.99 12 0 6
## 4 1.68 12 9 6
## 5 2.53 12 7 6
## 6 1.83 12 11 6
## labs_attended
## 1 1
## 2 5
## 3 0
## 4 0
## 5 4
## 6 3
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.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(GGally)
library(corrplot)
## corrplot 0.95 loaded
library(skimr)
library(flexclust)
library(dbscan)
##
## Attaching package: 'dbscan'
##
## The following object is masked from 'package:stats':
##
## as.dendrogram
library(meanShiftR)
library(e1071)
##
## Attaching package: 'e1071'
##
## The following object is masked from 'package:flexclust':
##
## bclust
##
## The following object is masked from 'package:ggplot2':
##
## element
library(cluster)
library(fpc)
##
## Attaching package: 'fpc'
##
## The following object is masked from 'package:dbscan':
##
## dbscan
library(mclust)
## 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(rsconnect)
# Struktur data
str(df)
## 'data.frame': 300 obs. of 16 variables:
## $ student_id : int 1 2 3 4 5 6 7 8 9 10 ...
## $ name : chr "Kristina Vaughan" "Rodney Daniels" "Jose Nash" "Nicole Martin" ...
## $ age : int 19 21 19 21 21 22 23 23 23 18 ...
## $ gender : chr "Male" "Male" "Female" "Male" ...
## $ quiz1_marks : num 8 10 7.5 5.2 5.9 5.8 7.4 6.8 7.7 9.1 ...
## $ quiz2_marks : num 5.7 7.9 1.2 2.5 6.3 10 1.1 5.2 1.6 8.3 ...
## $ quiz3_marks : num 7.4 4.1 0.3 9.9 2 5.5 1.5 1.1 6.5 3 ...
## $ total_assignments : int 5 5 5 5 5 5 5 5 5 5 ...
## $ assignments_submitted: logi NA NA NA NA NA NA ...
## $ midterm_marks : num 30 25.4 14.4 17.7 23.8 9.4 22 12.8 16.1 16.9 ...
## $ final_marks : num 36.5 33 24.8 41 31 50 50 33.1 29.8 45 ...
## $ previous_gpa : num 2.57 2.4 2.99 1.68 2.53 1.83 2.88 3.54 3.21 3.48 ...
## $ total_lectures : int 12 12 12 12 12 12 12 12 12 12 ...
## $ lectures_attended : int 4 1 0 9 7 11 3 6 4 1 ...
## $ total_lab_sessions : int 6 6 6 6 6 6 6 6 6 6 ...
## $ labs_attended : int 1 5 0 0 4 3 6 0 6 3 ...
# Statistik awal
summary(df)
## student_id name age gender
## Min. : 1.00 Length:300 Min. :18.00 Length:300
## 1st Qu.: 75.75 Class :character 1st Qu.:20.00 Class :character
## Median :150.50 Mode :character Median :22.00 Mode :character
## Mean :150.50 Mean :21.55
## 3rd Qu.:225.25 3rd Qu.:23.00
## Max. :300.00 Max. :25.00
## quiz1_marks quiz2_marks quiz3_marks total_assignments
## Min. : 2.800 Min. : 0.000 Min. : 0.000 Min. :5
## 1st Qu.: 6.075 1st Qu.: 4.000 1st Qu.: 3.600 1st Qu.:5
## Median : 7.300 Median : 5.800 Median : 5.600 Median :5
## Mean : 7.255 Mean : 5.867 Mean : 5.469 Mean :5
## 3rd Qu.: 8.400 3rd Qu.: 7.700 3rd Qu.: 7.200 3rd Qu.:5
## Max. :10.000 Max. :10.000 Max. :10.000 Max. :5
## assignments_submitted midterm_marks final_marks previous_gpa
## Mode:logical Min. : 0.00 Min. : 0.00 Min. :0.610
## NA's:300 1st Qu.:14.22 1st Qu.:29.88 1st Qu.:2.328
## Median :21.35 Median :39.60 Median :2.800
## Mean :19.97 Mean :37.38 Mean :2.806
## 3rd Qu.:27.43 3rd Qu.:49.55 3rd Qu.:3.320
## Max. :30.00 Max. :50.00 Max. :4.000
## total_lectures lectures_attended total_lab_sessions labs_attended
## Min. :12 Min. : 0.000 Min. :6 Min. :0.00
## 1st Qu.:12 1st Qu.: 3.000 1st Qu.:6 1st Qu.:1.00
## Median :12 Median : 6.000 Median :6 Median :3.00
## Mean :12 Mean : 6.013 Mean :6 Mean :3.11
## 3rd Qu.:12 3rd Qu.: 9.000 3rd Qu.:6 3rd Qu.:5.00
## Max. :12 Max. :12.000 Max. :6 Max. :6.00
# Missing value
colSums(is.na(df))
## student_id name age
## 0 0 0
## gender quiz1_marks quiz2_marks
## 0 0 0
## quiz3_marks total_assignments assignments_submitted
## 0 0 300
## midterm_marks final_marks previous_gpa
## 0 0 0
## total_lectures lectures_attended total_lab_sessions
## 0 0 0
## labs_attended
## 0
Interpretasi
Berdasarkan pemeriksaan struktur data, dataset memiliki 300 mahasiswa dan 16 variabel. Namun, tidak semua variabel layak langsung digunakan dalam analisis clustering. Variabel seperti student_id dan name hanya berfungsi sebagai identitas, sehingga tidak memiliki makna analitis dalam pembentukan kelompok. Selain itu, variabel assignments_submitted memiliki nilai hilang pada seluruh observasi, sehingga tidak dapat digunakan dalam analisis lanjutan.
# preprocessing
df_clean <- df %>%
mutate(
attendance_rate = lectures_attended / total_lectures,
lab_rate = labs_attended / total_lab_sessions
) %>%
select(
age,
quiz1_marks, quiz2_marks, quiz3_marks,
midterm_marks, final_marks,
previous_gpa,
attendance_rate,
lab_rate
)
colnames(df_clean)
## [1] "age" "quiz1_marks" "quiz2_marks" "quiz3_marks"
## [5] "midterm_marks" "final_marks" "previous_gpa" "attendance_rate"
## [9] "lab_rate"
# hapus kolom konstan
sd_check <- sapply(df_clean, function(x) sd(x, na.rm = TRUE))
sd_check[is.na(sd_check)] <- 0
important_cols <- c("attendance_rate", "lab_rate")
sd_check <- sapply(df_clean, function(x) sd(x, na.rm = TRUE))
sd_check[is.na(sd_check)] <- 0
valid_cols <- (sd_check > 0) | (names(sd_check) %in% important_cols)
df_clean <- df_clean[, valid_cols]
# validasi
print(colnames(df_clean))
## [1] "age" "quiz1_marks" "quiz2_marks" "quiz3_marks"
## [5] "midterm_marks" "final_marks" "previous_gpa" "attendance_rate"
## [9] "lab_rate"
print(dim(df_clean))
## [1] 300 9
if (ncol(df_clean) == 0) {
stop("Data kosong setelah preprocessing!")
}
colSums(is.na(df_clean))
## age quiz1_marks quiz2_marks quiz3_marks midterm_marks
## 0 0 0 0 0
## final_marks previous_gpa attendance_rate lab_rate
## 0 0 0 0
Interpretasi
Pada tahap preprocessing, variabel identitas seperti student_id dan name tidak digunakan karena tidak mencerminkan karakteristik akademik mahasiswa. Variabel kategorik gender juga tidak dimasukkan ke dalam clustering utama karena metode yang digunakan berbasis jarak Euclidean dan lebih sesuai untuk data numerik. Variabel total seperti total_lectures dan total_lab_sessions tidak digunakan karena nilainya konstan untuk seluruh mahasiswa, sehingga tidak memiliki variasi yang dapat membantu proses pengelompokan. Sebagai gantinya, dibuat fitur turunan berupa attendance_rate dan lab_rate agar tingkat partisipasi mahasiswa dapat direpresentasikan dengan lebih proporsional.
Alasan
Digunakan untuk melihat bentuk distribusi masing-masing variabel, apakah cenderung simetris, miring, atau memiliki penumpukan pada nilai tertentu. Informasi ini penting karena beberapa metode clustering sensitif terhadap bentuk distribusi dan keberadaan data ekstrem.
df_clean %>%
pivot_longer(cols = everything()) %>%
ggplot(aes(x = value)) +
geom_histogram(bins = 20, fill = "skyblue") +
facet_wrap(~name, scales = "free")
Interpretasi
Temuan ini menunjukkan bahwa data memiliki variasi antarmahasiswa yang cukup untuk dianalisis lebih lanjut dengan clustering, meskipun bentuk distribusinya tidak sepenuhnya simetris.
Alasan
Digunakan untuk mendeteksi kemungkinan outlier. Dalam clustering, outlier dapat menarik pusat cluster dan membuat hasil pengelompokan menjadi kurang stabil, terutama pada metode K-Means.
df_clean %>%
pivot_longer(cols = everything()) %>%
ggplot(aes(y = value)) +
geom_boxplot(fill = "orange") +
facet_wrap(~name, scales = "free")
Interpretasi
Boxplot menunjukkan bahwa beberapa variabel memiliki nilai yang berada di luar rentang kuartil, namun secara umum tidak tampak outlier ekstrem dalam jumlah besar. struktur data masih relatif wajar untuk dianalisis menggunakan metode clustering. Namun tetap perlu diperhatikan karena metode seperti K-Means cukup sensitif terhadap outlier.
Alasan
Digunakan untuk melihat kekuatan hubungan linear antarvariabel. Jika ada dua variabel yang terlalu berkorelasi tinggi, maka keduanya bisa memberi informasi yang tumpang tindih dalam pembentukan cluster.
cor_matrix <- cor(df_clean)
corrplot(cor_matrix, method = "color", tl.cex = 0.8)
nilai <- df_clean %>%
select(quiz1_marks, quiz2_marks, quiz3_marks, midterm_marks, final_marks, previous_gpa, attendance_rate, lab_rate)
cor(nilai)
## quiz1_marks quiz2_marks quiz3_marks midterm_marks
## quiz1_marks 1.000000000 -0.043697917 -0.053417101 0.002247129
## quiz2_marks -0.043697917 1.000000000 -0.018054015 -0.057367460
## quiz3_marks -0.053417101 -0.018054015 1.000000000 -0.021707029
## midterm_marks 0.002247129 -0.057367460 -0.021707029 1.000000000
## final_marks 0.035708710 0.172477432 0.085784724 -0.063032977
## previous_gpa 0.077042642 -0.105887553 0.001000300 0.013604204
## attendance_rate -0.142728255 -0.002700816 0.006231715 0.012045722
## lab_rate 0.004496065 -0.004400874 0.046679767 -0.019408259
## final_marks previous_gpa attendance_rate lab_rate
## quiz1_marks 0.03570871 0.07704264 -0.142728255 0.004496065
## quiz2_marks 0.17247743 -0.10588755 -0.002700816 -0.004400874
## quiz3_marks 0.08578472 0.00100030 0.006231715 0.046679767
## midterm_marks -0.06303298 0.01360420 0.012045722 -0.019408259
## final_marks 1.00000000 0.10108920 -0.018898478 0.108077448
## previous_gpa 0.10108920 1.00000000 -0.022005049 -0.038860102
## attendance_rate -0.01889848 -0.02200505 1.000000000 -0.017853515
## lab_rate 0.10807745 -0.03886010 -0.017853515 1.000000000
Interpretasi
Matriks korelasi menunjukkan bahwa sebagian besar pasangan variabel memiliki hubungan linear yang lemah. Nilai korelasi yang muncul umumnya mendekati nol, dan tidak ada pasangan variabel dengan korelasi tinggi yang mengindikasikan redundansi kuat. Artinya, performa mahasiswa pada satu jenis penilaian belum tentu sejalan dengan performa pada penilaian lainnya.
Alasan
Digunakan untuk mengecek apakah ada pola hubungan sederhana antara kehadiran dan prestasi akademik sebelumnya.
ggplot(df_clean, aes(x = attendance_rate, y = previous_gpa)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm")
## `geom_smooth()` using formula = 'y ~ x'
Interpretasi
Scatter plot antara attendance_rate dan previous_gpa tidak menunjukkan pola linear yang kuat. Garis regresi yang hampir datar mengindikasikan bahwa tingkat kehadiran pada data ini tidak memiliki hubungan yang jelas dengan GPA sebelumnya. Artinya, kehadiran kemungkinan menjadi dimensi yang relatif independen dalam proses pembentukan cluster.
Alasan
Digunakan sebagai eksplorasi multivariat awal untuk melihat pola hubungan pasangan variabel secara lebih menyeluruh.
ggpairs(df_clean)
Interpretasi
Karena itu, penggunaan metode clustering multivariat menjadi relevan.
Standardisasi dilakukan agar seluruh variabel memiliki skala yang sebanding. Langkah ini penting karena rentang nilai antarvariabel berbeda, misalnya final_marks berada pada skala 0–50, sedangkan previous_gpa berada pada skala sekitar 0–4. Tanpa scaling, variabel dengan rentang yang lebih besar akan lebih dominan dalam perhitungan jarak, sehingga hasil clustering dapat menjadi bias.
# scaling data
df_scaled <- scale(df_clean)
# cek hasil scaling
head(df_scaled)
## age quiz1_marks quiz2_marks quiz3_marks midterm_marks final_marks
## [1,] -1.1376395 0.4624255 -0.06901922 0.71226143 1.2126115 -0.07417282
## [2,] -0.2453732 1.7032809 0.84203448 -0.50496421 0.6565341 -0.37086409
## [3,] -1.1376395 0.1522116 -1.93253816 -1.90661797 -0.6732164 -1.06596935
## [4,] -0.2453732 -1.2747722 -1.39418824 1.63440206 -0.2742912 0.30728739
## [5,] -0.2453732 -0.8404728 0.17944997 -1.27956234 0.4631158 -0.54040196
## [6,] 0.2007599 -0.9025155 1.71167666 0.01143454 -1.2776484 1.07020780
## previous_gpa attendance_rate lab_rate
## [1,] -0.3221446 -0.5402881 -1.06421836
## [2,] -0.5541652 -1.3453531 0.95325721
## [3,] 0.2510826 -1.6137081 -1.56858726
## [4,] -1.5368405 0.8014870 -1.56858726
## [5,] -0.3767377 0.2647769 0.44888831
## [6,] -1.3321164 1.3381970 -0.05548058
colMeans(df_scaled)
## age quiz1_marks quiz2_marks quiz3_marks midterm_marks
## -3.160435e-16 -2.476029e-16 1.879515e-16 -1.211647e-16 -1.378643e-16
## final_marks previous_gpa attendance_rate lab_rate
## -2.798687e-18 2.496036e-16 1.618887e-16 2.553513e-17
apply(df_scaled, 2, sd)
## age quiz1_marks quiz2_marks quiz3_marks midterm_marks
## 1 1 1 1 1
## final_marks previous_gpa attendance_rate lab_rate
## 1 1 1 1
Interpretasi
Standardisasi dilakukan agar seluruh variabel berada pada skala yang sebanding. Langkah ini penting karena variabel seperti final_marks memiliki rentang nilai yang jauh lebih besar dibandingkan previous_gpa atau attendance_rate. Tanpa scaling, variabel dengan rentang besar akan mendominasi perhitungan jarak dan membuat hasil clustering menjadi bias. Hasil pengecekan menunjukkan bahwa rata-rata variabel hasil scaling mendekati 0 dan simpangan bakunya mendekati 1, sehingga standardisasi berhasil dilakukan.
df_scaled <- scale(na.omit(df_clean))
pca <- prcomp(df_scaled)
summary(pca)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8
## Standard deviation 1.1211 1.1018 1.0379 1.0266 1.0073 0.9844 0.9494 0.90884
## Proportion of Variance 0.1396 0.1349 0.1197 0.1171 0.1127 0.1077 0.1002 0.09178
## Cumulative Proportion 0.1396 0.2745 0.3942 0.5113 0.6241 0.7318 0.8319 0.92369
## PC9
## Standard deviation 0.82874
## Proportion of Variance 0.07631
## Cumulative Proportion 1.00000
pca_df <- as.data.frame(pca$x)
ggplot(pca_df, aes(PC1, PC2)) +
geom_point(alpha = 0.6)
Interpretasi
Elbow Method
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")
Interpretasi
Pada data ini, titik siku tidak terlihat sangat tegas, sehingga Elbow Method perlu dipertimbangkan bersama ukuran lain, yaitu silhouette.
Silhouette Analysis
set.seed(42)
avg_sil <- function(k) {
kmeans_model <- kmeans(df_scaled, centers = k, nstart = 25)
ss <- silhouette(kmeans_model$cluster, dist(df_scaled))
mean(ss[, 3])
}
k_values <- 2:10
avg_sil_values <- sapply(k_values, avg_sil)
par(mfrow = c(1, 1))
plot(k_values, avg_sil_values, type = "b", pch = 19, frame = FALSE,
xlab = "Number of clusters K",
ylab = "Average Silhouette Width",
main = "Silhouette Analysis")
Interpretasi
Berdasarkan silhouette, jumlah cluster terbaik adalah yang memiliki nilai silhouette rata-rata tertinggi, kalau dilihat dari grafik puncaknya sekitar 7.
k <- 7
kmeans_model <- kmeans(df_scaled, centers = k, nstart = 25)
kmedian_model <- pam(df_scaled, k = k)
sil_kmeans <- silhouette(kmeans_model$cluster, dist(df_scaled))
mean(sil_kmeans[,3])
## [1] 0.1051238
sil_kmedian <- silhouette(kmedian_model$cluster, dist(df_scaled))
mean(sil_kmedian[,3])
## [1] 0.06714033
Interpretasi
k <- 7
kmeans_model <- kmeans(df_scaled, centers = k, nstart = 25)
Simpan Hasil Assignment Cluster ke dataframe asli
df_features <- as.data.frame(df_scaled)
df_features$cluster_kmeans <- as.factor(kmeans_model$cluster)
Melihat jumlah mahasiswa di tiap Cluster
table(df_features$cluster_kmeans)
##
## 1 2 3 4 5 6 7
## 47 37 28 46 48 44 50
Mekanisme: Algoritma ini mengoptimalkan fungsi objektif \(J = \sum_{i=1}^{k} \sum_{x \in C_i} ||x - \mu_i||^2\).
aggregate(df_clean, by = list(cluster = kmeans_model$cluster), mean)
## cluster age quiz1_marks quiz2_marks quiz3_marks midterm_marks
## 1 1 22.65957 8.238298 6.987234 3.648936 22.104255
## 2 2 22.00000 5.562162 7.624324 6.516216 18.500000
## 3 3 19.71429 7.357143 5.914286 3.557143 13.235714
## 4 4 20.95652 7.678261 3.882609 4.193478 25.334783
## 5 5 21.04167 7.381250 4.331250 8.368750 20.560417
## 6 6 22.34091 7.050000 5.956818 5.620455 9.827273
## 7 7 21.54000 7.194000 6.706000 5.732000 26.240000
## final_marks previous_gpa attendance_rate lab_rate
## 1 42.25957 2.591489 0.1897163 0.6276596
## 2 31.17027 2.342973 0.5653153 0.2072072
## 3 34.90000 3.535357 0.3244048 0.2678571
## 4 22.08913 2.534565 0.5760870 0.4528986
## 5 41.65833 3.082500 0.2656250 0.6597222
## 6 42.08182 2.511591 0.7102273 0.7348485
## 7 44.57000 3.185400 0.8183333 0.5200000
Interpretasi
plot_cluster <- function(cluster_assignments, title) {
ggplot(pca_df, aes(PC1, PC2, color = cluster_assignments)) +
geom_point(alpha = 0.6) +
labs(title = title, color = "Cluster") +
theme_minimal()
}
plot_cluster(df_features$cluster_kmeans, "K-Means")
Interpretasi
set.seed(42)
kmedian_model <- pam(df_scaled, k = k)
df_features$cluster_kmedian <- as.factor(kmedian_model$cluster)
Mekanisme: Meminimalkan absolute error: \(\sum_{i=1}^{k} \sum_{x \in C_i} ||x - m_i||_1\) di mana \(m_i\) adalah median dari cluster.
plot_cluster(df_features$cluster_kmedian, "K-Medians")
## Interpretasi
Hasil K-Median menunjukkan bahwa data terbagi menjadi tujuh cluster sesuai dengan jumlah cluster yang ditentukan. Namun, secara visual terlihat bahwa sebaran antar cluster masih saling tumpang tindih, terutama pada area tengah, sehingga batas antar kelompok tidak terbentuk secara jelas. Meskipun K-Median dikenal lebih tahan terhadap outlier, pada dataset ini metode tersebut belum mampu menghasilkan pemisahan cluster yang kuat dan mudah diinterpretasikan.
# Menentukan kandidat eps
kNNdistplot(df_scaled, k = 5)
abline(h = 2.6, col = "red", lty = 2)
title(main = "kNN Distance Plot untuk Menentukan Epsilon")
# Uji beberapa nilai eps
eps_values <- c(2.6, 2.8, 3.0, 3.2)
for (e in eps_values) {
model <- dbscan(df_scaled, eps = e, MinPts = 5)
cat("\nEps =", e, "\n")
print(table(model$cluster))
}
##
## Eps = 2.6
##
## 0 1
## 14 286
##
## Eps = 2.8
##
## 0 1
## 3 297
##
## Eps = 3
##
## 0 1
## 1 299
##
## Eps = 3.2
##
## 1
## 300
Interpretasi
eps: radius pencarian titik tetangga
minPts: jumlah minimum titik dalam radius eps agar menjadi ‘core point’
# Model final
dbscan_model <- dbscan(df_scaled, eps = 2.6, MinPts = 5)
df_features$cluster_dbscan <- as.factor(dbscan_model$cluster)
# Frekuensi cluster
table(df_features$cluster_dbscan)
##
## 0 1
## 14 286
cluster_dbscan_plot <- ifelse(df_features$cluster_dbscan == 0, "Noise", "Cluster 1")
plot_cluster(cluster_dbscan_plot, "DBSCAN")
Interpretasi
Mekanisme singkat: Berbasis pada estimasi kepadatan kernel (Kernel Density Estimation / KDE). Algoritma menghitung vektor pergeseran rata-rata (mean shift vector) dan menggerakkan titik data menuju puncak kepadatan lokal.
# ubah data ke matrix
data_matrix <- as.matrix(df_scaled)
# coba beberapa nilai bandwidth
bandwidth_values <- c(0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5)
for (bw in bandwidth_values) {
model <- meanShift(data_matrix, bandwidth = rep(bw, ncol(data_matrix)))
cat("\nBandwidth =", bw, "\n")
print(table(model$assignment))
}
##
## Bandwidth = 0.8
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1
## 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
## 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
## 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1
## 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
## 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1
## 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 281 282 283 284 285 286 287 288 289 290 291 292 293
## 1 1 1 1 1 1 1 1 1 1 1 1 1
##
## Bandwidth = 0.9
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1
## 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
## 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
## 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1
## 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
## 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
## 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1
## 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##
## Bandwidth = 1
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1
## 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1
## 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
## 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2
## 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
## 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1
## 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
## 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1
## 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 281 282 283 284 285 286 287 288 289 290 291 292 293
## 1 1 1 1 1 1 1 1 1 1 1 1 1
##
## Bandwidth = 1.1
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 3 1 1 2
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 1 1 1 1 1 1 1 1 5 2 2 1 1 1 1 1 1 1 1 1
## 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
## 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 2 2
## 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
## 1 1 1 1 1 1 1 1 1 2 1 1 1 1 2 1 1 1 1 1
## 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
## 1 1 1 1 1 1 1 1 1 1 1 1 2 3 1 1 1 1 1 1
## 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1
## 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
## 1 1 2 1 1 2 1 2 1 2 1 1 1 1 1 3 1 1 1 1
## 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2
## 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
## 1 1 1 1 2 1 1 1 1 1 2 1 1 1 1 2 1 1 1 3
## 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
## 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1
## 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
## 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1
## 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
## 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 1 1 1 1 1
## 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##
## Bandwidth = 1.2
##
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
## 7 7 1 10 12 4 13 6 9 4 8 5 14 5 13 11 2 8 4 9 15 6 1 4 1 5
## 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
## 1 8 1 2 4 4 3 8 1 4 1 3 2 3 1 4 10 1 1 1 5 1 1 14 1 1
## 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
## 5 1 1 2 3 1 1 2 1 3 2 1 1 1 1 2 1 1
##
## Bandwidth = 1.3
##
## 1 2 3 4 5 6 7 8 9
## 92 60 78 9 21 15 16 8 1
##
## Bandwidth = 1.4
##
## 1
## 300
##
## Bandwidth = 1.5
##
## 1
## 300
Interpretasi
Berdasarkan percobaan berbagai nilai bandwidth, nilai bandwidth = 1.3 dipilih karena menghasilkan jumlah cluster yang tidak terlalu banyak maupun terlalu sedikit.
Namun, struktur cluster yang terbentuk masih kurang stabil dan cenderung menghasilkan beberapa cluster kecil. Oleh karena itu, metode ini kurang optimal dibandingkan K-Means dalam kasus ini.
meanshift_model <- meanShift(data_matrix, bandwidth = rep(1.3, ncol(data_matrix)))
# Simpan hasil cluster
df_features$cluster_meanshift <- as.factor(meanshift_model$assignment)
# Lihat jumlah anggota tiap cluster
table(df_features$cluster_meanshift)
##
## 1 2 3 4 5 6 7 8 9
## 92 60 78 9 21 15 16 8 1
plot_cluster(df_features$cluster_meanshift, "Mean Shift")
Interpretasi
set.seed(42)
# Menggunakan jumlah cluster yang sama dengan K-Means
fcm_model <- cmeans(df_scaled, centers = k, iter.max = 100, method = "cmeans")
# Simpan hasil cluster (hard assignment dari membership terbesar)
df_features$cluster_fcm <- as.factor(fcm_model$cluster)
# Lihat membership
head(fcm_model$membership)
## 1 2 3 4 5 6 7
## 1 0.1428668 0.1428488 0.1428672 0.1428523 0.1428660 0.1428516 0.1428474
## 2 0.1428714 0.1428642 0.1428358 0.1428503 0.1428655 0.1428738 0.1428390
## 3 0.1428710 0.1428480 0.1428683 0.1428479 0.1428673 0.1428551 0.1428423
## 4 0.1428491 0.1428523 0.1428729 0.1428604 0.1428515 0.1428485 0.1428652
## 5 0.1428419 0.1428426 0.1429059 0.1428526 0.1428512 0.1428408 0.1428649
## 6 0.1428321 0.1428661 0.1428608 0.1428639 0.1428403 0.1428599 0.1428769
Interpretasi
plot_cluster(df_features$cluster_fcm, "Fuzzy C-Means")
Berdasarkan seluruh proses analisis, dapat disimpulkan bahwa data mahasiswa memiliki struktur yang cukup kompleks dan tidak menunjukkan pemisahan cluster yang sangat kuat. Hal ini terlihat dari nilai silhouette yang relatif rendah serta visualisasi cluster yang masih saling tumpang tindih.
Dari berbagai metode yang digunakan, K-Means merupakan metode yang paling sesuai untuk dataset ini. Meskipun tidak menghasilkan pemisahan yang sangat tegas, K-Means mampu memberikan pembagian cluster yang relatif lebih stabil, kompak, dan masih dapat diinterpretasikan secara akademik.