Pada praktikum sebelumnya, klasifikasi dan prediksi dilakukan menggunakan CART, Bagging, Random Forest, dan Artificial Neural Network (ANN). Metode-metode tersebut membangun model terlebih dahulu dari data training, misalnya berupa aturan pemisahan atau bobot jaringan, lalu model tersebut digunakan untuk memprediksi data baru. K-Nearest Neighbor (K-NN) bekerja dengan cara yang berbeda. K-NN tidak membangun model secara eksplisit, tetapi menyimpan seluruh data training dan baru melakukan perhitungan ketika ada data yang akan diklasifikasikan (Hastie dkk., 2009; Cunningham & Delany, 2021). Kelas suatu observasi baru ditentukan berdasarkan kelas mayoritas dari \(k\) observasi training yang paling dekat dengannya (Fix & Hodges, 1951; Cover & Hart, 1967). Karena keputusan diambil berdasarkan jarak, hasil K-NN sangat dipengaruhi oleh skala variabel dan pemilihan nilai \(k\) (James dkk., 2021).
Kanker payudara merupakan kanker yang paling banyak didiagnosis pada perempuan di dunia (Bray dkk., 2024). Salah satu cara untuk menentukan apakah suatu tumor payudara bersifat jinak (benign) atau ganas (malignant) adalah pemeriksaan sitologi dari sampel aspirasi jarum halus, di mana beberapa karakteristik sel diberi skor 1 sampai 10 (Wolberg & Mangasarian, 1990). Dalam diagnosis, kedua jenis kesalahan tidak memiliki akibat yang sama. Tumor ganas yang diprediksi jinak (false negative) dapat membuat pasien tidak segera mendapat penanganan, sedangkan tumor jinak yang diprediksi ganas (false positive) masih dapat dikoreksi melalui pemeriksaan lanjutan. Oleh karena itu, model klasifikasi tidak cukup dinilai dari accuracy saja, tetapi juga dari sensitivity, yaitu kemampuan model mengenali tumor ganas (Altman & Bland, 1994a).
Data yang digunakan pada praktikum ini adalah Wisconsin Breast Cancer Database (BreastCancer) dari paket mlbench (Leisch & Dimitriadou, 2021). Data terlebih dahulu melalui tahap pra-pemrosesan, kemudian dibagi menjadi 80% data training dan 20% data testing dengan set.seed(12308). Prediktor diskalakan menggunakan normalisasi min-max dan hasilnya dibandingkan dengan tanpa penskalaan dan standardisasi z-score. Model K-NN dibangun menggunakan fungsi knn() dari paket class (Venables & Ripley, 2002) untuk \(k\) = 3, 5, 7, 9, dan 11, lalu dievaluasi menggunakan confusion matrix dari paket caret (Kuhn, 2008) dengan ukuran accuracy, sensitivity, specificity, dan positive predictive value. Selain itu, nilai \(k\) dieksplorasi dari 1 sampai 25 dan kurva performanya divisualisasikan menggunakan paket ggplot2 (Wickham, 2016). Nilai \(k\) optimal ditentukan dengan mempertimbangkan banyaknya false negative. Berdasarkan hal tersebut, pada praktikum Pertemuan 7 ini dilakukan klasifikasi K-NN pada data BreastCancer menggunakan RStudio.
Berdasarkan latar belakang di atas, rumusan masalah dalam praktikum ini adalah sebagai berikut:
Berdasarkan rumusan masalah di atas, tujuan praktikum ini adalah sebagai berikut:
Adapun manfaat pada penelitian ini yaitu:
Adapun batasan masalah pada praktikum ini adalah lakukan pemodelan klasifikasi K-NN untuk nilai \(k \in \{3, 5, 7, 9, 11\}\) dengan data training 80% dan testing 20% menggunakan set.seed(12308). Kemudian susun tabel perbandingan metrik evaluasi (Accuracy, Sensitivity, Specificity) dan simpulkan nilai \(k\) paling optimal dengan mempertimbangkan pentingnya meminimalkan kesalahan diagnosis sel kanker ganas (False Negative). Data yang digunakan adalah data Wisconsin Breast Cancer Database (BreastCancer) dari paket mlbench dengan variabel respon Class.
Adapun sistematika penulisan dari praktikum ini adalah:
BAB IPENDAHULUAN
Bab I terdiri dari latar belakang, rumusan masalah, tujuan penelitian, manfaat penelitian, batasan masalah, dan sistematika penulisan.
BAB IITINJAUAN PUSTAKA
Bab ini berisi kajian pustaka dari jurnal, buku, dan modul praktikum tentang konsep dasar dan karakteristik asimtotik K-Nearest Neighbor, kompromi bias dan varians, metrik jarak, penskalaan fitur, splitting data, dan evaluasi model klasifikasi.
BAB IIIMETODE PENELITIAN
Bab ini terdiri dari sumber data, variabel penelitian, langkah-langkah analisis, dan diagram alir.
BAB IVHASIL DAN PEMBAHASAN
Bab ini merupakan tahapan pencapaian hasil yang diperoleh dari analisis data yang dilakukan serta penjelasan dari hasil tersebut di bagian pembahasan.
BAB VKESIMPULAN DAN SARAN
Bab ini merupakan bagian penutup dalam laporan ini berupa penarikan kesimpulan mengenai hasil analisis dari penelitian serta penyampaian saran oleh penulis.
DAFTAR PUSTAKA
LAMPIRAN
Algoritma K-Nearest Neighbors (K-NN) pertama kali digagas oleh Fix dan Hodges (1951) sebagai metode klasifikasi nonparametrik dalam laporan penelitian untuk USAF School of Aviation Medicine. Landasan teoretis konvergensinya kemudian dibuktikan secara analitis oleh Cover dan Hart (1967). Berbeda dengan metode parametrik seperti analisis diskriminan linear atau regresi logistik yang mengasumsikan bentuk distribusi data tertentu, K-NN termasuk instance-based learning atau lazy learning. K-NN tidak membentuk model eksplisit pada tahap pelatihan, tetapi menyimpan seluruh data training dan menunda perhitungan sampai terdapat data baru (query point) yang akan diprediksi (Hastie dkk., 2009; Cunningham & Delany, 2021).
Misalkan data training terdiri dari \(n\) observasi berpasangan \[ D = \{(\mathbf{x}_1, y_1), (\mathbf{x}_2, y_2), \ldots, (\mathbf{x}_n, y_n)\} \tag{2.1} \] dengan \(\mathbf{x}_i \in \mathbb{R}^p\) adalah vektor prediktor berdimensi \(p\) dan \(y_i \in \{1, 2, \ldots, C\}\) adalah label kelas. Untuk observasi baru \(\mathbf{x}_0\), algoritma mencari himpunan \(N_k(\mathbf{x}_0)\) yang berisi \(k\) observasi training paling dekat dengan \(\mathbf{x}_0\) berdasarkan suatu ukuran jarak. Peluang posterior bahwa \(\mathbf{x}_0\) termasuk kelas \(j\) diduga dengan proporsi kelas \(j\) di antara tetangga terdekatnya, yaitu \[ \hat{P}(Y = j \mid X = \mathbf{x}_0) = \frac{1}{k}\sum_{i \in N_k(\mathbf{x}_0)} I(y_i = j) \tag{2.2} \] dengan \(I(\cdot)\) adalah fungsi indikator \[ I(y_i = j) = \begin{cases} 1, & \text{jika } y_i = j \\ 0, & \text{jika } y_i \neq j \end{cases} \tag{2.3} \] Keputusan klasifikasi akhir diambil berdasarkan suara terbanyak (majority voting), yaitu \[ \hat{G}(\mathbf{x}_0) = \underset{j \in \{1, 2, \ldots, C\}}{\arg\max}\ \hat{P}(Y = j \mid X = \mathbf{x}_0) \tag{2.4} \] Pada data dengan dua kelas, Persamaan (2.4) sama dengan memilih kelas yang paling banyak muncul di antara \(k\) tetangga. Ilustrasi cara kerja K-NN dapat dilihat pada Gambar 1.
Gambar 1. Ilustrasi klasifikasi K-NN dengan \(k\) = 3 dan \(k\) = 7
Pada Gambar 1, titik hitam \(\mathbf{x}_0\) merupakan observasi baru. Dengan \(k\) = 3, dua dari tiga tetangga terdekat termasuk kelas ganas sehingga \(\mathbf{x}_0\) diklasifikasikan sebagai ganas. Dengan \(k\) = 7, lima dari tujuh tetangga termasuk kelas jinak sehingga \(\mathbf{x}_0\) diklasifikasikan sebagai jinak. Ilustrasi ini menunjukkan bahwa observasi yang sama dapat memperoleh kelas yang berbeda jika nilai \(k\) berubah.
Cover dan Hart (1967) membuktikan bahwa ketika ukuran data training mendekati tak hingga (\(n \to \infty\)), peluang kesalahan klasifikasi aturan satu tetangga terdekat (\(k\) = 1), yang dinotasikan \(R\), dibatasi oleh peluang kesalahan pengklasifikasi Bayes optimal \(R^{*}\) sebagai berikut. \[ R^{*} \leq R \leq R^{*}\left(2 - \frac{C}{C-1}R^{*}\right) \leq 2R^{*} \tag{2.5} \] Teorema ini menjamin bahwa secara asimtotik kesalahan aturan tetangga terdekat paling buruk tidak lebih dari dua kali kesalahan pengklasifikasi optimal teoretis. Untuk dua kelas (\(C\) = 2), batas atasnya menjadi \(2R^{*}(1 - R^{*})\). Namun, jaminan ini berlaku untuk ukuran sampel yang sangat besar, sehingga pada data berukuran terbatas performa K-NN tetap bergantung pada nilai \(k\), ukuran jarak, dan penskalaan prediktor.
Pemilihan hiperparameter \(k\) menentukan keseimbangan antara bias dan varians (James dkk., 2021; Laboratorium Matematika FMIPA Universitas Bengkulu, 2026).
Pada klasifikasi dua kelas, nilai \(k\) biasanya dipilih ganjil agar banyaknya suara kedua kelas tidak sama. Namun, nilai \(k\) ganjil tidak selalu menghilangkan hasil seri. Jika terdapat beberapa observasi training dengan jarak yang sama dengan jarak tetangga ke-\(k\), fungsi knn() dengan argumen use.all = TRUE mengikutsertakan seluruh observasi tersebut dalam voting, sehingga banyaknya tetangga yang memberikan suara dapat lebih dari \(k\) dan dapat berjumlah genap. Jika suara kedua kelas sama banyak, kelas dipilih secara acak (Venables & Ripley, 2002). Oleh karena itu, set.seed() perlu diberikan sebelum knn() agar hasilnya dapat direproduksi.
Kedekatan antara dua observasi \(\mathbf{x} = (x_1, x_2, \ldots, x_p)^\top\) dan \(\mathbf{y} = (y_1, y_2, \ldots, y_p)^\top\) dapat diukur menggunakan beberapa metrik jarak (Cunningham & Delany, 2021; Laboratorium Matematika FMIPA Universitas Bengkulu, 2026).
Fungsi knn() pada paket class yang digunakan dalam praktikum ini menggunakan jarak Euclidean pada Persamaan (2.6) (Venables & Ripley, 2002). Jarak Euclidean memberikan bobot yang sama pada setiap variabel, sehingga jika dua variabel berkorelasi kuat, informasi yang sama ikut terhitung dua kali. Hal inilah yang dikoreksi oleh jarak Mahalanobis.
Jarak Euclidean dan Manhattan menghitung selisih nilai secara langsung, sehingga variabel dengan rentang nilai yang besar akan mendominasi perhitungan jarak. Oleh karena itu, tahap pra-pemrosesan pada K-NN perlu menyertakan penskalaan fitur (Han dkk., 2012; Laboratorium Matematika FMIPA Universitas Bengkulu, 2026). Dua cara yang umum digunakan adalah sebagai berikut.
Normalisasi min-max menyamakan rentang setiap variabel, sedangkan standardisasi z-score menyamakan simpangan bakunya. Akibatnya, pada z-score variabel dengan simpangan baku kecil memperoleh bobot yang lebih besar dalam jarak untuk setiap satu satuan perbedaan nilai aslinya. Parameter penskalaan, yaitu \(x_{min}\), \(x_{max}\), \(\mu\), dan \(\sigma\), harus dihitung dari data training saja, lalu digunakan juga untuk data testing. Jika dihitung dari seluruh data, informasi dari data testing ikut masuk ke tahap pelatihan sehingga hasil evaluasi menjadi terlalu optimis (Kuhn & Johnson, 2013). Selain penskalaan, K-NN juga mensyaratkan seluruh prediktor bertipe numerik dan tidak memiliki missing value, karena jarak tidak dapat dihitung pada nilai yang kosong.
Data dibagi menjadi data training sebagai tempat mencari tetangga dan data testing untuk menguji kemampuan model pada data yang belum pernah dilihat (Muraina, 2022). Pada praktikum ini pembagian dilakukan menggunakan fungsi createDataPartition() dari paket caret yang mengambil sampel acak di dalam setiap kelas, sehingga proporsi benign dan malignant pada data training dan testing tetap mendekati proporsi data asli (Kuhn, 2008).
Hasil klasifikasi dua kelas diringkas dalam confusion matrix yang membandingkan kelas prediksi dengan kelas aktual (Fawcett, 2006). Pada praktikum ini kelas positif adalah malignant karena tujuan pemodelan adalah mendeteksi tumor ganas. Bentuk confusion matrix dapat dilihat pada Tabel 1.
| Prediksi | Aktual malignant | Aktual benign |
|---|---|---|
| Malignant | True Positive (TP) | False Positive (FP) |
| Benign | False Negative (FN) | True Negative (TN) |
Berdasarkan Tabel 1, ukuran evaluasi yang digunakan adalah sebagai berikut (Altman & Bland, 1994a, 1994b; Fawcett, 2006). \[ \text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN} \tag{2.13} \] \[ \text{Sensitivity} = \frac{TP}{TP + FN} \tag{2.14} \] \[ \text{Specificity} = \frac{TN}{TN + FP} \tag{2.15} \] \[ \text{PPV} = \frac{TP}{TP + FP} \tag{2.16} \]
Accuracy menyatakan proporsi seluruh observasi yang diklasifikasikan dengan benar. Sensitivity menyatakan proporsi tumor ganas yang berhasil dideteksi sebagai ganas, sedangkan specificity menyatakan proporsi tumor jinak yang berhasil diklasifikasikan sebagai jinak (Altman & Bland, 1994a). Positive Predictive Value (PPV) menyatakan proporsi observasi yang diprediksi ganas yang memang benar-benar ganas. Berbeda dengan sensitivity dan specificity, nilai PPV juga dipengaruhi oleh proporsi kelas positif pada data (Altman & Bland, 1994b). Berdasarkan Persamaan (2.14), meminimalkan FN sama dengan memaksimalkan sensitivity. Pada data yang kelasnya tidak seimbang, accuracy dapat tetap tinggi walaupun banyak observasi kelas minoritas salah diklasifikasikan, sehingga accuracy sebaiknya tidak digunakan sendirian (Fawcett, 2006).
Model K-NN dibangun menggunakan fungsi knn() dari paket class (Venables & Ripley, 2002), sedangkan evaluasi dilakukan menggunakan fungsi confusionMatrix() dari paket caret (Kuhn, 2008). Argumen yang digunakan dan perannya dapat dilihat pada Tabel 2.
| Fungsi | Argumen | Nilai | Peran |
|---|---|---|---|
| knn() | train | Prediktor data training hasil penskalaan | Data tempat mencari tetangga terdekat |
| test | Prediktor data testing hasil penskalaan | Data yang akan diklasifikasikan | |
| cl | train$Class | Kelas aktual data training | |
| k | 3, 5, 7, 9, 11 (eksplorasi 1 sampai 25) | Banyaknya tetangga terdekat | |
| prob | TRUE | Menyimpan proporsi suara kelas pemenang pada atribut prob | |
| use.all | TRUE (default) | Seluruh observasi yang jaraknya sama dengan tetangga ke-\(k\) ikut voting | |
| confusionMatrix() | data | Kelas prediksi | Hasil klasifikasi model |
| reference | test$Class | Kelas aktual data testing | |
| positive | “malignant” | Kelas positif, default-nya adalah level pertama, yaitu benign |
Hal yang perlu diperhatikan adalah argumen positive. Jika tidak diisi, confusionMatrix() menjadikan level pertama faktor sebagai kelas positif. Pada data BreastCancer level pertama adalah benign, sehingga nilai sensitivity, specificity, dan PPV yang ditampilkan tidak sesuai dengan yang dimaksud pada Persamaan (2.14) sampai (2.16).
Data yang digunakan adalah data sekunder berupa dataset BreastCancer yang tersedia pada paket mlbench (Leisch & Dimitriadou, 2021). Data ini merupakan Wisconsin Breast Cancer Database yang dikumpulkan oleh Dr. William H. Wolberg di University of Wisconsin Hospitals, Madison (Wolberg & Mangasarian, 1990). Data terdiri dari 699 observasi sampel jaringan payudara dengan 11 variabel, yaitu satu variabel kode sampel, sembilan karakteristik sitologi sel yang diberi skor 1 sampai 10, dan satu variabel kelas tumor.
Variabel yang digunakan terdiri dari satu variabel respon dan sembilan variabel prediktor. Variabel respon (Y) adalah Class dengan dua kategori, yaitu benign dan malignant. Variabel Id hanya berupa kode sampel sehingga tidak digunakan sebagai prediktor. Seluruh prediktor berupa skor 1 sampai 10, dengan skor yang lebih besar menunjukkan kondisi sel yang semakin tidak normal. Rincian variabel dapat dilihat pada Tabel 3.
| Variabel | Keterangan | Skala | Peran |
|---|---|---|---|
| Class | Kelas tumor: benign (jinak) dan malignant (ganas) | Nominal | Respon (Y) |
| Cl.thickness | Ketebalan gumpalan sel (clump thickness) | Skor 1–10 | Prediktor (X1) |
| Cell.size | Keseragaman ukuran sel | Skor 1–10 | Prediktor (X2) |
| Cell.shape | Keseragaman bentuk sel | Skor 1–10 | Prediktor (X3) |
| Marg.adhesion | Perlekatan sel pada tepi (marginal adhesion) | Skor 1–10 | Prediktor (X4) |
| Epith.c.size | Ukuran sel epitel tunggal | Skor 1–10 | Prediktor (X5) |
| Bare.nuclei | Inti sel tanpa sitoplasma (bare nuclei) | Skor 1–10 | Prediktor (X6) |
| Bl.cromatin | Tekstur kromatin (bland chromatin) | Skor 1–10 | Prediktor (X7) |
| Normal.nucleoli | Nukleolus normal | Skor 1–10 | Prediktor (X8) |
| Mitoses | Aktivitas pembelahan sel (mitosis) | Skor 1–10 | Prediktor (X9) |
Langkah-langkah analisis yang dilakukan adalah sebagai berikut.
Gambar 2. Diagram alir penelitian
Pada Gambar 2, data BreastCancer terlebih dahulu melalui pra-pemrosesan dan eksplorasi, kemudian dibagi menjadi data training dan testing dan diskalakan. Untuk setiap nilai \(k\), data testing diklasifikasikan menggunakan K-NN, lalu dihitung confusion matrix dan ukuran evaluasinya. Proses tersebut diulang sampai kelima nilai \(k\) selesai. Selanjutnya, kelima nilai \(k\) dibandingkan bersama kurva performa untuk \(k\) = 1 sampai 25, terutama berdasarkan FN, untuk menentukan nilai \(k\) optimal.
Library yang digunakan adalah mlbench, class, caret, dan ggplot2. Paket mlbench digunakan untuk memuat data, class untuk membangun K-NN melalui fungsi knn(), caret untuk membagi data secara terstratifikasi dan menghitung confusion matrix, serta ggplot2 untuk visualisasi.
## 'data.frame': 699 obs. of 11 variables:
## $ Id : chr "1000025" "1002945" "1015425" "1016277" ...
## $ Cl.thickness : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 5 5 3 6 4 8 1 2 2 4 ...
## $ Cell.size : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 1 8 1 10 1 1 1 2 ...
## $ Cell.shape : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 1 8 1 10 1 2 1 1 ...
## $ Marg.adhesion : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 5 1 1 3 8 1 1 1 1 ...
## $ Epith.c.size : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 7 2 3 2 7 2 2 2 2 ...
## $ Bare.nuclei : Factor w/ 10 levels "1","2","3","4",..: 1 10 2 4 1 10 10 1 1 1 ...
## $ Bl.cromatin : Factor w/ 10 levels "1","2","3","4",..: 3 3 3 3 3 9 3 3 1 2 ...
## $ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 2 1 7 1 7 1 1 1 1 ...
## $ Mitoses : Factor w/ 9 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 5 1 ...
## $ Class : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
##
## benign malignant
## 458 241
Data BreastCancer terdiri dari 699 observasi dan 11 variabel. Variabel Id bertipe karakter, lima prediktor pertama bertipe faktor berurutan (ordered factor), dan empat prediktor lainnya bertipe faktor. Karena K-NN menghitung jarak, seluruh prediktor perlu diubah menjadi numerik. Kelas terdiri dari 458 benign dan 241 malignant, sehingga banyaknya tumor jinak hampir dua kali tumor ganas.
## Id Cl.thickness Cell.size Cell.shape Marg.adhesion
## 0 0 0 0 0
## Epith.c.size Bare.nuclei Bl.cromatin Normal.nucleoli Mitoses
## 0 16 0 0 0
## Class
## 0
##
## benign malignant
## 14 2
## [1] 8
## Id Cl.thickness Cell.size Class
## 209 1218860 1 1 benign
## 254 1100524 6 10 malignant
## 255 1116116 9 10 malignant
## 259 1198641 3 1 benign
## 273 320675 3 3 malignant
## 339 704097 1 1 benign
## 562 1321942 5 1 benign
## 685 466906 1 1 benign
## [1] "1" "2" "3" "4" "5" "6" "7" "8" "10"
Terdapat 16 missing value yang seluruhnya berada pada Bare.nuclei, yaitu 14 observasi benign dan 2 observasi malignant. Selain itu, terdapat 8 baris yang sama persis dengan baris lain pada seluruh variabel, termasuk Id, sehingga baris tersebut merupakan sampel yang tercatat dua kali. Pada K-NN, data duplikat seperti ini perlu dihapus. Jika satu salinan masuk data training dan salinan lainnya masuk data testing, observasi testing tersebut akan menemukan tetangga dengan jarak nol dan kelas yang sama, sehingga hasil evaluasi menjadi terlalu baik.
Observasi dengan missing value juga dihapus karena jumlahnya hanya 16 dari 699 observasi (2,29%) dan hanya terdapat pada satu variabel, sedangkan K-NN tidak dapat menghitung jarak jika ada nilai yang kosong. Variabel Id dihapus karena hanya berupa kode sampel. Prediktor diubah menjadi numerik menggunakan as.numeric(as.character()), bukan as.numeric() langsung. Hal ini karena level Mitoses tidak memuat nilai 9, sehingga as.numeric() langsung akan mengubah skor 10 menjadi 9 sesuai urutan levelnya.
bc <- BreastCancer[!duplicated(BreastCancer), ]
bc <- na.omit(bc)
bc$Id <- NULL
bc[, 1:9] <- lapply(bc[, 1:9], function(x) as.numeric(as.character(x)))
dim(bc)## [1] 675 10
## 'data.frame': 675 obs. of 10 variables:
## $ Cl.thickness : num 5 5 3 6 4 8 1 2 2 4 ...
## $ Cell.size : num 1 4 1 8 1 10 1 1 1 2 ...
## $ Cell.shape : num 1 4 1 8 1 10 1 2 1 1 ...
## $ Marg.adhesion : num 1 5 1 1 3 8 1 1 1 1 ...
## $ Epith.c.size : num 2 7 2 3 2 7 2 2 2 2 ...
## $ Bare.nuclei : num 1 10 2 4 1 10 10 1 1 1 ...
## $ Bl.cromatin : num 3 3 3 3 3 9 3 3 1 2 ...
## $ Normal.nucleoli: num 1 2 1 7 1 7 1 1 1 1 ...
## $ Mitoses : num 1 1 1 1 1 1 1 1 5 1 ...
## $ Class : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
## - attr(*, "na.action")= 'omit' Named int [1:16] 24 41 140 146 159 165 235 249 271 288 ...
## ..- attr(*, "names")= chr [1:16] "24" "41" "140" "146" ...
##
## benign malignant
## 439 236
##
## benign malignant
## 0.6504 0.3496
Setelah pra-pemrosesan, data terdiri dari 675 observasi dengan 9 prediktor numerik dan 1 variabel respon. Banyaknya kelas benign 439 observasi (65,04%) dan malignant 236 observasi (34,96%), sehingga proporsinya hampir sama dengan data awal.
## Class Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei
## 1 benign 2.97 1.31 1.42 1.35 2.11 1.35
## 2 malignant 7.20 6.56 6.54 5.64 5.31 7.61
## Bl.cromatin Normal.nucleoli Mitoses
## 1 2.08 1.26 1.07
## 2 5.98 5.90 2.61
## Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei
## Cl.thickness 1.000 0.641 0.653 0.489 0.519 0.594
## Cell.size 0.641 1.000 0.906 0.715 0.749 0.690
## Cell.shape 0.653 0.906 1.000 0.694 0.717 0.711
## Marg.adhesion 0.489 0.715 0.694 1.000 0.603 0.676
## Epith.c.size 0.519 0.749 0.717 0.603 1.000 0.583
## Bare.nuclei 0.594 0.690 0.711 0.676 0.583 1.000
## Bl.cromatin 0.556 0.759 0.738 0.672 0.623 0.679
## Normal.nucleoli 0.534 0.724 0.723 0.602 0.634 0.588
## Mitoses 0.351 0.462 0.443 0.418 0.484 0.340
## Bl.cromatin Normal.nucleoli Mitoses
## Cl.thickness 0.556 0.534 0.351
## Cell.size 0.759 0.724 0.462
## Cell.shape 0.738 0.723 0.443
## Marg.adhesion 0.672 0.602 0.418
## Epith.c.size 0.623 0.634 0.484
## Bare.nuclei 0.679 0.588 0.340
## Bl.cromatin 1.000 0.669 0.346
## Normal.nucleoli 0.669 1.000 0.433
## Mitoses 0.346 0.433 1.000
## [1] 226
bc.long <- data.frame(Variabel = factor(rep(names(bc)[1:9], each = nrow(bc)), levels = names(bc)[1:9]),
Skor = unlist(bc[, 1:9]), Class = rep(bc$Class, 9))
ggplot(bc.long, aes(Class, Skor, fill = Class)) +
geom_boxplot(width = 0.6, outlier.size = 0.8) +
facet_wrap(~ Variabel, ncol = 3) +
scale_fill_manual(values = c(benign = "#9AD1D4", malignant = "#F8CBA6")) +
scale_y_continuous(breaks = c(1, 4, 7, 10)) +
labs(x = NULL, y = "Skor") +
theme_minimal() +
theme(legend.position = "none")Gambar 3. Sebaran skor setiap prediktor menurut kelas tumor
Rata-rata skor seluruh prediktor pada kelas malignant lebih besar daripada kelas benign. Perbedaan terbesar terdapat pada Bare.nuclei (1,35 dan 7,61), Cell.size (1,31 dan 6,56), dan Cell.shape (1,42 dan 6,54), sedangkan perbedaan terkecil terdapat pada Mitoses (1,07 dan 2,61). Pada Gambar 3, sebagian besar skor kelas benign berada pada nilai 1 sampai 4, sedangkan skor kelas malignant menyebar lebar. Mitoses bernilai 1 pada sebagian besar observasi di kedua kelas sehingga kemampuannya membedakan kelas paling kecil. Hal ini menunjukkan bahwa kedua kelas umumnya cukup terpisah, sehingga observasi yang berdekatan cenderung berasal dari kelas yang sama. Kondisi ini sesuai dengan cara kerja K-NN.
Korelasi antarprediktor seluruhnya positif. Korelasi terkuat terdapat antara Cell.size dan Cell.shape (0,906), sedangkan Mitoses memiliki korelasi paling lemah dengan prediktor lainnya (0,340 sampai 0,484). Seperti dijelaskan pada subbab 2.3, jarak Euclidean memberi bobot yang sama pada setiap variabel, sehingga informasi keseragaman sel dari Cell.size dan Cell.shape yang hampir sama ikut terhitung dua kali dalam jarak. Selain itu, karena skor berupa bilangan bulat 1 sampai 10, terdapat 226 observasi yang nilai kesembilan prediktornya sama dengan observasi lain. Akibatnya, banyak observasi memiliki jarak yang sama, sehingga hasil seri pada penentuan tetangga ke-\(k\) akan sering terjadi.
Sesuai teladan untuk NPM genap, data dibagi menjadi 80% data training dan 20% data testing dengan set.seed(12308), di mana 08 adalah dua digit terakhir NPM praktikan. Pembagian dilakukan secara terstratifikasi menggunakan createDataPartition() agar proporsi kelas malignant pada data testing tetap terjaga, karena kelas inilah yang menjadi fokus evaluasi.
set.seed(12308)
index <- createDataPartition(bc$Class, p = 0.8, list = FALSE)
train <- bc[index, ]; nrow(train)## [1] 541
## [1] 134
##
## benign malignant
## 352 189
##
## benign malignant
## 87 47
##
## benign malignant
## 0.6493 0.3507
Sebanyak 541 observasi menjadi data training, yaitu 352 benign dan 189 malignant, sedangkan 134 observasi menjadi data testing, yaitu 87 benign dan 47 malignant. Proporsi kelas pada data testing (64,93% dan 35,07%) hampir sama dengan data keseluruhan. Karena data testing hanya memuat 47 tumor ganas, setiap tambahan satu FN menurunkan sensitivity sebesar \(1/47\) atau sekitar 2,13 poin persen.
Penskalaan utama dilakukan menggunakan normalisasi min-max sesuai Persamaan (2.11) dengan nilai minimum dan maksimum dari data training.
minmax <- function(data, mn, mx) as.data.frame(mapply(function(x, a, b) (x - a) / (b - a), data, mn, mx))
mn <- sapply(train[, 1:9], min)
mx <- sapply(train[, 1:9], max)
rbind(Minimum = mn, Maksimum = mx)## Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei
## Minimum 1 1 1 1 1 1
## Maksimum 10 10 10 10 10 10
## Bl.cromatin Normal.nucleoli Mitoses
## Minimum 1 1 1
## Maksimum 10 10 10
train.n <- minmax(train[, 1:9], mn, mx)
test.n <- minmax(test[, 1:9], mn, mx)
summary(train.n[, 1:4])## Cl.thickness Cell.size Cell.shape Marg.adhesion
## Min. :0.0000 Min. :0.0000 Min. :0.0000 Min. :0.0000
## 1st Qu.:0.1111 1st Qu.:0.0000 1st Qu.:0.0000 1st Qu.:0.0000
## Median :0.3333 Median :0.0000 Median :0.0000 Median :0.0000
## Mean :0.3919 Mean :0.2440 Mean :0.2526 Mean :0.2050
## 3rd Qu.:0.5556 3rd Qu.:0.4444 3rd Qu.:0.4444 3rd Qu.:0.3333
## Max. :1.0000 Max. :1.0000 Max. :1.0000 Max. :1.0000
Pada data training, seluruh prediktor memiliki nilai minimum 1 dan maksimum 10, sehingga normalisasi min-max sama dengan mengubah setiap skor menjadi \((x - 1)/9\). Hasil ini juga sama dengan sintaks modul yang menormalisasi seluruh data sebelum splitting, karena nilai minimum dan maksimum seluruh data juga 1 dan 10. Untuk memahami pengaruh penskalaan, hasil K-NN dengan normalisasi min-max dibandingkan dengan tanpa penskalaan dan dengan standardisasi z-score sesuai Persamaan (2.12), menggunakan rata-rata dan simpangan baku data training.
## Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size
## 2.794 3.123 3.032 2.866 2.193
## Bare.nuclei Bl.cromatin Normal.nucleoli Mitoses
## 3.602 2.494 3.130 1.776
train.z <- as.data.frame(scale(train[, 1:9], center = mu, scale = sdv))
test.z <- as.data.frame(scale(test[, 1:9], center = mu, scale = sdv))
k <- c(3, 5, 7, 9, 11)
skala <- list("Tanpa penskalaan" = list(train[, 1:9], test[, 1:9]), "Min-max" = list(train.n, test.n),
"Z-score" = list(train.z, test.z))
banding.skala <- t(sapply(skala, function(d) sapply(k, function(j) {
set.seed(12308)
p <- knn(d[[1]], d[[2]], cl = train$Class, k = j)
paste(sum(p == "benign" & test$Class == "malignant"), "/", sum(p == "malignant" & test$Class == "benign"))
})))
colnames(banding.skala) <- paste("k =", k)
knitr::kable(banding.skala, align = "c", caption = "Tabel 4. Banyaknya FN / FP pada data *testing* menurut metode penskalaan")| k = 3 | k = 5 | k = 7 | k = 9 | k = 11 | |
|---|---|---|---|---|---|
| Tanpa penskalaan | 3 / 3 | 3 / 3 | 3 / 2 | 3 / 2 | 3 / 2 |
| Min-max | 3 / 3 | 3 / 3 | 3 / 2 | 3 / 2 | 3 / 2 |
| Z-score | 3 / 3 | 3 / 2 | 3 / 2 | 3 / 2 | 3 / 3 |
Berdasarkan Tabel 4, hasil tanpa penskalaan dan dengan normalisasi min-max sama persis untuk seluruh nilai \(k\). Hal ini karena min-max mengubah semua variabel dengan cara yang sama, yaitu \((x - 1)/9\), sehingga jarak antarobservasi hanya mengecil menjadi \(1/9\) kali jarak awal dan urutan tetangga terdekat tidak berubah. Pada data ini seluruh prediktor memang sudah diukur pada skala yang sama, yaitu skor 1 sampai 10.
Sebaliknya, standardisasi z-score mengubah FP pada \(k\) = 5 (dari 3 menjadi 2) dan pada \(k\) = 11 (dari 2 menjadi 3), sedangkan FN tetap 3. Perbedaan ini muncul karena z-score membagi setiap variabel dengan simpangan bakunya, dan simpangan baku setiap variabel berbeda. Simpangan baku Mitoses paling kecil (1,776) dan Bare.nuclei paling besar (3,602), sehingga setelah standardisasi selisih satu skor pada Mitoses bernilai sekitar dua kali lipat selisih satu skor pada Bare.nuclei. Padahal, hasil eksplorasi menunjukkan Mitoses adalah prediktor yang paling lemah membedakan kelas, sedangkan Bare.nuclei paling kuat. Jadi, metode penskalaan menentukan bobot setiap variabel dalam perhitungan jarak. Karena seluruh prediktor sudah memiliki skala dan makna yang sama, normalisasi min-max lebih sesuai untuk data ini dan digunakan pada analisis selanjutnya.
Model K-NN dibangun untuk kelima nilai \(k\). set.seed(12308) diberikan sebelum setiap knn() karena hasil seri pada voting ditentukan secara acak.
pred <- lapply(k, function(j) {
set.seed(12308)
knn(train = train.n, test = test.n, cl = train$Class, k = j, prob = TRUE)
})
names(pred) <- paste0("k=", k)
hasil <- data.frame(Aktual = test$Class, sapply(pred, as.character), check.names = FALSE, row.names = rownames(test))
head(hasil, 10)## Aktual k=3 k=5 k=7 k=9 k=11
## 1 benign benign benign benign benign benign
## 3 benign benign benign benign benign benign
## 5 benign benign benign benign benign benign
## 11 benign benign benign benign benign benign
## 12 benign benign benign benign benign benign
## 15 malignant malignant malignant malignant malignant malignant
## 18 benign benign benign benign benign benign
## 19 malignant malignant malignant malignant malignant malignant
## 35 benign benign benign benign benign benign
## 39 malignant malignant malignant malignant malignant malignant
Tabel hasil prediksi menunjukkan kelas aktual dan kelas prediksi setiap nilai \(k\) untuk sepuluh observasi pertama data testing, dengan nomor baris sebagai nomor observasi pada data BreastCancer. Pada sepuluh observasi tersebut, kelima nilai \(k\) menghasilkan prediksi yang sama dan seluruhnya sesuai dengan kelas aktual. Untuk melihat pengaruh hasil seri yang ditemukan pada eksplorasi data, dihitung banyaknya observasi training yang ikut voting untuk setiap observasi testing, serta banyaknya kesalahan klasifikasi jika seed sebelum knn() diganti dari 1 sampai 20.
D <- as.matrix(dist(rbind(test.n, train.n)))[seq_len(nrow(test.n)), -seq_len(nrow(test.n))]
voter <- sapply(k, function(j) apply(D, 1, function(d) sum(d <= sort(d)[j] + 1e-9)))
salah <- sapply(k, function(j) sapply(1:20, function(s) {
set.seed(s)
sum(knn(train.n, test.n, cl = train$Class, k = j) != test$Class)
}))
seri <- data.frame(k = k, Rata2 = colMeans(voter), Maks = apply(voter, 2, max),
Persen = 100 * colMeans(voter > rep(k, each = nrow(voter))),
Min_salah = apply(salah, 2, min), Maks_salah = apply(salah, 2, max))
knitr::kable(seri, digits = 2, align = "c", format.args = list(decimal.mark = ","),
col.names = c("k", "Rata-rata tetangga yang voting", "Maksimum tetangga yang voting",
"Persentase observasi testing dengan tetangga > k (%)",
"Kesalahan minimum (20 seed)", "Kesalahan maksimum (20 seed)"),
caption = "Tabel 5. Banyaknya tetangga yang ikut *voting* dan kesalahan klasifikasi dari 20 *seed*")| k | Rata-rata tetangga yang voting | Maksimum tetangga yang voting | Persentase observasi testing dengan tetangga > k (%) | Kesalahan minimum (20 seed) | Kesalahan maksimum (20 seed) |
|---|---|---|---|---|---|
| 3 | 7,44 | 26 | 63,43 | 5 | 6 |
| 5 | 10,90 | 54 | 58,96 | 5 | 6 |
| 7 | 13,31 | 54 | 70,15 | 5 | 5 |
| 9 | 15,97 | 54 | 67,91 | 5 | 5 |
| 11 | 17,74 | 54 | 64,18 | 5 | 5 |
Berdasarkan Tabel 5, pada lebih dari separuh observasi testing banyaknya tetangga yang ikut voting lebih besar daripada \(k\). Pada \(k\) = 3, rata-rata tetangga yang ikut voting adalah 7,44 dan paling banyak 26 observasi, sedangkan pada \(k\) = 5 sampai 11 dapat mencapai 54 observasi. Hal ini terjadi karena banyak observasi training memiliki skor yang sama, sehingga jaraknya terhadap observasi testing juga sama dan seluruhnya ikut voting sesuai argumen use.all = TRUE. Akibatnya, perbedaan \(k\) yang dicoba tidak sebesar angkanya, karena pada banyak observasi \(k\) = 3 dan \(k\) = 11 sebenarnya menggunakan tetangga yang hampir sama.
Kolom kesalahan pada Tabel 5 menunjukkan bahwa pada \(k\) = 3 dan \(k\) = 5, banyaknya observasi yang salah diklasifikasikan berubah antara 5 dan 6 ketika seed diganti. Artinya, terdapat observasi dengan hasil voting seri yang kelasnya ditentukan secara acak. Pada \(k\) = 7, 9, dan 11, banyaknya kesalahan tetap 5 untuk seluruh seed. Hal ini perlu diingat ketika membandingkan hasil antarnilai \(k\) pada subbab berikutnya.
Sebelum menghitung ukuran evaluasi seluruh model, terlebih dahulu dibandingkan output confusionMatrix() tanpa dan dengan argumen positive pada model \(k\) = 7.
confusionMatrix(pred[["k=7"]], test$Class)$byClass[c("Sensitivity", "Specificity", "Pos Pred Value")]## Sensitivity Specificity Pos Pred Value
## 0.9770115 0.9361702 0.9659091
## Confusion Matrix and Statistics
##
## Reference
## Prediction benign malignant
## benign 85 3
## malignant 2 44
##
## Accuracy : 0.9627
## 95% CI : (0.9151, 0.9878)
## No Information Rate : 0.6493
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9177
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9362
## Specificity : 0.9770
## Pos Pred Value : 0.9565
## Neg Pred Value : 0.9659
## Prevalence : 0.3507
## Detection Rate : 0.3284
## Detection Prevalence : 0.3433
## Balanced Accuracy : 0.9566
##
## 'Positive' Class : malignant
##
Tanpa argumen positive, sensitivity bernilai 0,9770, specificity 0,9362, dan PPV 0,9659. Nilai tersebut dihitung dengan benign sebagai kelas positif, sehingga sensitivity yang ditampilkan sebenarnya adalah kemampuan model mengenali tumor jinak. Dengan positive = “malignant”, sensitivity menjadi 0,9362, specificity 0,9770, dan PPV 0,9565, sesuai Persamaan (2.14) sampai (2.16). Jika argumen ini tidak diperhatikan, kemampuan model dalam mendeteksi tumor ganas akan terlihat lebih tinggi daripada yang sebenarnya.
Output confusionMatrix() untuk \(k\) = 7 menunjukkan bahwa dari 47 tumor ganas, 44 terdeteksi sebagai ganas (TP) dan 3 diprediksi jinak (FN). Dari 87 tumor jinak, 85 diprediksi jinak (TN) dan 2 diprediksi ganas (FP). Accuracy sebesar 96,27% dengan selang kepercayaan 95% antara 91,51% dan 98,78%. Nilai ini jauh lebih besar daripada No Information Rate (64,93%), yaitu accuracy yang diperoleh jika seluruh observasi diprediksi sebagai kelas terbanyak (benign), dengan p-value kurang dari \(2 \times 10^{-16}\). Nilai Kappa sebesar 0,9177 menunjukkan kesesuaian prediksi dan kelas aktual yang sangat tinggi setelah memperhitungkan kesesuaian karena kebetulan. PPV (Pos Pred Value) sebesar 95,65% berarti dari 46 tumor yang diprediksi ganas, 44 memang ganas. Ukuran evaluasi kelima nilai \(k\) disajikan pada Tabel 6.
eval.knn <- data.frame(k = k, t(sapply(cm, function(m) c(
TP = m$table["malignant", "malignant"], FN = m$table["benign", "malignant"],
FP = m$table["malignant", "benign"], TN = m$table["benign", "benign"],
Accuracy = 100 * unname(m$overall["Accuracy"]),
Sensitivity = 100 * unname(m$byClass["Sensitivity"]),
Specificity = 100 * unname(m$byClass["Specificity"]),
PPV = 100 * unname(m$byClass["Pos Pred Value"])))), row.names = NULL)
knitr::kable(eval.knn, digits = 2, align = "c", format.args = list(decimal.mark = ","),
col.names = c("k", "TP", "FN", "FP", "TN", "Accuracy (%)", "Sensitivity (%)", "Specificity (%)", "PPV (%)"),
caption = "Tabel 6. Perbandingan metrik evaluasi K-NN pada data *testing*")| k | TP | FN | FP | TN | Accuracy (%) | Sensitivity (%) | Specificity (%) | PPV (%) |
|---|---|---|---|---|---|---|---|---|
| 3 | 44 | 3 | 3 | 84 | 95,52 | 93,62 | 96,55 | 93,62 |
| 5 | 44 | 3 | 3 | 84 | 95,52 | 93,62 | 96,55 | 93,62 |
| 7 | 44 | 3 | 2 | 85 | 96,27 | 93,62 | 97,70 | 95,65 |
| 9 | 44 | 3 | 2 | 85 | 96,27 | 93,62 | 97,70 | 95,65 |
| 11 | 44 | 3 | 2 | 85 | 96,27 | 93,62 | 97,70 | 95,65 |
Berdasarkan Tabel 6, kelima nilai \(k\) menghasilkan FN yang sama, yaitu 3, sehingga sensitivity seluruh model sama sebesar 93,62%. Artinya, dari setiap 100 tumor ganas, sekitar 94 berhasil dideteksi dan sekitar 6 lolos sebagai tumor jinak. Perbedaan hanya terdapat pada FP. Model \(k\) = 3 dan \(k\) = 5 memiliki 3 FP dengan specificity 96,55%, PPV 93,62%, dan accuracy 95,52%, sedangkan model \(k\) = 7, 9, dan 11 memiliki 2 FP dengan specificity 97,70%, PPV 95,65%, dan accuracy 96,27%. Jadi, pada pembagian data ini, perubahan \(k\) dari 3 sampai 11 tidak mengubah kemampuan model dalam mendeteksi tumor ganas dan hanya mengubah satu prediksi tumor jinak.
Untuk mengetahui penyebabnya, observasi yang salah diklasifikasikan oleh setidaknya satu nilai \(k\) ditampilkan sebagai berikut. Kolom prob_k7 adalah proporsi suara untuk kelas pemenang pada \(k\) = 7.
## Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei Bl.cromatin
## 223 4 1 1 3 1 5 2
## 253 6 3 3 5 3 10 3
## 260 5 7 7 1 5 8 3
## 357 5 3 3 1 3 3 3
## 490 6 3 2 1 3 4 4
## 658 5 4 5 1 8 1 3
## Normal.nucleoli Mitoses Class
## 223 1 1 malignant
## 253 5 3 benign
## 260 4 1 benign
## 357 3 3 malignant
## 490 1 1 malignant
## 658 6 1 benign
data.frame(hasil[r, ], prob_k7 = round(attr(pred[["k=7"]], "prob")[match(r, rownames(test))], 3), check.names = FALSE)## Aktual k=3 k=5 k=7 k=9 k=11 prob_k7
## 223 malignant benign benign benign benign benign 1.000
## 253 benign malignant malignant malignant malignant malignant 1.000
## 260 benign malignant malignant malignant malignant malignant 0.750
## 357 malignant benign benign benign benign benign 0.818
## 490 malignant benign benign benign benign benign 0.875
## 658 benign malignant malignant benign benign benign 0.571
Terdapat enam observasi yang salah diklasifikasikan. Observasi 223, 357, dan 490 adalah tumor ganas yang diprediksi jinak oleh kelima nilai \(k\). Ketiganya memiliki skor Cell.size dan Cell.shape yang rendah (1 sampai 3), sehingga profil selnya lebih mirip tumor jinak. Proporsi suara untuk kelas benign pada \(k\) = 7 mencapai 1, 0,818, dan 0,875, sehingga tetangga terdekatnya hampir seluruhnya tumor jinak dan perubahan \(k\) tidak dapat memperbaikinya. Kasus seperti ini juga dilaporkan oleh Wolberg dan Mangasarian (1990), yaitu terdapat tumor ganas yang hasil sitologinya terlihat jinak dan diduga karena jaringan tumor tidak terambil saat aspirasi. Dengan kata lain, ketiga FN tersebut bukan berasal dari pemilihan \(k\), tetapi dari observasi ganas yang memang sulit dibedakan menggunakan sembilan skor sitologi ini.
Observasi 253 dan 260 adalah tumor jinak yang memiliki skor tinggi pada beberapa prediktor, misalnya Bare.nuclei sebesar 10 dan 8, sehingga diprediksi ganas oleh seluruh nilai \(k\). Satu-satunya observasi yang hasilnya berbeda antarnilai \(k\) adalah observasi 658, yaitu tumor jinak yang diprediksi ganas oleh \(k\) = 3 dan 5, tetapi diprediksi jinak oleh \(k\) = 7, 9, dan 11. Proporsi suara benign pada \(k\) = 7 hanya 0,571, yang berarti tetangganya bercampur antara kedua kelas. Berdasarkan Tabel 5, kelas observasi seperti ini pada \(k\) = 3 dan 5 dapat berubah jika seed berubah, sehingga selisih satu FP antara \(k\) = 3, 5 dan \(k\) = 7, 9, 11 belum cukup kuat untuk menyimpulkan bahwa nilai \(k\) yang lebih besar memang lebih baik.
Sesuai sintaks modul, nilai \(k\) dieksplorasi dari 1 sampai 25 untuk melihat kurva performanya. Eksplorasi pertama dilakukan pada pembagian data utama dengan set.seed(12308).
kurva <- do.call(rbind, lapply(1:25, function(j) {
set.seed(12308)
p <- knn(train.n, test.n, cl = train$Class, k = j)
m <- confusionMatrix(p, test$Class, positive = "malignant")
data.frame(k = j, Accuracy = 100 * m$overall[["Accuracy"]], Sensitivity = 100 * m$byClass[["Sensitivity"]],
Specificity = 100 * m$byClass[["Specificity"]], PPV = 100 * m$byClass[["Pos Pred Value"]],
FN = m$table["benign", "malignant"], FP = m$table["malignant", "benign"])
}))
print(kurva, digits = 4, row.names = FALSE)## k Accuracy Sensitivity Specificity PPV FN FP
## 1 96.27 93.62 97.70 95.65 3 2
## 2 96.27 95.74 96.55 93.75 2 3
## 3 95.52 93.62 96.55 93.62 3 3
## 4 96.27 95.74 96.55 93.75 2 3
## 5 95.52 93.62 96.55 93.62 3 3
## 6 95.52 93.62 96.55 93.62 3 3
## 7 96.27 93.62 97.70 95.65 3 2
## 8 96.27 93.62 97.70 95.65 3 2
## 9 96.27 93.62 97.70 95.65 3 2
## 10 96.27 93.62 97.70 95.65 3 2
## 11 96.27 93.62 97.70 95.65 3 2
## 12 96.27 93.62 97.70 95.65 3 2
## 13 96.27 93.62 97.70 95.65 3 2
## 14 96.27 93.62 97.70 95.65 3 2
## 15 96.27 93.62 97.70 95.65 3 2
## 16 96.27 93.62 97.70 95.65 3 2
## 17 96.27 93.62 97.70 95.65 3 2
## 18 96.27 93.62 97.70 95.65 3 2
## 19 96.27 93.62 97.70 95.65 3 2
## 20 96.27 93.62 97.70 95.65 3 2
## 21 96.27 93.62 97.70 95.65 3 2
## 22 96.27 93.62 97.70 95.65 3 2
## 23 96.27 93.62 97.70 95.65 3 2
## 24 96.27 93.62 97.70 95.65 3 2
## 25 96.27 93.62 97.70 95.65 3 2
Pada pembagian data utama, model \(k\) = 1 memiliki 3 FN dan 2 FP. Model \(k\) = 2 dan 4 memiliki FN paling sedikit, yaitu 2, tetapi FP-nya 3. Nilai \(k\) genap pada dua kelas sangat mungkin menghasilkan suara seri yang kelasnya ditentukan secara acak, sehingga hasil ini tidak dapat dijadikan dasar pemilihan \(k\). Mulai \(k\) = 7 sampai \(k\) = 25, hasilnya tidak berubah sama sekali, yaitu 3 FN dan 2 FP. Jadi, pada satu pembagian data dengan 134 observasi testing, kurva performa hampir datar dan tidak memperlihatkan pola yang jelas. Hal ini sejalan dengan Tabel 5 dan hasil subbab 4.7, yaitu banyak observasi memiliki tetangga yang sama untuk berbagai nilai \(k\), sedangkan observasi yang salah diklasifikasikan memiliki lingkungan tetangga yang hampir seluruhnya dari kelas lain.
Karena satu pembagian data belum cukup untuk melihat pola, eksplorasi diulang pada 100 pembagian data 80% dan 20% dengan seed 1 sampai 100. Pada setiap pembagian, penskalaan min-max dihitung ulang dari data training pembagian tersebut.
ulang <- do.call(rbind, lapply(1:100, function(s) {
set.seed(s)
i <- createDataPartition(bc$Class, p = 0.8, list = FALSE)
a <- bc[i, ]; b <- bc[-i, ]
mn.s <- sapply(a[, 1:9], min); mx.s <- sapply(a[, 1:9], max)
a.n <- minmax(a[, 1:9], mn.s, mx.s); b.n <- minmax(b[, 1:9], mn.s, mx.s)
do.call(rbind, lapply(1:25, function(j) {
set.seed(s)
p <- knn(a.n, b.n, cl = a$Class, k = j)
TP <- sum(p == "malignant" & b$Class == "malignant"); FP <- sum(p == "malignant" & b$Class == "benign")
data.frame(seed = s, k = j, Accuracy = 100 * mean(p == b$Class),
Sensitivity = 100 * mean(p[b$Class == "malignant"] == "malignant"),
Specificity = 100 * mean(p[b$Class == "benign"] == "benign"),
PPV = 100 * TP / (TP + FP), FN = sum(p == "benign" & b$Class == "malignant"))
}))
}))
rata <- aggregate(cbind(Accuracy, Sensitivity, Specificity, PPV, FN) ~ k, data = ulang, FUN = mean)
print(rata, digits = 4, row.names = FALSE)## k Accuracy Sensitivity Specificity PPV FN
## 1 95.95 93.23 97.41 95.21 3.18
## 2 96.10 93.81 97.34 95.11 2.91
## 3 97.07 95.94 97.69 95.82 1.91
## 4 97.02 95.85 97.66 95.74 1.95
## 5 97.20 96.04 97.83 96.05 1.86
## 6 97.06 95.62 97.84 96.05 2.06
## 7 97.08 95.81 97.77 95.94 1.97
## 8 96.91 95.45 97.70 95.80 2.14
## 9 96.92 95.34 97.77 95.93 2.19
## 10 96.92 95.38 97.75 95.89 2.17
## 11 96.91 95.28 97.79 95.95 2.22
## 12 96.84 95.15 97.75 95.86 2.28
## 13 96.80 95.04 97.75 95.86 2.33
## 14 96.82 95.02 97.79 95.94 2.34
## 15 96.83 94.98 97.83 96.00 2.36
## 16 96.78 94.77 97.87 96.07 2.46
## 17 96.77 94.64 97.92 96.15 2.52
## 18 96.74 94.43 97.99 96.26 2.62
## 19 96.66 94.17 98.00 96.27 2.74
## 20 96.61 93.94 98.06 96.37 2.85
## 21 96.53 93.70 98.06 96.36 2.96
## 22 96.51 93.62 98.08 96.40 3.00
## 23 96.46 93.45 98.08 96.39 3.08
## 24 96.43 93.36 98.09 96.41 3.12
## 25 96.42 93.30 98.10 96.43 3.15
ukuran <- c("Accuracy", "Sensitivity", "Specificity", "PPV")
kurva.df <- rbind(
data.frame(Data = "set.seed(12308)", k = rep(kurva$k, 4), Ukuran = rep(ukuran, each = 25), Nilai = unlist(kurva[, ukuran])),
data.frame(Data = "Rata-rata 100 pembagian data", k = rep(rata$k, 4), Ukuran = rep(ukuran, each = 25), Nilai = unlist(rata[, ukuran])))
kurva.df$Data <- factor(kurva.df$Data, levels = c("set.seed(12308)", "Rata-rata 100 pembagian data"))
ggplot(kurva.df, aes(k, Nilai, colour = Ukuran)) +
annotate("rect", xmin = 3, xmax = 11, ymin = -Inf, ymax = Inf, fill = "grey92") +
geom_line(linewidth = 0.8) +
geom_point(size = 1.6) +
facet_wrap(~ Data) +
scale_x_continuous(breaks = seq(1, 25, by = 2)) +
scale_colour_manual(values = c(Accuracy = "#2b5c8f", Sensitivity = "#d95f02", Specificity = "#7B5EA7", PPV = "#008080")) +
labs(x = "Jumlah tetangga terdekat (k)", y = "Persentase (%)", colour = "") +
theme_minimal() +
theme(legend.position = "bottom", panel.grid.minor = element_blank())Gambar 4. Kurva performa K-NN untuk \(k\) = 1 sampai 25 pada set.seed(12308) dan rata-rata 100 pembagian data
Pada Gambar 4, daerah abu-abu menunjukkan rentang \(k\) = 3 sampai 11 yang menjadi batasan masalah. Panel kiri memperlihatkan kurva yang hampir datar pada pembagian data utama, sedangkan panel kanan memperlihatkan pola yang lebih jelas setelah hasil 100 pembagian data dirata-ratakan. Rata-rata sensitivity naik dari 93,23% pada \(k\) = 1 menjadi sekitar 96% pada \(k\) = 3 sampai 5, kemudian turun terus sampai 93,30% pada \(k\) = 25. Pola ini sama dengan rata-rata FN yang turun dari 3,18 pada \(k\) = 1 menjadi 1,86 pada \(k\) = 5, lalu naik kembali sampai 3,15 pada \(k\) = 25. Sebaliknya, rata-rata specificity dan PPV justru naik perlahan ketika \(k\) bertambah, yaitu dari 97,41% dan 95,21% pada \(k\) = 1 menjadi 98,10% dan 96,43% pada \(k\) = 25. Rata-rata accuracy tertinggi terdapat pada \(k\) = 5 (97,20%).
Pola tersebut sesuai dengan kompromi bias dan varians pada subbab 2.2. Pada \(k\) = 1 dan 2, keputusan hanya bergantung pada satu atau dua tetangga sehingga mudah terpengaruh noise dan kesalahannya lebih banyak. Pada \(k\) yang besar, tetangga yang lebih jauh ikut menentukan kelas, sehingga keputusan semakin terdorong ke kelas mayoritas, yaitu benign. Akibatnya, tumor jinak semakin jarang salah diklasifikasikan, tetapi semakin banyak tumor ganas yang lolos sebagai jinak. Untuk tujuan diagnosis, kondisi ini tidak diinginkan karena FN lebih berbahaya daripada FP. Ringkasan untuk kelima nilai \(k\) pada batasan masalah disajikan pada Tabel 7.
ulang5 <- ulang[ulang$k %in% k, ]
ringkas <- aggregate(cbind(Accuracy, Sensitivity, Specificity, PPV, FN) ~ k, data = ulang5, FUN = mean)
fn <- xtabs(FN ~ seed + k, data = ulang5)
ringkas$FN_min <- colSums(fn == apply(fn, 1, min))
knitr::kable(ringkas, digits = 2, align = "c", format.args = list(decimal.mark = ","),
col.names = c("k", "Rata-rata accuracy (%)", "Rata-rata sensitivity (%)", "Rata-rata specificity (%)",
"Rata-rata PPV (%)", "Rata-rata FN", "Banyak pembagian dengan FN terkecil"),
caption = "Tabel 7. Ringkasan hasil K-NN dari 100 pembagian data untuk $k$ = 3, 5, 7, 9, dan 11")| k | Rata-rata accuracy (%) | Rata-rata sensitivity (%) | Rata-rata specificity (%) | Rata-rata PPV (%) | Rata-rata FN | Banyak pembagian dengan FN terkecil |
|---|---|---|---|---|---|---|
| 3 | 97,07 | 95,94 | 97,69 | 95,82 | 1,91 | 59 |
| 5 | 97,20 | 96,04 | 97,83 | 96,05 | 1,86 | 60 |
| 7 | 97,08 | 95,81 | 97,77 | 95,94 | 1,97 | 55 |
| 9 | 96,92 | 95,34 | 97,77 | 95,93 | 2,19 | 43 |
| 11 | 96,91 | 95,28 | 97,79 | 95,95 | 2,22 | 42 |
# perbandingan FN setiap k terhadap k = 7 pada pembagian data yang sama
sapply(c("3", "5", "9", "11"), function(j) c(Lebih_kecil = sum(fn[, j] < fn[, "7"]),
Sama = sum(fn[, j] == fn[, "7"]),
Lebih_besar = sum(fn[, j] > fn[, "7"])))## 3 5 9 11
## Lebih_kecil 27 28 7 13
## Sama 47 52 67 53
## Lebih_besar 26 20 26 34
Berdasarkan Tabel 7, rata-rata FN pada \(k\) = 3, 5, dan 7 hampir sama, yaitu 1,91; 1,86; dan 1,97, sedangkan pada \(k\) = 9 dan 11 lebih besar, yaitu 2,19 dan 2,22. Banyaknya pembagian data dengan FN terkecil juga lebih banyak pada \(k\) = 3, 5, dan 7 (59, 60, dan 55 kali) dibandingkan \(k\) = 9 dan 11 (43 dan 42 kali). Rata-rata specificity dan PPV kelima nilai \(k\) hampir sama, yaitu 97,69% sampai 97,83% dan 95,82% sampai 96,05%.
Perbandingan pada pembagian data yang sama menunjukkan bahwa FN \(k\) = 3 dan \(k\) = 7 hampir seimbang, yaitu \(k\) = 3 lebih kecil pada 27 pembagian dan lebih besar pada 26 pembagian. FN \(k\) = 5 lebih kecil daripada \(k\) = 7 pada 28 pembagian dan lebih besar pada 20 pembagian. Selisih pada \(k\) = 3 dan 5 tersebut kecil, dan sekitar separuh pembagian menghasilkan FN yang sama dengan \(k\) = 7. Sebaliknya, FN \(k\) = 9 lebih besar daripada \(k\) = 7 pada 26 pembagian dan hanya lebih kecil pada 7 pembagian, sedangkan FN \(k\) = 11 lebih besar pada 34 pembagian dan hanya lebih kecil pada 13 pembagian. Artinya, di antara kelima nilai \(k\) pada batasan masalah, kenaikan FN mulai terlihat jelas setelah \(k\) = 7.
Penentuan nilai \(k\) optimal didasarkan pada kriteria utama meminimalkan FN, kemudian kriteria specificity, PPV, dan accuracy digunakan apabila FN sama. Pada pembagian data dengan set.seed(12308), kelima nilai \(k\) memiliki FN yang sama (3), sehingga kriteria FN belum dapat membedakan model. Berdasarkan specificity, PPV, dan accuracy, model terbaik adalah \(k\) = 7, 9, dan 11. Hasil 100 pembagian data kemudian menunjukkan bahwa FN paling rendah berada pada \(k\) = 3 sampai 7, sedangkan pada \(k\) = 9 dan 11 FN cenderung bertambah.
Dengan demikian, nilai \(k\) paling optimal adalah \(k\) = 7, dengan accuracy 96,27%, sensitivity 93,62%, specificity 97,70%, PPV 95,65%, 3 FN, dan 2 FP pada data testing. Nilai \(k\) = 7 merupakan satu-satunya nilai yang termasuk kelompok terbaik pada pembagian data utama sekaligus kelompok dengan FN rendah pada 100 pembagian data. Selain itu, berdasarkan Tabel 5, hasil \(k\) = 7 pada data testing tidak berubah ketika seed diganti, sedangkan hasil \(k\) = 3 dan 5 masih bergantung pada pemilihan acak saat voting seri. Namun, perlu dicatat bahwa perbedaan \(k\) = 7 dengan \(k\) = 3 dan 5 sangat kecil. Pada data testing selisihnya hanya satu FP, dan pada 100 pembagian data selisih rata-rata FN-nya hanya sekitar 0,1 observasi per pembagian. Jadi, \(k\) = 3, 5, dan 7 sama-sama layak digunakan, sedangkan \(k\) = 9 dan 11 kurang disarankan karena cenderung meningkatkan FN.
Berdasarkan hasil praktikum, diperoleh kesimpulan sebagai berikut.
Pada praktikum Machine Learning berikutnya, disarankan agar praktikan lebih teliti dalam penggunaan sintaks dan interpretasi output di RStudio, terutama dalam menentukan kelas positif pada confusionMatrix() dan mengubah faktor menjadi numerik menggunakan as.numeric(as.character()). Parameter penskalaan sebaiknya dihitung dari data training saja, dan metode penskalaan dipilih dengan memperhatikan skala serta makna setiap variabel. Nilai \(k\) sebaiknya dipilih menggunakan validasi silang pada data training, bukan berdasarkan data testing, agar data testing hanya digunakan untuk evaluasi akhir. Karena FN lebih berbahaya daripada FP, batas keputusan juga dapat diturunkan, misalnya observasi diklasifikasikan ganas walaupun proporsi suara malignant kurang dari 0,5, dengan tetap memperhatikan kenaikan FP. Selain itu, dapat dicoba metrik jarak lain seperti Manhattan atau Mahalanobis untuk mengatasi korelasi antarprediktor, serta set.seed() perlu selalu diberikan sebelum knn() karena hasil voting seri ditentukan secara acak.
Altman, D. G., & Bland, J. M. (1994a). Diagnostic tests 1: Sensitivity and specificity. BMJ, 308(6943), 1552. https://doi.org/10.1136/bmj.308.6943.1552
Altman, D. G., & Bland, J. M. (1994b). Diagnostic tests 2: Predictive values. BMJ, 309(6947), 102. https://doi.org/10.1136/bmj.309.6947.102
Bray, F., Laversanne, M., Sung, H., Ferlay, J., Siegel, R. L., Soerjomataram, I., & Jemal, A. (2024). Global cancer statistics 2022: GLOBOCAN estimates of incidence and mortality worldwide for 36 cancers in 185 countries. CA: A Cancer Journal for Clinicians, 74(3), 229–263. https://doi.org/10.3322/caac.21834
Cover, T. M., & Hart, P. E. (1967). Nearest neighbor pattern classification. IEEE Transactions on Information Theory, 13(1), 21–27. https://doi.org/10.1109/TIT.1967.1053964
Cunningham, P., & Delany, S. J. (2021). k-Nearest neighbour classifiers: A tutorial. ACM Computing Surveys, 54(6), 1–25. https://doi.org/10.1145/3459665
Fawcett, T. (2006). An introduction to ROC analysis. Pattern Recognition Letters, 27(8), 861–874. https://doi.org/10.1016/j.patrec.2005.10.010
Fix, E., & Hodges, J. L. (1951). Discriminatory analysis. Nonparametric discrimination: Consistency properties (Report No. 4, Project No. 21-49-004). USAF School of Aviation Medicine.
Han, J., Kamber, M., & Pei, J. (2012). Data mining: Concepts and techniques (3rd ed.). Morgan Kaufmann.
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data mining, inference, and prediction (2nd ed.). Springer.
James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An introduction to statistical learning: With applications in R (2nd ed.). Springer.
Kuhn, M. (2008). Building predictive models in R using the caret package. Journal of Statistical Software, 28(5), 1–26. https://doi.org/10.18637/jss.v028.i05
Kuhn, M., & Johnson, K. (2013). Applied predictive modeling. Springer.
Laboratorium Matematika FMIPA Universitas Bengkulu. (2026). Modul praktikum machine learning: K-NN (K-nearest neighbor). Universitas Bengkulu.
Leisch, F., & Dimitriadou, E. (2021). mlbench: Machine learning benchmark problems (R package version 2.1-3.1). https://CRAN.R-project.org/package=mlbench
Muraina, I. O. (2022). Ideal dataset splitting ratios in machine learning algorithms: General concerns for data scientists and data analysts. Dalam Proceedings of the 7th International Mardin Artuklu Scientific Research Conference (hlm. 496–504).
Venables, W. N., & Ripley, B. D. (2002). Modern applied statistics with S (4th ed.). Springer.
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis (2nd ed.). Springer.
Wolberg, W. H., & Mangasarian, O. L. (1990). Multisurface method of pattern separation for medical diagnosis applied to breast cytology. Proceedings of the National Academy of Sciences, 87(23), 9193–9196. https://doi.org/10.1073/pnas.87.23.9193