Beberapa penyanyi/musisi biasanya mempunyai ciri khas
tersendiri dalam membuat dan/atau membawakan sebuah lagu. Dan ada
kalanya, beberapa lagu tidak memiliki informasi yang lengkap - salah
satunya mungkin tidak adanya informasi mengenai siapa
penyanyi/pemilik/pembawa lagu tersebut. Hal ini tentu dapat menghambat
pihak music streaming platform - atau mungkin para pendengarnya
- dalam membuat playlist lagu. Namun, tenang saja, kurangnya
informasi ini tentu dapat diatasi dengan menggunakan sistem
prediksi.
Di dalam project ini, saya akan menggunakan data yang berisikan karakteristik dari beberapa lagu, dengan tujuan untuk memprediksi siapakah penyanyi/pemilik lagu-lagu tersebut - dan dengan harapan: metode machine learning bisa mempelajari style seorang penyanyi/musisi secara historis.
Metode machine learning yang digunakan adalah regresi logistik biner dan k-nearest neighbor, dimana model akan dibangun berdasarkan kedua metode tersebut, lalu dibuat beberapa prediksi, dan kemudian performa dari kedua metode akan dibandingkan sehingga didapatkan model terbaik.
Dataset dalam project ini didapatkan dari kaggle,
dimana dataset berisikan fitur audio dari 328 lagu di Spotify.
Tujuan dari penggunaan dataset ini adalah untuk memprediksi lagu-lagu yang sesuai dengan karakteristik/style Steven Wilson.
Dataset terdiri atas fitur-fitur audio dari 123 lagu Steven Wilson dan 205 lagu musisi lainnya.
Variabel-variabel yang ada dalam dataset meliputi:
acousticness: ukuran yang menggambarkan seberapa
akustik lagu tersebut; dimana nilai 1 menggambarkan bahwa lagunya
merupakan lagu akustik [dalam interval 0-1]album: nama album dari laguanalysis_url: URL yang digunakan untuk mendapatkan
fitur audiodanceability: ukuran yang menggambarkan betapa cocoknya
lagu tersebut digunakan untuk menari; dimana nilai 0 mewakili lagu yang
paling tidak cocok dipakai untuk menari dan 1 mewakili lagu yang paling
cocok dipakai untuk menari [dalam interval 0-1]duration_ms: durasi lagu dalam ms (miliseconds)energy: ukuran yang mewakili intensitas atau energi
yang dihasilkan suatu lagu; dimana nilai 1 mewakili lagu yang energik
dengan ciri-ciri lagunya cepat, keras, dan berisik [dalam
interval 0-1]id: ID Spotify dari laguinstrumentalness: ukuran yang menggambarkan seberapa
lama musik instrumen (dimana vokal sedang tidak ada) bermain dalam lagu;
dimana nilai di atas 0,5 dimaksudkan untuk mewakili lagu instrumental
[dalam interval 0-1]key: kunci dari lagu [0 = C, 1 = C♯/D, 2 = D, dan
seterusnya]liveness: ukuran kepercayaan untuk mendeteksi kehadiran
penonton dalam rekaman; dimana 1 mewakili kepercayaan yang tinggi bahwa
lagu tersebut dibawakan secara live [dalam interval
0-1]loudness: ukuran yang menggambarkan seberapa nyaring
lagu; dimana nilai 1 mewakili lagu yang tidak nyaring dan 0 mewakili
lagu yang nyaring [dalam interval 0-1]mode: modalitas dalam lagu [1 = major, 0 = minor]name: nama laguspeechiness: ukuran yang menggambarkan seberapa banyak
keberadaan spoken words dalam lagu; semakin eksklusif rekaman,
seperti pada pidato/acara talkshow, nilai atributnya semakin
mendekati 1 [dalam interval 0-1]tempo: ukuran yang menggambarkan keseluruhan tempo pada
lagu; dimana nilai 1 mewakili lagu dengan tempo cepat [dalam
interval 0-1]time_signature: time signature dari suatu lagu
- atau yang menentukan berapa banyak ketukan di setiap bar; misalnya:
3/4, 4/4, 5/4, dan seterusnyatrack_href: tautan API Spotify dari lagutype: tipe datauri: URI Spotify dari laguvalence: ukuran yang menggambarkan kepositifan lagu;
lagu dengan valence tinggi bersifat lebih positif (bersifat
bahagia, ceria, euforia), sedangkan lagu dengan valence rendah
bersifat lebih negatif (bersifat sedih, tertekan, marah) [dalam
interval 0-1]class: penyanyi lagu [0 = Penyanyi lain, 1 = Steven
Wilson]Pertama-tama, load terlebih dahulu library yang dibutuhkan.
# load library
library(stringr) # untuk replace karakter
library(dplyr) # untuk transformasi data
library(GGally) # untuk EDA
library(rsample) # untuk train-test splitting
library(caret) # untuk membuat confusion matrix
library(car) # untuk cek multikolinieritas
library(class) # untuk knn
library(DT) # untuk membuat datatableBaca dataset song.csv yang akan digunakan.
# read data
song <- read.csv("data_input/song.csv", header=T, na.strings=c(""))Dari tabel di atas, dapat diidentifikasi bahwa variabel targetnya
adalah class.. dan variabel prediktornya (sementara ini)
adalah variabel sisanya.
Namun sebelum lanjut ke pemodelan, kita harus memeriksa terlebih dahulu kesesuaian struktur data yang akan dipakai dan melakukan cleansing (jika perlu).
Lihat struktur data song untuk mengecek kesesuaian tipe
data.
# lihat struktur data
str(song)## 'data.frame': 338 obs. of 21 variables:
## $ acousticness : chr "0.732" "0.0277" "0.75" "0.319" ...
## $ album : chr "Grace for Drowning" "Grace for Drowning" "Grace for Drowning" "Grace for Drowning" ...
## $ analysis_url : chr "https://api.spotify.com/v1/audio-analysis/1mAfaiS8yke7hG73sp3keZ" "https://api.spotify.com/v1/audio-analysis/6eM6LP9zOo93sDaQpOi7Iv" "https://api.spotify.com/v1/audio-analysis/60GnLUvlb4fUHThzG8rZDT" "https://api.spotify.com/v1/audio-analysis/1MCamKVAN8QA68QYqpdCCy" ...
## $ danceability : num 0.196 0.456 0.464 0.418 0.362 0.126 0.416 0.447 0.297 0.315 ...
## $ duration_ms : num 0.327 0.24 0.187 0.394 0.125 ...
## $ energy : num 0.171 0.427 0.249 0.443 0.0911 0.0539 0.447 0.349 0.675 0.294 ...
## $ id : chr "1mAfaiS8yke7hG73sp3keZ" "6eM6LP9zOo93sDaQpOi7Iv" "60GnLUvlb4fUHThzG8rZDT" "1MCamKVAN8QA68QYqpdCCy" ...
## $ instrumentalness: num 0.68 0.412 0.408 0.876 0.817 0.509 0.604 0.401 0.00167 0.045 ...
## $ key : int 11 0 0 9 7 9 4 2 7 7 ...
## $ liveness : num 0.0753 0.0983 0.0969 0.0588 0.121 0.0901 0.083 0.102 0.22 0.0776 ...
## $ loudness : num 0.68 0.58 0.574 0.553 0.86 ...
## $ mode : int 0 0 1 0 1 1 0 1 1 0 ...
## $ name : chr "Deform to Form a Star" "No Part of Me" "Postcard" "Remainder the Black Dog" ...
## $ speechiness : num 0.0376 0.0328 0.0294 0.0264 0.0373 0.0459 0.0309 0.0314 0.0471 0.0334 ...
## $ tempo : num 0.393 0.875 0.713 0.525 0.991 ...
## $ time_signature : int 4 4 4 4 4 4 4 4 4 4 ...
## $ track_href : chr "https://api.spotify.com/v1/tracks/1mAfaiS8yke7hG73sp3keZ" "https://api.spotify.com/v1/tracks/6eM6LP9zOo93sDaQpOi7Iv" "https://api.spotify.com/v1/tracks/60GnLUvlb4fUHThzG8rZDT" "https://api.spotify.com/v1/tracks/1MCamKVAN8QA68QYqpdCCy" ...
## $ type : chr "audio_features" "audio_features" "audio_features" "audio_features" ...
## $ uri : chr "spotify:track:1mAfaiS8yke7hG73sp3keZ" "spotify:track:6eM6LP9zOo93sDaQpOi7Iv" "spotify:track:60GnLUvlb4fUHThzG8rZDT" "spotify:track:1MCamKVAN8QA68QYqpdCCy" ...
## $ valence : num 0.049 0.17 0.0914 0.28 0.0353 0.038 0.288 0.0756 0.04 0.0963 ...
## $ class.. : chr "1;;" "1;;" "1;;" "1;;" ...
Berdasarkan struktur data di atas, dapat dilihat bahwa ada beberapa hal yang perlu diperbaiki (di-cleansing). Workflow dari data cleansing meliputi:
class..
agar nantinya variabel tersebut dapat diubah tipe datanya menjadi faktor
(dalam hal ini, hasil perbaikannya saya simpan pada kolom baru, yaitu
class).class..
dan type. Variabel type saya hapus karena
berisikan nilai yang sama untuk semua atributnya.class menjadi integer,
agar nantinya tidak timbul masalah saat tipe datanya diubah menjadi
faktor.acousticness,
danceability, duration_ms,
energy, instrumentalness,
liveness, loudness, speechiness,
tempo, valence menjadi numerik.key,
mode, time_signature, class
menjadi faktor.key,
mode, time_signature agar mudah untuk
diinterpretasi.album,
analysis_url, id, name,
track_href, uri) karena variabel dengan banyak
nilai unik tidak dapat digunakan untuk pemodelan.# menghapus karakter yang tidak diperlukan
song$class <- str_replace_all(song$class.., ";;", "")
song$class <- str_replace_all(song$class.., ";", "")# menghapus kolom yang tidak diperlukan
# mengubah tipe data class
song <- song %>%
select(-c(class.., type)) %>%
mutate(class = as.integer(class))# mengecek missing value
colSums(is.na(song)) ## acousticness album analysis_url danceability
## 0 10 10 10
## duration_ms energy id instrumentalness
## 10 10 10 10
## key liveness loudness mode
## 10 10 10 10
## name speechiness tempo time_signature
## 10 10 10 10
## track_href uri valence class
## 10 10 10 10
Ternyata ada missing value.
# menghapus baris yang mengandung missing value
song <- song[rowSums(is.na(song)) == 0,]# mengecek kembali missing value
colSums(is.na(song))## acousticness album analysis_url danceability
## 0 0 0 0
## duration_ms energy id instrumentalness
## 0 0 0 0
## key liveness loudness mode
## 0 0 0 0
## name speechiness tempo time_signature
## 0 0 0 0
## track_href uri valence class
## 0 0 0 0
Sudah tidak ada missing value.
# mengecek duplikasi
nrow(song[duplicated(song$id),])## [1] 0
Tidak ada data duplicate.
# mengubah tipe data
song <- song %>%
mutate_at(vars(acousticness, danceability, duration_ms, energy, instrumentalness, liveness, loudness, speechiness, tempo, valence), as.numeric) %>%
mutate_at(vars(key, mode, time_signature, class), as.factor)# mengintip tipe dan struktur data
glimpse(song)## Rows: 328
## Columns: 20
## $ acousticness <dbl> 0.732000, 0.027700, 0.750000, 0.319000, 0.893000, 0.7~
## $ album <chr> "Grace for Drowning", "Grace for Drowning", "Grace fo~
## $ analysis_url <chr> "https://api.spotify.com/v1/audio-analysis/1mAfaiS8yk~
## $ danceability <dbl> 0.196, 0.456, 0.464, 0.418, 0.362, 0.126, 0.416, 0.44~
## $ duration_ms <dbl> 0.3274706, 0.2395618, 0.1868133, 0.3942850, 0.1247743~
## $ energy <dbl> 0.1710, 0.4270, 0.2490, 0.4430, 0.0911, 0.0539, 0.447~
## $ id <chr> "1mAfaiS8yke7hG73sp3keZ", "6eM6LP9zOo93sDaQpOi7Iv", "~
## $ instrumentalness <dbl> 0.680000, 0.412000, 0.408000, 0.876000, 0.817000, 0.5~
## $ key <fct> 11, 0, 0, 9, 7, 9, 4, 2, 7, 7, 5, 11, 2, 0, 2, 1, 4, ~
## $ liveness <dbl> 0.0753, 0.0983, 0.0969, 0.0588, 0.1210, 0.0901, 0.083~
## $ loudness <dbl> 0.6800589, 0.5798625, 0.5740529, 0.5529417, 0.8598724~
## $ mode <fct> 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1,~
## $ name <chr> "Deform to Form a Star", "No Part of Me", "Postcard",~
## $ speechiness <dbl> 0.0376, 0.0328, 0.0294, 0.0264, 0.0373, 0.0459, 0.030~
## $ tempo <dbl> 0.3929459, 0.8754701, 0.7131355, 0.5250627, 0.9910081~
## $ time_signature <fct> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 3, 4, 5,~
## $ track_href <chr> "https://api.spotify.com/v1/tracks/1mAfaiS8yke7hG73sp~
## $ uri <chr> "spotify:track:1mAfaiS8yke7hG73sp3keZ", "spotify:trac~
## $ valence <dbl> 0.0490, 0.1700, 0.0914, 0.2800, 0.0353, 0.0380, 0.288~
## $ class <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
# mengubah nama level variabel key
levels(song$key) <- list('C' = "0",
'C#/Db' = "1",
'D' = "2",
'D#/Eb' = "3",
'E' = "4",
'F' = "5",
'F#/Gb' = "6",
'G' = "7",
'G#/Ab' = "8",
'A' = "9",
'A#/Bb' = "10",
'B' = "11")# mengubah nama level variabel mode
levels(song$mode) <- list("minor" = "0", "major" = "1")# mengubah nama level variabel time_signature
levels(song$time_signature) <- list('3/4' = "1",
'4/4' = "2",
'5/4' = "3",
'6/4' = "4",
'7/4' = "5")# membuat data frame baru
# data frame berisi variabel yang bukan bertipe character
song_clean <- song %>%
select(-c(album, analysis_url, id, name, track_href, uri))# mengintip tipe dan struktur data dari dataframe baru
glimpse(song_clean)## Rows: 328
## Columns: 14
## $ acousticness <dbl> 0.732000, 0.027700, 0.750000, 0.319000, 0.893000, 0.7~
## $ danceability <dbl> 0.196, 0.456, 0.464, 0.418, 0.362, 0.126, 0.416, 0.44~
## $ duration_ms <dbl> 0.3274706, 0.2395618, 0.1868133, 0.3942850, 0.1247743~
## $ energy <dbl> 0.1710, 0.4270, 0.2490, 0.4430, 0.0911, 0.0539, 0.447~
## $ instrumentalness <dbl> 0.680000, 0.412000, 0.408000, 0.876000, 0.817000, 0.5~
## $ key <fct> B, C, C, A, G, A, E, D, G, G, F, B, D, C, D, C#/Db, E~
## $ liveness <dbl> 0.0753, 0.0983, 0.0969, 0.0588, 0.1210, 0.0901, 0.083~
## $ loudness <dbl> 0.6800589, 0.5798625, 0.5740529, 0.5529417, 0.8598724~
## $ mode <fct> minor, minor, major, minor, major, major, minor, majo~
## $ speechiness <dbl> 0.0376, 0.0328, 0.0294, 0.0264, 0.0373, 0.0459, 0.030~
## $ tempo <dbl> 0.3929459, 0.8754701, 0.7131355, 0.5250627, 0.9910081~
## $ time_signature <fct> 6/4, 6/4, 6/4, 6/4, 6/4, 6/4, 6/4, 6/4, 6/4, 6/4, 6/4~
## $ valence <dbl> 0.0490, 0.1700, 0.0914, 0.2800, 0.0353, 0.0380, 0.288~
## $ class <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
Data song_clean sudah sesuai. Lanjut ke tahap berikutnya.
Mulai titik ini, dataset yang digunakan adalah song_clean.
Sebelum menganalisis lebih jauh tentang pengaruh variabel prediktor terhadap variabel target, sebaiknya terlebih dahulu memeriksa hubungan antara variabel prediktor dan variabel target. Hal ini dilakukan sekaligus sebagai langkah untuk menyeleksi fitur (feature selection).
Variabel target pada dataset memiliki tipe data faktor, sedangkan variabel prediktornya ada yang bertipe faktor (kategorik) dan numerik.
Dengan demikian, untuk memeriksa hubungan antara variabel prediktor numerik dengan variabel target, akan digunakan uji perbedaan rata-rata dua populasi independen (dimana rata-rata populasi antara dua kelas target akan diuji perbedaannya - saling berbeda atau tidak). Uji yang diterapkan dalam project ini adalah Mann-Whitney U Test (walaupun tidak dibahas lebih jauh pada markdown ini); dimana jika hasil pengujian menghasilkan hasil yang signifikan, berarti terdapat perbedaan rata-rata antara dua kelas target, atau secara intuisi dapat dinilai bahwa ada hubungan antara variabel prediktor numerik dan variabel target.
Selain itu, akan dicek pula distribusi nilai variabel prediktor numerik untuk setiap level kelas targetnya. Hal ini juga dilakukan untuk menggambarkan secara garis besar - apakah ada perbedaan distribusi antar level kelas target.
Sedangkan untuk memeriksa hubungan antara variabel prediktor kategorik dengan variabel target, akan digunakan Chi-Square Test; dimana jika hasil pengujian menghasilkan hasil yang signifikan, berarti terdapat hubungan antara dua variabel tersebut.
Feature selection pada project ini juga memperhatikan opini dari referensi utama. Lihat dokumentasinya di sini.
# mengecek distribusi nilai dari variabel prediktor untuk setiap level class
ggduo(song_clean,
"class",
c("acousticness", "danceability", "energy"))# mengecek distribusi nilai dari variabel prediktor untuk setiap level class
ggduo(song_clean,
"class",
c("instrumentalness", "liveness", "loudness"))# mengecek distribusi nilai dari variabel prediktor untuk setiap level class
ggduo(song_clean,
"class",
c("speechiness", "tempo", "valence"))Berdasarkan uji Mann-Whitney U dan tiga gambar di atas, variabel
prediktor numerik yang ditengarai memiliki hubungan dengan variabel
target adalah: danceability, energy,
loudness, speechiness, valence.
Namun, jika dipikir secara logika, speechiness sepertinya
tidak ada hubungannya dengan target. Hal ini sejalan dengan referensi
utama (lihat
di sini); ditambah nilainya yang mengandung banyak outlier,
membuat saya memutuskan untuk menghapus variabel
speechiness dari variabel prediktor.
Sedangkan menurut referensi utama (lihat
di sini), terdapat concern bahwa: sepertinya, variabel
instrumentalness dan acousticness memiliki
hubungan dengan variabel target - dan ini masuk akal. Oleh karena itu,
saya pun memutuskan untuk memasukkan dua variabel tersebut ke dalam
variabel prediktor.
Di bawah ini akan dilakukan uji korelasi antara variabel prediktor kategorik dengan variabel target. Selanjutnya, akan diidentifikasi pula apakah ada variabel prediktor yang bersifat perfect separator.
# cek perfect separator
table(song_clean$key, song_clean$class) ##
## 0 1
## C 26 21
## C#/Db 16 4
## D 26 21
## D#/Eb 7 2
## E 17 19
## F 20 10
## F#/Gb 10 5
## G 20 14
## G#/Ab 9 2
## A 26 16
## A#/Bb 13 2
## B 15 7
Bukan perfect separator.
# mengecek hubungan variabel key dengan target
chisq.test(table(song_clean$key, song_clean$class))##
## Pearson's Chi-squared test
##
## data: table(song_clean$key, song_clean$class)
## X-squared = 15.491, df = 11, p-value = 0.1611
# cek perfect separator
table(song_clean$mode, song_clean$class)##
## 0 1
## minor 74 63
## major 131 60
Bukan perfect separator.
# mengecek hubungan variabel mode dengan target
chisq.test(table(song_clean$mode, song_clean$class))##
## Pearson's Chi-squared test with Yates' continuity correction
##
## data: table(song_clean$mode, song_clean$class)
## X-squared = 6.6192, df = 1, p-value = 0.01009
# menghapus level yang tidak ada anggotanya
song_clean$time_signature <- droplevels(song_clean$time_signature)# cek perfect separator
table(song_clean$time_signature, song_clean$class)##
## 0 1
## 3/4 4 3
## 5/4 30 16
## 6/4 165 101
## 7/4 6 3
Bukan perfect separator.
# mengecek hubungan variabel time_signature dengan target
chisq.test(table(song_clean$time_signature, song_clean$class))##
## Pearson's Chi-squared test
##
## data: table(song_clean$time_signature, song_clean$class)
## X-squared = 0.32237, df = 3, p-value = 0.9558
Dari hasil ketiga uji Chi-Square di atas, variabel prediktor
kategorik yang memiliki korelasi dengan variabel target adalah variabel
mode.
Dengan demikian, variabel yang digunakan dalam pemodelan adalah:
danceabilityenergyloudnessvalenceinstrumentalnessacousticnessmodeDisclaimer: variabel
duration_msdiputuskan untuk tidak digunakan dalam pemodelan karena terdiri dari nilai-nilai yang aneh dan tidak konsisten (ada yang desimal dan ada juga yang ratusan ribu)
# menghapus variabel yang tidak digunakan di dataframe baru
song_clean <- song_clean %>%
select(c(danceability, energy, loudness, valence, instrumentalness, acousticness, mode, class))# melihat summary dari dataframe baru
summary(song_clean)## danceability energy loudness valence
## Min. :0.0778 Min. :0.00374 Min. :0.08781 Min. :0.0299
## 1st Qu.:0.3568 1st Qu.:0.33800 1st Qu.:0.24986 1st Qu.:0.1270
## Median :0.4670 Median :0.55400 Median :0.37276 Median :0.2815
## Mean :0.4740 Mean :0.53570 Mean :0.39824 Mean :0.3421
## 3rd Qu.:0.5663 3rd Qu.:0.74375 3rd Qu.:0.50797 3rd Qu.:0.5102
## Max. :0.9270 Max. :0.96700 Max. :1.00000 Max. :0.9700
## instrumentalness acousticness mode class
## Min. :0.00000 Min. :0.0000085 minor:137 0:205
## 1st Qu.:0.00308 1st Qu.:0.0146000 major:191 1:123
## Median :0.23800 Median :0.1090000
## Mean :0.37491 Mean :0.2768855
## 3rd Qu.:0.77850 3rd Qu.:0.4645000
## Max. :0.98600 Max. :0.9940000
Menurut summary di atas, dapat disimpulkan bahwa: nilai dari variabel prediktor numerik kebanyakan berada di rentang 0 dan 1.
Setelah variabel prediktor berhasil dipilih, akan dilakukan pengecekan asumsi regresi logistik - terpenuhi atau tidak, dimana salah satu asumsi dari regresi logistik adalah tidak adanya multikolinieritas. Oleh karena itu, sebelum melakukan pemodelan, sebaiknya cek terlebih dahulu apakah ada korelasi antar variabel prediktor atau tidak.
# mengecek korelasi antar variabel prediktor numerik
ggcorr(song_clean, hjust = 1, layout.exp = 1, label = TRUE)Menurut gambar heatmap di atas, sepertinya terdapat beberapa variabel prediktor yang berhubungan kuat. Tetapi untuk sementara, hal ini diabaikan karena nanti akan dicek kembali melalui nilai VIF.
Imbalanced class adalah salah satu concern yang patut diperhatikan dalam pemodelan menggunakan machine learning. Oleh karenanya, cek apakah variabel target memiliki kelas dengan proporsi yang tidak seimbang.
# mengecek keseimbangan kelas dari variabel target
prop.table(table(song_clean$class))##
## 0 1
## 0.625 0.375
Berdasarkan proporsi di atas, dapat disimpulkan bahwa kelas masih seimbang, sehingga tidak perlu dilakukan resampling.
Note: kelas umumnya dinyatakan imbalanced jika memiliki proporsi 90/10 atau 95/5.
# membagi ke training set dan testing set
set.seed(100)
index <- initial_split(data = song_clean,
prop = 0.8,
strata = class)
song_train <- training(index)
song_test <- testing(index)Pada metode ini, pemodelan dilakukan sebanyak dua kali, yaitu dengan membentuk:
# membentuk model dengan semua variabel prediktor
model_all <- glm(class ~ ., data = song_train, family = "binomial")
summary(model_all)##
## Call:
## glm(formula = class ~ ., family = "binomial", data = song_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.0813 -0.5139 -0.1297 0.4979 2.6594
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -0.7436 1.6641 -0.447 0.654990
## danceability -1.9824 1.4360 -1.380 0.167439
## energy 3.0088 1.7111 1.758 0.078680 .
## loudness 10.6648 2.1069 5.062 4.15e-07 ***
## valence -8.6922 1.5274 -5.691 1.27e-08 ***
## instrumentalness -2.3121 0.5811 -3.979 6.93e-05 ***
## acousticness -3.2749 0.9281 -3.529 0.000418 ***
## modemajor -1.1016 0.3852 -2.860 0.004240 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 346.40 on 261 degrees of freedom
## Residual deviance: 187.86 on 254 degrees of freedom
## AIC: 203.86
##
## Number of Fisher Scoring iterations: 6
# melakukan feature selection dengan metode backward
step(model_all, direction = "backward", trace = 0)##
## Call: glm(formula = class ~ energy + loudness + valence + instrumentalness +
## acousticness + mode, family = "binomial", data = song_train)
##
## Coefficients:
## (Intercept) energy loudness valence
## -1.816 3.347 11.108 -9.347
## instrumentalness acousticness modemajor
## -2.302 -3.232 -1.142
##
## Degrees of Freedom: 261 Total (i.e. Null); 255 Residual
## Null Deviance: 346.4
## Residual Deviance: 189.8 AIC: 203.8
# membuat model menggunakan prediktor yang dihasilkan oleh metode backward
model_backward <- glm(class ~ danceability + loudness + valence + instrumentalness +
acousticness + mode, data = song_train, family = "binomial")
summary(model_backward)##
## Call:
## glm(formula = class ~ danceability + loudness + valence + instrumentalness +
## acousticness + mode, family = "binomial", data = song_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.3336 -0.5467 -0.1386 0.4876 2.5055
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.7970 0.8649 2.078 0.037753 *
## danceability -2.3737 1.4036 -1.691 0.090816 .
## loudness 8.2883 1.5372 5.392 6.97e-08 ***
## valence -7.5699 1.3129 -5.766 8.12e-09 ***
## instrumentalness -2.1367 0.5674 -3.766 0.000166 ***
## acousticness -4.0444 0.8379 -4.827 1.39e-06 ***
## modemajor -1.0191 0.3797 -2.684 0.007269 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 346.40 on 261 degrees of freedom
## Residual deviance: 191.04 on 255 degrees of freedom
## AIC: 205.04
##
## Number of Fisher Scoring iterations: 6
Setelah membuat model, langkah selanjutnya adalah melakukan prediksi.
Labelling pada project ini dilakukan dengan acuan:
class adalah 1class adalah 0# memprediksi pada training set menggunakan model_all
song_train$pred_prob_all <- predict(object = model_all, newdata = song_train, type="response")
# melakukan labelling pada hasil prediksi model_all di training set
song_train$pred_label_all <- ifelse(song_train$pred_prob_all > 0.5, 1, 0) %>% as.factor()# memprediksi pada testing set menggunakan model_all
song_test$pred_prob_all <- predict(object = model_all, newdata = song_test, type="response")
# melakukan labelling pada hasil prediksi model_all di testing set
song_test$pred_label_all <- ifelse(song_test$pred_prob_all > 0.5, 1, 0) %>% as.factor()# memprediksi pada training set menggunakan model_backward
song_train$pred_prob_backward <- predict(object = model_backward, newdata = song_train, type="response")
# melakukan labelling pada hasil prediksi model_backward di training set
song_train$pred_label_backward <- ifelse(song_train$pred_prob_backward > 0.5, 1, 0) %>% as.factor()# memprediksi pada testing set menggunakan model_backward
song_test$pred_prob_backward <- predict(object = model_backward, newdata = song_test, type="response")
# melakukan labelling pada hasil prediksi model_backward di testing set
song_test$pred_label_backward <- ifelse(song_test$pred_prob_backward > 0.5, 1, 0) %>% as.factor()Membuat confusion matrix dan menghitung nilai metrik evaluasi untuk mengkalkulasikan performa model.
# membuat confusion matrix untuk mengetahui performa model_all di training set
confusionMatrix(data = song_train$pred_label_all, reference = song_train$class, positive = "1")## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 142 22
## 1 22 76
##
## Accuracy : 0.8321
## 95% CI : (0.7812, 0.8752)
## No Information Rate : 0.626
## P-Value [Acc > NIR] : 2.227e-13
##
## Kappa : 0.6414
##
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.7755
## Specificity : 0.8659
## Pos Pred Value : 0.7755
## Neg Pred Value : 0.8659
## Prevalence : 0.3740
## Detection Rate : 0.2901
## Detection Prevalence : 0.3740
## Balanced Accuracy : 0.8207
##
## 'Positive' Class : 1
##
# membuat confusion matrix untuk mengetahui performa model_all di testing set
confusionMatrix(data = song_test$pred_label_all, reference = song_test$class, positive = "1")## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 31 6
## 1 10 19
##
## Accuracy : 0.7576
## 95% CI : (0.6364, 0.8546)
## No Information Rate : 0.6212
## P-Value [Acc > NIR] : 0.01359
##
## Kappa : 0.5005
##
## Mcnemar's Test P-Value : 0.45325
##
## Sensitivity : 0.7600
## Specificity : 0.7561
## Pos Pred Value : 0.6552
## Neg Pred Value : 0.8378
## Prevalence : 0.3788
## Detection Rate : 0.2879
## Detection Prevalence : 0.4394
## Balanced Accuracy : 0.7580
##
## 'Positive' Class : 1
##
# membuat confusion matrix untuk mengetahui performa model_backward di training set
confusionMatrix(data = song_train$pred_label_backward, reference = song_train$class, positive = "1")## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 144 26
## 1 20 72
##
## Accuracy : 0.8244
## 95% CI : (0.7728, 0.8685)
## No Information Rate : 0.626
## P-Value [Acc > NIR] : 1.868e-12
##
## Kappa : 0.6204
##
## Mcnemar's Test P-Value : 0.461
##
## Sensitivity : 0.7347
## Specificity : 0.8780
## Pos Pred Value : 0.7826
## Neg Pred Value : 0.8471
## Prevalence : 0.3740
## Detection Rate : 0.2748
## Detection Prevalence : 0.3511
## Balanced Accuracy : 0.8064
##
## 'Positive' Class : 1
##
# membuat confusion matrix untuk mengetahui performa model_backward di testing set
confusionMatrix(data = song_test$pred_label_backward, reference = song_test$class, positive = "1")## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 31 7
## 1 10 18
##
## Accuracy : 0.7424
## 95% CI : (0.6199, 0.8422)
## No Information Rate : 0.6212
## P-Value [Acc > NIR] : 0.02624
##
## Kappa : 0.4652
##
## Mcnemar's Test P-Value : 0.62763
##
## Sensitivity : 0.7200
## Specificity : 0.7561
## Pos Pred Value : 0.6429
## Neg Pred Value : 0.8158
## Prevalence : 0.3788
## Detection Rate : 0.2727
## Detection Prevalence : 0.4242
## Balanced Accuracy : 0.7380
##
## 'Positive' Class : 1
##
Cek nilai VIF (Variance Inflation Factor) untuk setiap variabel prediktor dengan aturan sebagai berikut:
# uji multikolinieritas pada model_all
vif(model_all)## danceability energy loudness valence
## 1.121153 5.580297 4.331198 1.770004
## instrumentalness acousticness mode
## 1.372755 2.786147 1.067824
# uji multikolinieritas pada model_backward
vif(model_backward)## danceability loudness valence instrumentalness
## 1.109833 2.404918 1.382202 1.339214
## acousticness mode
## 2.369120 1.045837
Dari nilai VIF kedua model di atas, dapat disimpulkan bahwa tidak ada multikolinieritas (asumsi terpenuhi).
Berdasarkan summary pada model_all, diketahui
ada satu variabel prediktor yang tidak signifikan memengaruhi variabel
target - variabel prediktor tersebut adalah variabel
energy. Diketahui pula bahwa model_all
memiliki nilai AIC sebesar 203,18.
Sedangkan pada model_backward, variabel
energy dihapus dari model dan menghasilkan nilai AIC yang
lebih kecil daripada model_all, yaitu sebesar 202,4.
Namun, jika dilihat dari nilai Residual Deviance, ternyata
nilai di model_all lebih kecil daripada di
model_backward.
model_all$deviance## [1] 187.8622
model_backward$deviance## [1] 191.0424
Ditambah lagi, precision dari model_all
diketahui lebih besar daripada di model_backward, terutama
di bagian precision untuk testing set.
Note: precision menjadi acuan utama dalam evaluasi model karena: kita tentu tidak menginginkan lagu yang bukan selera kita masuk ke sistem rekomendasi (atau false positive menjadi fokus perhatian)
# pembuatan tabel perbandingan
metrik <- c("accuracy", "precision")
model_all_training <- c(0.8282, 0.7849)
model_all_testing <- c(0.7879, 0.6774)
model_backward_training <- c(0.8206, 0.7684)
model_backward_testing <- c(0.7727, 0.6667)
data.frame(metrik, model_all_training, model_all_testing, model_backward_training, model_backward_testing)Dengan pertimbangan-pertimbangan di atas, saya pun memutuskan untuk
memilih model_all sebagai model regresi logistik biner
terbaik. Hal ini dikarenakan nilai Residual Deviance dan
precision - nya yang lebih besar daripada
model_backward, meskipun nilai AIC-nya sedikit lebih
kecil.
Agar memudahkan untuk analisis, saya membuat dataframe baru yang khusus untuk analisis k-nearest neighbor, dimana data-data prediksi (yang sebelumnya dibuat saat analisis menggunakan regresi logistik) dihapus terlebih dahulu.
Note: rincian variabel prediktor yang digunakan dalam k-nearest neighbor disamakan dengan regresi logistik biner
# buat dataframe untuk knn
song_train_knn <- song_train %>%
select(-c(pred_prob_all, pred_label_all, pred_prob_backward, pred_label_backward))
song_test_knn <- song_test %>%
select(-c(pred_prob_all, pred_label_all, pred_prob_backward, pred_label_backward))Cek struktur data dari dataframe yang baru dibuat.
# cek struktur data
str(song_train_knn)## 'data.frame': 262 obs. of 8 variables:
## $ danceability : num 0.845 0.477 0.845 0.903 0.698 0.872 0.878 0.883 0.502 0.831 ...
## $ energy : num 0.33 0.566 0.783 0.463 0.432 0.514 0.535 0.82 0.867 0.794 ...
## $ loudness : num 0.504 0.42 0.359 0.315 0.592 ...
## $ valence : num 0.883 0.778 0.577 0.733 0.679 0.798 0.958 0.839 0.547 0.735 ...
## $ instrumentalness: num 7.14e-01 0.00 4.66e-03 1.07e-01 7.62e-03 6.76e-03 8.45e-06 3.70e-04 2.47e-02 3.40e-05 ...
## $ acousticness : num 0.322 0.238 0.087 0.0238 0.297 0.54 0.114 0.29 0.17 0.705 ...
## $ mode : Factor w/ 2 levels "minor","major": 1 2 1 1 2 1 2 1 2 1 ...
## $ class : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
# cek struktur data
str(song_test_knn)## 'data.frame': 66 obs. of 8 variables:
## $ danceability : num 0.418 0.126 0.315 0.608 0.355 0.49 0.439 0.549 0.578 0.456 ...
## $ energy : num 0.443 0.0539 0.294 0.523 0.569 0.454 0.605 0.713 0.6 0.776 ...
## $ loudness : num 0.553 0.833 0.454 0.553 0.468 ...
## $ valence : num 0.28 0.038 0.0963 0.696 0.292 0.157 0.107 0.206 0.154 0.223 ...
## $ instrumentalness: num 0.876 0.509 0.045 0.6 0.171 0.417 0.534 0.0145 0.0131 0.00975 ...
## $ acousticness : num 0.319 0.799 0.185 0.0775 0.00311 0.185 0.00202 0.0453 0.0129 0.0423 ...
## $ mode : Factor w/ 2 levels "minor","major": 1 2 1 1 2 1 1 2 2 1 ...
## $ class : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
Terlihat dari struktur data di atas, bahwa variabel mode
masih berupa faktor. Padahal, dalam k-nearest neighbor, semua
variabel prediktor harus bertipe numerik karena akan dihitung jarak
Euclidean-nya. Untuk mengatasi hal ini, sebaiknya dilakukan one-hot
encoding pada variabel mode seperti berikut.
# one-hot encoding
levels(song_train_knn$mode) <- list("0" = "minor", "1" = "major")
song_train_knn <- song_train_knn %>%
mutate(mode = as.character(mode)) %>%
mutate(mode = as.numeric(mode))
levels(song_test_knn$mode) <- list("0" = "minor", "1" = "major")
song_test_knn <- song_test_knn %>%
mutate(mode = as.character(mode)) %>%
mutate(mode = as.numeric(mode))# mengintip tipe dan struktur data
glimpse(song_train_knn)## Rows: 262
## Columns: 8
## $ danceability <dbl> 0.8450, 0.4770, 0.8450, 0.9030, 0.6980, 0.8720, 0.878~
## $ energy <dbl> 0.33000, 0.56600, 0.78300, 0.46300, 0.43200, 0.51400,~
## $ loudness <dbl> 0.5036444, 0.4200116, 0.3593060, 0.3152996, 0.5923416~
## $ valence <dbl> 0.8830, 0.7780, 0.5770, 0.7330, 0.6790, 0.7980, 0.958~
## $ instrumentalness <dbl> 7.14e-01, 0.00e+00, 4.66e-03, 1.07e-01, 7.62e-03, 6.7~
## $ acousticness <dbl> 0.322000, 0.238000, 0.087000, 0.023800, 0.297000, 0.5~
## $ mode <dbl> 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0,~
## $ class <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
# mengintip tipe dan struktur data
glimpse(song_test_knn)## Rows: 66
## Columns: 8
## $ danceability <dbl> 0.418, 0.126, 0.315, 0.608, 0.355, 0.490, 0.439, 0.54~
## $ energy <dbl> 0.4430, 0.0539, 0.2940, 0.5230, 0.5690, 0.4540, 0.605~
## $ loudness <dbl> 0.5529417, 0.8326651, 0.4541772, 0.5532281, 0.4682514~
## $ valence <dbl> 0.2800, 0.0380, 0.0963, 0.6960, 0.2920, 0.1570, 0.107~
## $ instrumentalness <dbl> 8.76e-01, 5.09e-01, 4.50e-02, 6.00e-01, 1.71e-01, 4.1~
## $ acousticness <dbl> 0.319000, 0.799000, 0.185000, 0.077500, 0.003110, 0.1~
## $ mode <dbl> 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1,~
## $ class <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,~
Terlihat bahwa semua variabel prediktor sudah bertipe numerik, maka dari itu tahapan analisis bisa dilanjutkan.
# prediktor data train
song_train_x <- song_train_knn %>% select_if(is.numeric)
# target data train
song_train_y <- song_train_knn %>% select(class)
# prediktor data test
song_test_x <- song_test_knn %>% select_if(is.numeric)
# target data test
song_test_y <- song_test_knn %>% select(class)# pemilihan nilai k optimum
(sqrt(nrow(song_train_x))) - 1## [1] 15.18641
Karena kelas pada variabel target berjumlah genap, maka sebaiknya nilai k-nya adalah ganjil. Berdasarkan hasil penghitungan di atas, mari kita ambil k = 15.
# prediksi menggunakan metode knn
song_kknpred_15 <- knn(train = song_train_x,
test = song_test_x,
cl = song_train_y$class,
k = 15)# pembuatan confusion matrix dan penghitungan metrik untuk mengetahui performa model pada testing set
confusionMatrix(data = song_kknpred_15, reference = song_test_y$class, positive = "1")## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 29 5
## 1 12 20
##
## Accuracy : 0.7424
## 95% CI : (0.6199, 0.8422)
## No Information Rate : 0.6212
## P-Value [Acc > NIR] : 0.02624
##
## Kappa : 0.481
##
## Mcnemar's Test P-Value : 0.14561
##
## Sensitivity : 0.8000
## Specificity : 0.7073
## Pos Pred Value : 0.6250
## Neg Pred Value : 0.8529
## Prevalence : 0.3788
## Detection Rate : 0.3030
## Detection Prevalence : 0.4848
## Balanced Accuracy : 0.7537
##
## 'Positive' Class : 1
##
Berdasarkan hasil evaluasi model di atas, diketahui bahwa nilai precision-nya adalah 0,6250 dan nilai accuracy-nya adalah 0,7424.
# perbandingan metode regresi logistik biner dan knn
metrik <- c("accuracy", "precision")
regresi_logistik_biner <- c(0.7879, 0.6774)
knn <- c(0.7424, 0.6250)
data.frame(metrik, regresi_logistik_biner, knn)Berdasarkan metrik di atas, dapat ditarik kesimpulan bahwa metode regresi logistik biner menghasilkan performa yang lebih baik daripada k-nearest neighbor. Meskipun begitu, karena nilai precision-nya yang masih tergolong kecil, maka tidak menutup kemungkinan terdapat metode lain yang dapat meningkatkan kepresisian dan keakuratan hasil prediksi.