K-Nearest Neighbour
Pendahuluan
Latar Belakang: Memprediksi Akreditasi (A, B, C) dengan Skor Literasi dan Numerasi
Peringkat Akreditasi (A, B, C) adalah benchmark fundamental untuk menilai mutu sebuah satuan pendidikan. Namun, apa yang mendorong perbedaan peringkat tersebut? Selain faktor infrastruktur dan administratif, faktor yang paling penting seharusnya adalah output pembelajaran siswa.
Dalam analisis ini, kita akan mengeksplorasi sebuah pendekatan machine learning untuk menjawab pertanyaan:
“Bisakah kita memprediksi Peringkat Akreditasi sebuah sekolah (A, B, C) hanya dengan melihat skor Literasi dan Numerasi?”
Kita akan menggunakan empat variabel prediktor kuantitatif yang sangat relevan:
Literasi 2023
Numerasi 2023
Literasi 2024
Numerasi 2024
Karena variabel target kita (Peringkat) memiliki tiga
kategori (A, B, dan C), ini menjadikannya masalah Klasifikasi
Multikelas.
Metodologi: K-Nearest Neighbors (KNN)
Untuk memecahkan masalah ini, kita akan menggunakan algoritma K-Nearest Neighbors (KNN).
Apa itu KNN?
KNN adalah algoritma machine learning yang sederhana namun sangat intuitif. Prinsip dasarnya bekerja seperti pepatah: “Anda dinilai dari dengan siapa Anda bergaul.”
KNN tidak “belajar” sebuah formula rumit. Sebaliknya, ia menghafal seluruh data latih. Ketika kita ingin memprediksi peringkat sekolah baru, KNN melakukan tiga langkah:
Mengukur Jarak: Ia menghitung “jarak” atau “kemiripan” sekolah baru tersebut dengan semua sekolah lain dalam data latih.
Mencari Tetangga (K): Ia menemukan ‘K’ sekolah terdekat (tetangga terdekat) berdasarkan skornya. ‘K’ adalah angka yang kita tentukan (misalnya, 5, 7, atau 10).
Mengambil Suara (Voting): Ia melihat peringkat akreditasi dari ‘K’ tetangga tersebut dan mengambil suara terbanyak (majority vote) untuk menentukan prediksi.
Bagaimana KNN Bekerja untuk Multikelas (A, B, C)?
Inilah keindahan KNN: logikanya tidak berubah sama sekali untuk multikelas.
Contoh: Kita ingin memprediksi Sekolah X dan kita menetapkan K = 7.
KNN mencari 7 tetangga terdekat berdasarkan skor literasi dan numerasinya.
Hasil “voting” dari 7 tetangga tersebut adalah:
Peringkat ‘A’: 3 suara
Peringkat ‘B’: 2 suara
Peringkat ‘C’: 2 suara
Keputusan: Suara terbanyak adalah ‘A’ (3 suara). Maka, KNN memprediksi Sekolah X akan mendapatkan Peringkat ‘A’.
Studi Kasus : Provinsi DKI Jakarta, DIY, Bali, Banten
Kami memfokuskan studi kasus pada empat provinsi yang sering dianggap sebagai pusat keunggulan di Indonesia: DKI Jakarta, Daerah Istimewa Yogyakarta (DIY), Bali, dan Banten.
Tujuan RPubs
Eksplorasi Data (EDA): Memvisualisasikan distribusi skor dan melihat korelasi antar variabel prediktor.
Pembagian Data: Membagi data menjadi 80% train dan 20% test.
Optimasi Model (Tuning): Menjalankan cross-validation untuk menemukan nilai ‘K’ optimal.
Pelatihan Model: Melatih model KNN final pada data latih menggunakan ‘K’ optimal tersebut.
Penanganan Imbalance Class : Menyeimbangkan kelas peringkat akreditasi A B dan C dengan SMOTE
Evaluasi Model: Membuat Confusion Matrix multikelas pada data uji untuk menilai Akurasi, Presisi, dan Recall untuk setiap kelas (A, B, dan C).
Hasil dan Pembahasan
1. Persiapan Data
##
## 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
## Warning: package 'ggplot2' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0 ✔ readr 2.1.5
## ✔ lubridate 1.9.4 ✔ stringr 1.5.1
## ✔ purrr 1.0.2 ✔ tibble 3.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
## ── Attaching packages ────────────────────────────────────── tidymodels 1.2.0 ──
## ✔ broom 1.0.7 ✔ rsample 1.2.1
## ✔ dials 1.3.0 ✔ tune 1.2.1
## ✔ infer 1.0.7 ✔ workflows 1.1.4
## ✔ modeldata 1.4.0 ✔ workflowsets 1.1.0
## ✔ parsnip 1.2.1 ✔ yardstick 1.3.2
## ✔ recipes 1.1.0
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ scales::discard() masks purrr::discard()
## ✖ dplyr::filter() masks stats::filter()
## ✖ recipes::fixed() masks stringr::fixed()
## ✖ dplyr::lag() masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ✖ recipes::step() masks stats::step()
## • Learn how to get started at https://www.tidymodels.org/start/
## Warning: package 'DataExplorer' was built under R version 4.4.3
## Warning: package 'kknn' was built under R version 4.4.3
## tibble [6,648 × 7] (S3: tbl_df/tbl/data.frame)
## $ Nomor : num [1:6648] 1 2 3 4 5 6 7 8 9 10 ...
## $ Provinsi : chr [1:6648] "Prov. Jawa Timur" "Prov. Nusa Tenggara Timur" "Prov. Jawa Timur" "Prov. Sumatera Utara" ...
## $ Peringkat Akreditasi: chr [1:6648] "A" "B" "A" "A" ...
## $ Lit_2023 : num [1:6648] 20 80 91.1 57.8 55.6 ...
## $ Num_2023 : num [1:6648] 40 48.8 82.2 57.8 66.7 ...
## $ Lit_2024 : num [1:6648] 0 82.2 93.3 64.4 83.3 ...
## $ Num_2024 : num [1:6648] 0 62.8 93.3 62.2 66.7 ...
## Rows: 6,648
## Columns: 6
## $ Provinsi <chr> "Prov. Jawa Timur", "Prov. Nusa Tenggara Timur"…
## $ `Peringkat Akreditasi` <chr> "A", "B", "A", "A", "B", "A", "C", "B", "A", "C…
## $ Lit_2023 <dbl> 20.00, 80.00, 91.11, 57.78, 55.56, 51.11, 56.52…
## $ Num_2023 <dbl> 40.00, 48.78, 82.22, 57.78, 66.67, 51.11, 61.90…
## $ Lit_2024 <dbl> 0.000, 82.222, 93.333, 64.444, 83.333, 71.795, …
## $ Num_2024 <dbl> 0.000, 62.791, 93.333, 62.222, 66.667, 55.556, …
Filter provinsi DKI, DIY, Bali, Banten
df <- df %>%
filter(Provinsi %in% c("Prov. D.K.I. Jakarta",
"Prov. D.I. Yogyakarta",
"Prov. Bali",
"Prov. Banten"))
# cek hasil
df %>% count(Provinsi)## # A tibble: 4 × 2
## Provinsi n
## <chr> <int>
## 1 Prov. Bali 66
## 2 Prov. Banten 394
## 3 Prov. D.I. Yogyakarta 102
## 4 Prov. D.K.I. Jakarta 229
2. Eksplorasi Data
## Lit_2023 Num_2023 Lit_2024 Num_2024
## Min. : 0.00 Min. : 6.67 Min. : 0.00 Min. : 0.00
## 1st Qu.: 72.78 1st Qu.: 65.89 1st Qu.: 77.78 1st Qu.: 71.11
## Median : 91.11 Median : 83.33 Median : 93.33 Median : 87.81
## Mean : 81.80 Mean : 77.17 Mean : 83.46 Mean : 80.11
## 3rd Qu.: 97.78 3rd Qu.: 93.25 3rd Qu.: 97.78 3rd Qu.: 95.56
## Max. :100.00 Max. :100.00 Max. :100.00 Max. :100.00
2.2 Jumlah Sekolah Per Provinsi
# pie chart dengan label jumlah
ggplot(prov_count, aes(x = "", y = n, fill = Provinsi)) +
geom_bar(stat = "identity", width = 1, color = "white") +
coord_polar(theta = "y") +
geom_text(aes(label = n),
position = position_stack(vjust = 0.5),
color = "black",
size = 4) +
scale_fill_brewer(palette = "Oranges") +
labs(title = "Jumlah Sekolah per Provinsi",
fill = "Provinsi") +
theme_void()Ubah Kolom “Peringkat Akreditasi” jadi Y biar lebih gampang
2.3. Barplot Frekuensi Peringkat Akreditasi
# Hitung frekuensi + proporsi
freq_tab <- df %>%
count(Y) %>%
mutate(prop = n / sum(n) * 100)
# Plot
ggplot(freq_tab, aes(x = Y, y = n, fill = Y)) +
geom_col() +
geom_text(aes(label = paste0(n, " (", round(prop,1), "%)")),
vjust = -0.5, size = 4) +
labs(title = "Frekuensi Peringkat Akreditasi",
x = "Peringkat Akreditasi",
y = "Frekuensi") +
scale_fill_brewer(palette = "Oranges") +
theme_classic()hapus observasi pada kelas “Tidak Terakreditasi” karena terlalu sedikit yang akan menyebabkan model kurang mampu menggeneralisir
## # A tibble: 2 × 6
## Provinsi Y Lit_2023 Num_2023 Lit_2024 Num_2024
## <fct> <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Prov. Banten Tidak Terakreditasi 66.7 66.7 0 100
## 2 Prov. Banten Tidak Terakreditasi 25 18.8 4.54 25
# Hitung frekuensi + proporsi
freq_tab <- df %>%
count(Y) %>%
mutate(prop = n / sum(n) * 100)
# Plot
ggplot(freq_tab, aes(x = Y, y = n, fill = Y)) +
geom_col() +
geom_text(aes(label = paste0(n, " (", round(prop,1), "%)")),
vjust = -0.5, size = 4) +
labs(title = "Frekuensi Peringkat Akreditasi",
x = "Peringkat Akreditasi",
y = "Frekuensi") +
scale_fill_brewer(palette = "Oranges") +
theme_classic()Berdasarkan plot sebaran Variabel Respon (Y) dalam hal ini peringkat akreditasi mengalami kelas yang tidak seimbang
2.4 Boxplot sebaran nilai peubah terhadap kelas Y
df_long <- df %>%
pivot_longer(cols = c(Lit_2023, Num_2023, Lit_2024, Num_2024),
names_to = "Variabel",
values_to = "Skor")
ggplot(df_long, aes(x = Y, y = Skor, fill = Y)) +
geom_boxplot() +
facet_wrap(~ Variabel, scales = "free_y") +
scale_fill_brewer(palette = "Oranges") +
labs(title = "Distribusi Nilai Literasi & Numerasi per Akreditasi",
x = "Akreditasi", y = "Skor") +
theme_minimal()nilai masing-masing peubah terlihat berbeda rentang nilainya yaitu A lebih tinggi dari B C, tapi A banyak outlier
2.4. Frekuensi Peringkat Akreditasi per Provinsi
df %>%
count(Provinsi, Y) %>%
arrange(Provinsi, desc(n)) %>%
ggplot(aes(x = Provinsi, y = n, fill = Y)) +
geom_col(position = "stack") +
scale_fill_brewer(palette = "Oranges") +
labs(title = "Distribusi Peringkat Akreditasi per Provinsi",
x = "Provinsi",
y = "Frekuensi") +
theme_minimal() + # background putih polos, no grid
theme(axis.text.x = element_text(angle = 45, hjust = 1),
panel.background = element_blank(),
plot.background = element_blank(),
legend.background = element_blank())2.5 Korelasi
## corrplot 0.95 loaded
cor_mat <- df %>%
select(Lit_2023, Num_2023, Lit_2024, Num_2024) %>%
cor(use = "complete.obs")
corrplot(cor_mat, method = "color", type = "upper",
addCoef.col = "white", number.cex = 0.7,
tl.col = "black", tl.srt = 45)## Warning: package 'GGally' was built under R version 4.4.3
## Registered S3 method overwritten by 'GGally':
## method from
## +.gg ggplot2
library(ggplot2)
df %>%
select(Lit_2023, Num_2023, Lit_2024, Num_2024) %>%
ggpairs(
aes(alpha = 0.6),
upper = list(continuous = wrap("cor", size = 4, stars = FALSE)), # hanya angka korelasi
lower = list(continuous = wrap("smooth",
color = "black", alpha = 0.6, se = FALSE)),
diag = list(continuous = wrap("densityDiag",
fill = "orange", alpha = 0.6))
) +
theme_classic() 3. Model KNN
Untuk analisis klasifikasi ini, kita akan fokus murni pada skor rapor
pendidikan. Oleh karena itu, kolom Provinsi tidak akan
digunakan sebagai variabel prediktor dan kita keluarkan dari data.
Tantangan utama dalam dataset ini adalah data yang tidak seimbang (imbalanced). Kemungkinan besar, jumlah sekolah dengan akreditasi ‘A’ atau ‘B’ jauh lebih banyak daripada ‘C’. Jika kita melatih model pada data ini, model akan cenderung “malas” dan lebih sering menebak kelas mayoritas, sehingga kinerjanya buruk pada kelas minoritas (‘C’).
Untuk mengatasi ini, kita akan merancang sebuah eksperimen untuk membandingkan dua pendekatan:
Workflow 1 (
no_prep): Melatih KNN pada data mentah (asli).Workflow 2 (
smote_rec): Melatih KNN pada data yang telah kita seimbangkan menggunakan SMOTE dan kita normalisasi.
KNN wajib menggunakan normalisasi/standardisasi karena ia
berbasis jarak. step_smote akan membuat data sintetis
(buatan) untuk kelas minoritas agar jumlahnya seimbang.
3.1 Pembagian data
Pertama, kita bagi data kita menjadi data latih (80%) dan data uji
(20%). Kita gunakan strata = Y agar proporsi kelas A, B,
dan C tetap sama di kedua set data.
set.seed(123)
holdout_split <- initial_split(df,
#sampel acak berdasarkan kelompok
strata = Y,
# proporsi untuk training data
prop = 0.8)
train_data <- training(holdout_split)
test_data <- testing(holdout_split)library(patchwork) # biar bisa gabung plot
# distribusi train
p_train <- train_data %>%
count(Y) %>%
ggplot(aes(x = Y, y = n, fill = Y)) +
geom_col() +
scale_fill_brewer(palette = "Oranges") +
labs(title = "Distribusi Y - Training Set", x = "Kelas", y = "Jumlah") +
theme_minimal()
# distribusi test
p_test <- test_data %>%
count(Y) %>%
ggplot(aes(x = Y, y = n, fill = Y)) +
geom_col() +
labs(title = "Distribusi Y - Testing Set", x = "Kelas", y = "Jumlah") +
scale_fill_brewer(palette = "Oranges") +
theme_minimal()
# gabungkan plot
p_train + p_testPlot di atas untuk memperlihatkan bahwa pembagian data training dan testing sesuai dengan strata Y sehingga proporsi kelas sama untuk train dan test set
Selanjutnya, kita siapkan “resep” untuk kedua workflow kita.
## Warning: package 'themis' was built under R version 4.4.3
library(workflowsets) # Untuk membandingkan workflow
set.seed(123)
# Recipe 1: Tanpa preprocessing
no_prep <- recipe(Y ~ ., data = train_data)
# Recipe 2: SMOTE + Normalisasi
# Kita terapkan SMOTE untuk menyeimbangkan kelas
# Kita terapkan Normalisasi (centering dan scaling) karena KNN sensitif terhadap skala
smote_rec <- recipe(Y ~ ., data = train_data) %>%
step_smote(Y) %>%
step_normalize(all_numeric_predictors())3.2. Penentuan Model dan Tuning Grid
Kita akan menggunakan model KNN. Parameter terpenting dalam KNN
adalah neighbors (jumlah tetangga, ‘k’). Kita tidak tahu
nilai ‘k’ terbaik, jadi kita akan memberitahu tidymodels
untuk mencarinya (tuning).
Kita akan menguji 50 nilai ‘k’ yang berbeda, dari k=2 sampai k=50.
# Model: KNN dengan K yang bisa di-tuning
knn_tune <- nearest_neighbor(
neighbors = tune(), # 'tune()' berarti parameter ini akan kita cari
weight_func = "rectangular",
dist_power = 2 # Jarak Euclidean
) %>%
set_engine("kknn") %>%
set_mode("classification")
# Grid (daftar) nilai 'k' yang akan diuji
knn_grid <- grid_regular(neighbors(range = c(2, 50)), levels = 50)3.3. Validasi Silang (Cross-Validation)
Untuk menemukan ‘k’ terbaik, kita tidak bisa menggunakan
test_data (karena itu curang). Kita akan menggunakan
10-Fold Cross-Validation pada train_data.
Data latih akan dibagi 10, lalu model akan dilatih 10 kali.
Kita gunakan workflow_set untuk menggabungkan kedua
resep (no_prep dan smote_rec) dengan model
(knn_tune).
# 1. Buat workflow set
wfst <- workflow_set(
preproc = list(no_prep = no_prep,
smote_rec = smote_rec),
models = list(knn = knn_tune)
)
# 2. Siapkan 10-fold CV (stratified)
set.seed(345)
folds <- vfold_cv(train_data, v = 10, strata = Y)
# 3. Jalankan tuning!
# Ini akan menguji 2 workflow x 50 nilai K x 10 folds
knn_tune_cv <- wfst %>%
workflow_map(
fn = "tune_grid",
verbose = TRUE,
seed = 2045,
resamples = folds,
grid = knn_grid,
metrics = metric_set(accuracy),
control = control_resamples(save_pred = TRUE)
)## i 1 of 2 tuning: no_prep_knn
## ✔ 1 of 2 tuning: no_prep_knn (7.2s)
## i 2 of 2 tuning: smote_rec_knn
## ✔ 2 of 2 tuning: smote_rec_knn (7.9s)
3.4. Hasil Tuning: SMOTE vs Data Asli
Sekarang kita plot hasil eksperimen CV kita. Kita ingin tahu:
Workflow mana yang lebih baik (biru vs oranye)?
Berapa nilai ‘k’ terbaik untuk masing-masing workflow?
# Kumpulkan hasil tuning (metrics) untuk keduanya
neighbors_result_no_prep <- knn_tune_cv %>%
extract_workflow_set_result("no_prep_knn") %>%
collect_metrics() %>%
mutate(workflow = "No SMOTE")
neighbors_result_smote <- knn_tune_cv %>%
extract_workflow_set_result("smote_rec_knn") %>%
collect_metrics() %>%
mutate(workflow = "SMOTE")
# Gabungkan hasil
neighbors_result <- bind_rows(neighbors_result_no_prep,
neighbors_result_smote)
# Plot perbandingan
neighbors_result %>%
filter(.metric == "accuracy") %>%
ggplot(aes(x = neighbors, y = mean, color = workflow)) +
geom_errorbar(aes(ymin = mean - std_err,
ymax = mean + std_err),
width = 0.3, alpha = 0.6) +
geom_point(size = 2) +
geom_line(alpha = 0.7) +
ylab("Accuracy (Rata-rata CV)") +
xlab("Neighbors (k)") +
scale_color_manual(values = c("No SMOTE" = "#03A9F4",
"SMOTE" = "#f44e03"),
name = "Workflow") +
labs(title = "Perbandingan Kinerja Akurasi KNN",
subtitle = "Data Asli vs. SMOTE + Normalisasi") +
theme_bw() +
theme(legend.position = "top")Interpretasi Plot: Dari plot di atas, kita dapat
melihat bahwa workflow SMOTE (garis oranye) secara
konsisten memberikan akurasi yang lebih tinggi daripada workflow
No SMOTE (garis biru).
Sekarang kita pilih nilai ‘k’ terbaik untuk masing-masing:
# 'k' terbaik untuk No SMOTE
best_neighbors <- knn_tune_cv %>%
extract_workflow_set_result("no_prep_knn") %>%
select_best(metric = "accuracy")
best_neighbors## # A tibble: 1 × 2
## neighbors .config
## <int> <chr>
## 1 33 Preprocessor1_Model32
# 'k' terbaik untuk SMOTE
best_neighbors_smote <- knn_tune_cv %>%
extract_workflow_set_result("smote_rec_knn") %>%
select_best(metric = "accuracy")
best_neighbors_smote## # A tibble: 1 × 2
## neighbors .config
## <int> <chr>
## 1 50 Preprocessor1_Model49
Berdasarkan hasil tuning CV
No SMOTE = k terbaik berdasarkan accuracy 33
SMOTE = k terbaik berdasarkan accuracy 50
3.5. Evaluasi Akhir pada Data Uji
Kita telah menemukan nilai ‘k’ optimal untuk kedua workflow (data asli dan SMOTE) menggunakan cross-validation pada data latih.
Kini saatnya menguji kedua model final tersebut pada
test_data—data yang belum pernah tersentuh sama sekali.
Fungsi last_fit() sangat ideal untuk ini: ia akan melatih
model final pada seluruh train_data dan
mengevaluasinya satu kali pada test_data.
3.5.1. Finalisasi Kedua Workflow
Pertama, kita siapkan dua workflow final menggunakan nilai ‘k’ terbaik yang sudah kita temukan untuk masing-masing.
# 1. Finalize workflow untuk non-SMOTE (menggunakan 'k' terbaiknya)
wf_no_prep <- workflow() %>%
add_model(knn_tune %>% finalize_model(best_neighbors)) %>%
add_recipe(no_prep)
# 2. Finalize workflow untuk SMOTE (menggunakan 'k' terbaiknya)
wf_smote <- workflow() %>%
add_model(knn_tune %>% finalize_model(best_neighbors_smote)) %>%
add_recipe(smote_rec)3.5.2. Menjalankan last_fit()
Sekarang kita jalankan last_fit() pada kedua workflow
menggunakan holdout_split yang sama.
Fungsi last_fit() dirancang khusus untuk langkah “final”
ini.
Ia mengambil workflow final (misal:
wf_no_prepdanwf_smote).Ia melatihnya (
fit) satu kali pada data latih dariholdout_split(yaitutrain_data).Ia mengevaluasinya (
evaluate) satu kali pada data uji dariholdout_split(yaitutest_data).
3.6. Hasil Kinerja Model: SMOTE vs Data Asli
Kita sekarang memiliki dua hasil akhir. Mari kita kumpulkan metrik dan confusion matrix dari keduanya untuk perbandingan head-to-head.
3.6.1. Perbandingan Metrik Performa
Metrik ini memberi tahu kita kinerja keseluruhan model di data uji.
# Ambil metrik (Accuracy & ROC AUC) untuk No SMOTE
metrics_no_prep <- final_no_prep %>%
collect_metrics() %>%
mutate(workflow = "No SMOTE")
# Ambil metrik (Accuracy & ROC AUC) untuk SMOTE
metrics_smote <- final_smote %>%
collect_metrics() %>%
mutate(workflow = "SMOTE")
# Gabungkan dan tampilkan
bind_rows(metrics_no_prep, metrics_smote)## # A tibble: 6 × 5
## .metric .estimator .estimate .config workflow
## <chr> <chr> <dbl> <chr> <chr>
## 1 accuracy multiclass 0.772 Preprocessor1_Model1 No SMOTE
## 2 roc_auc hand_till 0.825 Preprocessor1_Model1 No SMOTE
## 3 brier_class multiclass 0.156 Preprocessor1_Model1 No SMOTE
## 4 accuracy multiclass 0.804 Preprocessor1_Model1 SMOTE
## 5 roc_auc hand_till 0.878 Preprocessor1_Model1 SMOTE
## 6 brier_class multiclass 0.152 Preprocessor1_Model1 SMOTE
Analisis Awal: Dari tabel ringkasan di atas, kita bisa melihat perbandingan langsung Accuracy dan ROC AUC (rata-rata) pada data uji. Workflow SMOTE menunjukkan performa lebih baik secara keseluruhan.
3.6.2. Perbandingan Metrik per Kelas (Lebih Detail)
Akurasi saja tidak cukup untuk data imbalanced. Kita perlu melihat metrik per kelas, terutama Balanced Accuracy, Precision, dan Recall.
# === Metrik Detail untuk No SMOTE ===
class_metrics_no_prep <- final_no_prep %>%
collect_predictions() %>%
yardstick::metric_set(accuracy, bal_accuracy, precision, recall, f_meas)(truth = Y, estimate = .pred_class) %>%
mutate(workflow = "No SMOTE")## Warning: While computing multiclass `precision()`, some levels had no predicted events
## (i.e. `true_positive + false_positive = 0`).
## Precision is undefined in this case, and those levels will be removed from the
## averaged result.
## Note that the following number of true events actually occurred for each
## problematic event level:
## 'C': 8
## While computing multiclass `precision()`, some levels had no predicted events
## (i.e. `true_positive + false_positive = 0`).
## Precision is undefined in this case, and those levels will be removed from the
## averaged result.
## Note that the following number of true events actually occurred for each
## problematic event level:
## 'C': 8
## [1] "--- Hasil Model: No SMOTE (Data Asli) ---"
## # A tibble: 5 × 4
## .metric .estimator .estimate workflow
## <chr> <chr> <dbl> <chr>
## 1 accuracy multiclass 0.772 No SMOTE
## 2 bal_accuracy macro 0.624 No SMOTE
## 3 precision macro 0.718 No SMOTE
## 4 recall macro 0.454 No SMOTE
## 5 f_meas macro 0.679 No SMOTE
# === Metrik Detail untuk SMOTE ===
class_metrics_smote <- final_smote %>%
collect_predictions() %>%
yardstick::metric_set(accuracy, bal_accuracy, precision, recall, f_meas)(truth = Y, estimate = .pred_class) %>%
mutate(workflow = "SMOTE")
print("--- Hasil Model: SMOTE + Normalisasi ---")## [1] "--- Hasil Model: SMOTE + Normalisasi ---"
## # A tibble: 5 × 4
## .metric .estimator .estimate workflow
## <chr> <chr> <dbl> <chr>
## 1 accuracy multiclass 0.804 SMOTE
## 2 bal_accuracy macro 0.788 SMOTE
## 3 precision macro 0.700 SMOTE
## 4 recall macro 0.725 SMOTE
## 5 f_meas macro 0.701 SMOTE
Analisis Mendalam:
Balanced Accuracy: Ini adalah metrik yang paling adil. Model SMOTE memiliki 0.787 lebih tinggi dari No SMOTE yaitu 0.624 , menunjukkan SMOTE memiliki kemampuannya yang lebih dalam menangani kelas minoritas.
Recall (Kelas C): Lihat
recalluntuk kelas ‘C’ (jika itu minoritas). ModelNo SMOTEmencapai 0.454, sementaraSMOTEmencapai 0.725. Ini membuktikan bahwa SMOTE berhasil meningkatkan kemampuan model untuk “menemukan” kelas minoritas.
3.6.3. Perbandingan Confusion Matrix
Visualisasi adalah cara terbaik untuk melihat di mana letak kesalahan model.
# === Confusion Matrix: No SMOTE ===
conf_mat_no_prep <- final_no_prep %>%
collect_predictions() %>%
conf_mat(truth = Y, estimate = .pred_class)
autoplot(conf_mat_no_prep, type = "heatmap") +
labs(title = "Confusion Matrix: Model KNN (Data Asli)") +
scale_fill_gradient2(low = "#E3F2FD", mid = "#64B5F6", high = "#1565C0")## Scale for fill is already present.
## Adding another scale for fill, which will replace the existing scale.
# === Confusion Matrix: SMOTE ===
conf_mat_smote <- final_smote %>%
collect_predictions() %>%
conf_mat(truth = Y, estimate = .pred_class)
autoplot(conf_mat_smote, type = "heatmap") +
labs(title = "Confusion Matrix: Model KNN (SMOTE + Normalisasi)") +
scale_fill_gradient2(low = "#FFF3E0", mid = "#FFB74D", high = "#EF6C00")## Scale for fill is already present.
## Adding another scale for fill, which will replace the existing scale.
3.7. Kesimpulan Model KNN
# 1. Kumpulkan metrik dari kedua model
metrics_no_prep <- final_no_prep %>%
collect_metrics() %>%
mutate(workflow = "No SMOTE")
metrics_smote <- final_smote %>%
collect_metrics() %>%
mutate(workflow = "SMOTE")
# 2. Gabungkan, filter hanya akurasi, dan format
perbandingan_akurasi <- bind_rows(metrics_no_prep, metrics_smote) %>%
filter(.metric == "accuracy") %>%
select(Workflow = workflow, Akurasi = .estimate) %>%
# (Opsional) Format angka jadi persentase
mutate(Akurasi = scales::percent(Akurasi, accuracy = 0.01))
# 3. Tampilkan tabel dengan judul
knitr::kable(
perbandingan_akurasi,
caption = "Perbandingan Akurasi Final pada Data Uji"
)| Workflow | Akurasi |
|---|---|
| No SMOTE | 77.22% |
| SMOTE | 80.38% |
Berdasarkan perbandingan langsung pada data uji, workflow SMOTE terbukti menjadi pemenang yang jelas.
Meskipun akurasi keseluruhan beda sedikit, model
SMOTE menunjukkan Balanced Accuracy yang
jauh lebih superior dan Recall yang jauh lebih baik
untuk kelas minoritas ‘C’. Ini membuktikan bahwa untuk dataset ini,
[penanganan imbalance (SMOTE) dan normalisasi] sangat
krusial untuk menciptakan model yang adil dan akurat.
Kesimpulan
Metode K-Nearest Neighbour dengan penanganan imbalance
SMOTE (ditambah normalisasi) memiliki kemampuan
yang lebih superior dan seimbang (adil) dalam
mengklasifikasikan Peringkat Akreditasi (A, B, dan C) dibandingkan model
yang dilatih pada data asli (No SMOTE).
Evaluasi akhir menunjukkan bahwa model SMOTE adalah
pemenang yang jelas karena:
Kinerja Kelas Minoritas Jauh Lebih Baik: Model
SMOTEmenunjukkan nilai Balanced Accuracy dan Recall (untuk kelas minoritas ‘C’) yang jauh lebih tinggi. Ini membuktikan bahwa model tersebut berhasil “belajar” untuk mengidentifikasi kelas yang sulit dan tidak hanya menebak kelas mayoritas.Model yang Lebih Fungsional: Model
No SMOTEmungkin memiliki akurasi yang tampak tinggi, tetapi ini adalah “akurasi palsu” yang didapat dari kegagalannya mengenali kelas minoritas. ModelSMOTEmenghasilkan model yang secara fungsional jauh lebih berguna dan adil di dunia nyata.
Untuk studi kasus ini, terbukti bahwa penanganan imbalance data menggunakan SMOTE dan normalisasi data adalah langkah yang wajib dan krusial untuk menghasilkan model KNN yang valid.