Spotify Song Clustering with K-Means

Explanation

Permulaan

Hallo ini adalah Rpubs ke-tujuh saya mengenai Unsupervised Learning Dengan Metode Clustering menggunakan Algoritma K-Means, semoga bermanfaat :)

Tentang Data

Dataset ini adalah dataset yang berisikan pengukuran lagu pada spotify berdasarkan kriteria tertentu, mulai dari genre, popularity, time-signature, dll.

Beberapa kolom yang terdapat di dalam nya :
  • genre : jenis / kategori lagu
  • artist_name : nama artis pemilik lagu
  • track_name : nama dari lagu
  • track_id : id unik dari setiap track lagu
  • popularity : angka popularitas dari setiap lagi, semakin besar semakin populer
  • acousticness : ukuran kepercayaan dari 0,0 hingga 1,0 apakah trek akustik. 1.0 mewakili keyakinan tinggi bahwa trek akustik.
  • danceability : danceability menggambarkan seberapa cocok trek untuk menari berdasarkan kombinasi elemen musik termasuk tempo, stabilitas ritme, kekuatan ketukan, dan keteraturan keseluruhan. Nilai 0,0 paling tidak dapat menari dan 1,0 paling dapat menari.
  • duration_ms : durasi trek dalam milidetik.
  • energy : energi adalah ukuran dari 0,0 hingga 1,0 dan mewakili ukuran persepsi intensitas dan aktivitas. Biasanya, trek energik terasa cepat, keras, dan berisik. Misalnya, death metal memiliki energi tinggi, sementara pendahuluan Bach mendapat skor rendah pada skala. Fitur persepsi yang berkontribusi pada atribut ini termasuk rentang dinamis, kenyaringan yang dirasakan, timbre, tingkat onset, dan entropi umum.
  • instrumentalness : memprediksi apakah trek tidak berisi vokal. Suara “Ooh” dan “aah” diperlakukan sebagai instrumental dalam konteks ini. Rap atau trek kata yang diucapkan jelas “vokal”. Semakin dekat nilai instrumental menjadi 1,0, semakin besar kemungkinan trek tersebut tidak berisi konten vokal. Nilai di atas 0,5 dimaksudkan untuk mewakili trek instrumental, tetapi kepercayaan diri lebih tinggi saat nilainya mendekati 1,0.
  • key : Kuncinya ada di trek. Integer memetakan ke nada menggunakan notasi Kelas Pitch standar. Misalnya. 0 = C, 1 = C♯/D♭, 2 = D, dan seterusnya. Jika tidak ada kunci yang terdeteksi, nilainya adalah -1.
  • liveness : mendeteksi kehadiran penonton dalam rekaman. Nilai keaktifan yang lebih tinggi menunjukkan peningkatan kemungkinan bahwa lagu tersebut ditampilkan secara langsung. Nilai di atas 0,8 memberikan kemungkinan yang kuat bahwa trek itu hidup.
  • mode : mode menunjukkan modalitas (mayor atau minor) dari sebuah trek, jenis tangga nada dari mana konten melodinya berasal. Mayor diwakili oleh 1 dan minor adalah 0.
  • speechiness : speechiness mendeteksi keberadaan kata-kata yang diucapkan di trek. Semakin eksklusif rekaman seperti pidato (misalnya acara bincang-bincang, buku audio, puisi), semakin mendekati 1,0 nilai atributnya. Nilai di atas 0,66 menggambarkan trek yang mungkin seluruhnya terbuat dari kata-kata yang diucapkan. Nilai antara 0,33 dan 0,66 menggambarkan trek yang mungkin berisi musik dan ucapan, baik dalam bagian atau berlapis, termasuk kasus seperti musik rap. Nilai di bawah 0,33 kemungkinan besar mewakili musik dan trek non-suara lainnya.
  • tempo : perkiraan tempo keseluruhan trek dalam ketukan per menit (BPM). Dalam terminologi musik, tempo adalah kecepatan atau kecepatan dari bagian tertentu dan berasal langsung dari durasi ketukan rata-rata.
  • time_signature : perkiraan waktu tanda tangan. Tanda tangan waktu (meter) adalah konvensi notasi untuk menentukan berapa banyak ketukan di setiap bar (atau ukuran). Tanda tangan waktu berkisar dari 3 hingga 7 yang menunjukkan tanda waktu “3/4”, hingga “7/4”.
  • valence : ukuran dari 0,0 hingga 1,0 menggambarkan kepositifan musik yang disampaikan oleh sebuah lagu. Trek dengan valensi tinggi terdengar lebih positif (misalnya bahagia, ceria, euforia), sedangkan trek dengan valensi rendah terdengar lebih negatif (misalnya sedih, tertekan, marah).

Business Goal

Disini saya membuat Role-Play yang dimana saya adalah sebagai song-writer sekaligus komposer pada suatu Studio Music. Disini saya ingin merancang sebuah System Recomendation bersama seorang teman saya yang bekerja sebagai Data-Scienctist, pada lagu yang ada di Spotify. Tujuan nya agar saya bisa mengatahui sekiranya mayoritas lagu yang banyak di Rekomendasikan kepada User berdasarkan Popularitas nya itu apa, sehingga saya bisa menciptakan lagu yang memiliki nuansa yang sama.

Setup Library

kita disini akan membutuhkan beberapa library baik itu untuk visualisasi maupun pembentukan model

library(readr)
library(tidyverse)
library(factoextra)
library(scales)
library(FactoMineR)

Read Data

Pertama-tama mari baca data nya nya terlebih dahulu

song <- read_csv("data_input/SpotifyFeatures.csv")

cek kolom dan tipe data yang terdapat pada song

glimpse(song)
#> Rows: 232,725
#> Columns: 18
#> $ genre            <chr> "Movie", "Movie", "Movie", "Movie", "Movie", "Movie",…
#> $ artist_name      <chr> "Henri Salvador", "Martin & les fées", "Joseph Willia…
#> $ track_name       <chr> "C'est beau de faire un Show", "Perdu d'avance (par G…
#> $ track_id         <chr> "0BRjO6ga9RKCKjfDqeFgWV", "0BjC1NfoEOOusryehmNudP", "…
#> $ popularity       <dbl> 0, 1, 3, 0, 4, 0, 2, 15, 0, 10, 0, 2, 4, 3, 0, 0, 0, …
#> $ acousticness     <dbl> 0.61100, 0.24600, 0.95200, 0.70300, 0.95000, 0.74900,…
#> $ danceability     <dbl> 0.389, 0.590, 0.663, 0.240, 0.331, 0.578, 0.703, 0.41…
#> $ duration_ms      <dbl> 99373, 137373, 170267, 152427, 82625, 160627, 212293,…
#> $ energy           <dbl> 0.9100, 0.7370, 0.1310, 0.3260, 0.2250, 0.0948, 0.270…
#> $ instrumentalness <dbl> 0.00000000, 0.00000000, 0.00000000, 0.00000000, 0.123…
#> $ key              <chr> "C#", "F#", "C", "C#", "F", "C#", "C#", "F#", "C", "G…
#> $ liveness         <dbl> 0.3460, 0.1510, 0.1030, 0.0985, 0.2020, 0.1070, 0.105…
#> $ loudness         <dbl> -1.828, -5.559, -13.879, -12.178, -21.150, -14.970, -…
#> $ mode             <chr> "Major", "Minor", "Minor", "Major", "Major", "Major",…
#> $ speechiness      <dbl> 0.0525, 0.0868, 0.0362, 0.0395, 0.0456, 0.1430, 0.953…
#> $ tempo            <dbl> 166.969, 174.003, 99.488, 171.758, 140.576, 87.479, 8…
#> $ time_signature   <chr> "4/4", "4/4", "5/4", "4/4", "4/4", "4/4", "4/4", "4/4…
#> $ valence          <dbl> 0.8140, 0.8160, 0.3680, 0.2270, 0.3900, 0.3580, 0.533…

Disini banyak sekali tipe data yang belum sesuai yakni pada tipe Character yang seharusnya menjadi Faktor, tetapi disini karena kita ingin melakukan Clustering jadi biarkan saja tipe data Characternya tidak perlu di ubah karena nanti akan kita take-out dan kita satukan kembali.

Data Wrangling

Cek Missing Value

colSums(is.na(song))
#>            genre      artist_name       track_name         track_id 
#>                0                0                0                0 
#>       popularity     acousticness     danceability      duration_ms 
#>                0                0                0                0 
#>           energy instrumentalness              key         liveness 
#>                0                0                0                0 
#>         loudness             mode      speechiness            tempo 
#>                0                0                0                0 
#>   time_signature          valence 
#>                0                0
anyNA(song)
#> [1] FALSE

Yeayy tidak ada missing value, berarti kita bisa melanjutkan ke tahap berikutnya.

Cek Outlier

summary(song)
#>     genre           artist_name         track_name          track_id        
#>  Length:232725      Length:232725      Length:232725      Length:232725     
#>  Class :character   Class :character   Class :character   Class :character  
#>  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
#>                                                                             
#>                                                                             
#>                                                                             
#>    popularity      acousticness     danceability     duration_ms     
#>  Min.   :  0.00   Min.   :0.0000   Min.   :0.0569   Min.   :  15387  
#>  1st Qu.: 29.00   1st Qu.:0.0376   1st Qu.:0.4350   1st Qu.: 182857  
#>  Median : 43.00   Median :0.2320   Median :0.5710   Median : 220427  
#>  Mean   : 41.13   Mean   :0.3686   Mean   :0.5544   Mean   : 235122  
#>  3rd Qu.: 55.00   3rd Qu.:0.7220   3rd Qu.:0.6920   3rd Qu.: 265768  
#>  Max.   :100.00   Max.   :0.9960   Max.   :0.9890   Max.   :5552917  
#>      energy          instrumentalness        key               liveness      
#>  Min.   :0.0000203   Min.   :0.0000000   Length:232725      Min.   :0.00967  
#>  1st Qu.:0.3850000   1st Qu.:0.0000000   Class :character   1st Qu.:0.09740  
#>  Median :0.6050000   Median :0.0000443   Mode  :character   Median :0.12800  
#>  Mean   :0.5709577   Mean   :0.1483012                      Mean   :0.21501  
#>  3rd Qu.:0.7870000   3rd Qu.:0.0358000                      3rd Qu.:0.26400  
#>  Max.   :0.9990000   Max.   :0.9990000                      Max.   :1.00000  
#>     loudness           mode            speechiness         tempo       
#>  Min.   :-52.457   Length:232725      Min.   :0.0222   Min.   : 30.38  
#>  1st Qu.:-11.771   Class :character   1st Qu.:0.0367   1st Qu.: 92.96  
#>  Median : -7.762   Mode  :character   Median :0.0501   Median :115.78  
#>  Mean   : -9.570                      Mean   :0.1208   Mean   :117.67  
#>  3rd Qu.: -5.501                      3rd Qu.:0.1050   3rd Qu.:139.05  
#>  Max.   :  3.744                      Max.   :0.9670   Max.   :242.90  
#>  time_signature        valence      
#>  Length:232725      Min.   :0.0000  
#>  Class :character   1st Qu.:0.2370  
#>  Mode  :character   Median :0.4440  
#>                     Mean   :0.4549  
#>                     3rd Qu.:0.6600  
#>                     Max.   :1.0000

Terlihat dari hasil Summary banyak sekali nilai Q3 dan max yang terpaut jauh, dimana itu artinya terdapat Outlier pada data kita

boxplot(song$duration_ms)

boxplot(song$liveness)

boxplot(song$tempo)

Dari contoh ketiga Variable diatas (duration_ms, liveness dan tempo) terlihat masih banyak sekali Outlier, tetapi karena Outlier nya tergolong banyak, sehingga kalaupun ingin di bersihkan, pasti akan menghilangkan banyak informasi dan disini saya menyimpulkan dari semua Outlier tersebut, sepertinya memang itulah pola data kita, yang cendrung tinggi, sehingga Outlier tersebut tidak apa kalau ingin di abaikan.

Mari kita hapus kolom Kategorik pada data Song, karena tidak akan kita gunakan pada Clustering yang akan kita lakukan. Seperti yang kita ketahui Algoritma K-Means tidak bisa memproses data Kategorik, oleh karena itu harus kita take-out untuk sekarang kolom kategorik nya dan nanti kita akan satukan kembali setelah melakukan Clustering.

song_cluster <- song %>% 
  select(-genre, -track_id, -key, -mode, -artist_name, -track_name, -time_signature)

glimpse(song_cluster)
#> Rows: 232,725
#> Columns: 11
#> $ popularity       <dbl> 0, 1, 3, 0, 4, 0, 2, 15, 0, 10, 0, 2, 4, 3, 0, 0, 0, …
#> $ acousticness     <dbl> 0.61100, 0.24600, 0.95200, 0.70300, 0.95000, 0.74900,…
#> $ danceability     <dbl> 0.389, 0.590, 0.663, 0.240, 0.331, 0.578, 0.703, 0.41…
#> $ duration_ms      <dbl> 99373, 137373, 170267, 152427, 82625, 160627, 212293,…
#> $ energy           <dbl> 0.9100, 0.7370, 0.1310, 0.3260, 0.2250, 0.0948, 0.270…
#> $ instrumentalness <dbl> 0.00000000, 0.00000000, 0.00000000, 0.00000000, 0.123…
#> $ liveness         <dbl> 0.3460, 0.1510, 0.1030, 0.0985, 0.2020, 0.1070, 0.105…
#> $ loudness         <dbl> -1.828, -5.559, -13.879, -12.178, -21.150, -14.970, -…
#> $ speechiness      <dbl> 0.0525, 0.0868, 0.0362, 0.0395, 0.0456, 0.1430, 0.953…
#> $ tempo            <dbl> 166.969, 174.003, 99.488, 171.758, 140.576, 87.479, 8…
#> $ valence          <dbl> 0.8140, 0.8160, 0.3680, 0.2270, 0.3900, 0.3580, 0.533…

Cek Skala dari setiap nilai numerik nya, karena jika terdapat skala nilai yang jauh berbeda maka akan menutupi data point dengan skala nilai yang kecil sehingga akan menghasilkan Clusterisasi yang MisLeading karena cendrung mengelompokkan data dengan skala nilai besar.

summary(song_cluster)
#>    popularity      acousticness     danceability     duration_ms     
#>  Min.   :  0.00   Min.   :0.0000   Min.   :0.0569   Min.   :  15387  
#>  1st Qu.: 29.00   1st Qu.:0.0376   1st Qu.:0.4350   1st Qu.: 182857  
#>  Median : 43.00   Median :0.2320   Median :0.5710   Median : 220427  
#>  Mean   : 41.13   Mean   :0.3686   Mean   :0.5544   Mean   : 235122  
#>  3rd Qu.: 55.00   3rd Qu.:0.7220   3rd Qu.:0.6920   3rd Qu.: 265768  
#>  Max.   :100.00   Max.   :0.9960   Max.   :0.9890   Max.   :5552917  
#>      energy          instrumentalness       liveness          loudness      
#>  Min.   :0.0000203   Min.   :0.0000000   Min.   :0.00967   Min.   :-52.457  
#>  1st Qu.:0.3850000   1st Qu.:0.0000000   1st Qu.:0.09740   1st Qu.:-11.771  
#>  Median :0.6050000   Median :0.0000443   Median :0.12800   Median : -7.762  
#>  Mean   :0.5709577   Mean   :0.1483012   Mean   :0.21501   Mean   : -9.570  
#>  3rd Qu.:0.7870000   3rd Qu.:0.0358000   3rd Qu.:0.26400   3rd Qu.: -5.501  
#>  Max.   :0.9990000   Max.   :0.9990000   Max.   :1.00000   Max.   :  3.744  
#>   speechiness         tempo           valence      
#>  Min.   :0.0222   Min.   : 30.38   Min.   :0.0000  
#>  1st Qu.:0.0367   1st Qu.: 92.96   1st Qu.:0.2370  
#>  Median :0.0501   Median :115.78   Median :0.4440  
#>  Mean   :0.1208   Mean   :117.67   Mean   :0.4549  
#>  3rd Qu.:0.1050   3rd Qu.:139.05   3rd Qu.:0.6600  
#>  Max.   :0.9670   Max.   :242.90   Max.   :1.0000

terlihat masih banyak skala nilai yang berbeda. Mari kita melakukan Scaling dengan Z-Score Standardization menggunakan Fungsi scale()

song_cluster_scale <- scale(song_cluster)
summary(song_cluster_scale)
#>    popularity       acousticness      danceability       duration_ms     
#>  Min.   :-2.2610   Min.   :-1.0389   Min.   :-2.68019   Min.   :-1.8475  
#>  1st Qu.:-0.6667   1st Qu.:-0.9329   1st Qu.:-0.64310   1st Qu.:-0.4394  
#>  Median : 0.1029   Median :-0.3849   Median : 0.08963   Median :-0.1236  
#>  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000  
#>  3rd Qu.: 0.7626   3rd Qu.: 0.9963   3rd Qu.: 0.74154   3rd Qu.: 0.2577  
#>  Max.   : 3.2365   Max.   : 1.7686   Max.   : 2.34168   Max.   :44.7114  
#>      energy        instrumentalness     liveness          loudness      
#>  Min.   :-2.1671   Min.   :-0.4898   Min.   :-1.0356   Min.   :-7.1500  
#>  1st Qu.:-0.7058   1st Qu.:-0.4898   1st Qu.:-0.5932   1st Qu.:-0.3670  
#>  Median : 0.1292   Median :-0.4897   Median :-0.4388   Median : 0.3014  
#>  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
#>  3rd Qu.: 0.8200   3rd Qu.:-0.3716   3rd Qu.: 0.2471   3rd Qu.: 0.6784  
#>  Max.   : 1.6247   Max.   : 2.8097   Max.   : 3.9591   Max.   : 2.2196  
#>   speechiness           tempo             valence        
#>  Min.   :-0.53129   Min.   :-2.82494   Min.   :-1.74924  
#>  1st Qu.:-0.45314   1st Qu.:-0.79963   1st Qu.:-0.83793  
#>  Median :-0.38091   Median :-0.06112   Median :-0.04198  
#>  Mean   : 0.00000   Mean   : 0.00000   Mean   : 0.00000  
#>  3rd Qu.:-0.08498   3rd Qu.: 0.69217   3rd Qu.: 0.78858  
#>  Max.   : 4.56146   Max.   : 4.05310   Max.   : 2.09595

Bagus, sekarang data kita sudah memiliki skala nilai yang sama.

EDA (Exploratory Data Analysis)

Mari cari tahu lagu apa saja yang paling di sukai berdasarkan dari Popularitas dan Genre nya.

song_eda <- song %>% 
  group_by(track_id, genre, popularity) %>% 
  summarise(popularity = sum(popularity)) %>% 
  arrange(genre, desc(popularity))

song_eda_agg <- song_eda %>% 
  group_by(genre) %>% 
  summarise(popularity = sum(popularity))

ggplot(data = song_eda_agg,
       mapping = aes(x = popularity, y = reorder(genre, popularity))) +
  geom_col(aes(fill = genre), position = "dodge", show.legend = F) +
  scale_x_continuous(labels = unit_format(scale = 10e-3, suffix = " K")) +
  geom_label(aes(label = popularity), size = 3, fontface = "bold") +
  labs(y = NULL, x = "Rating / Popularity", title = "Top Song Rating / Populartity by Genre") + theme_minimal()

Dapat di lihat dari Rating / tingkat Popularitas dari setiap lagu tiap Genre nya, Pop, Hip-Hop, Rock, Indie dan Children’s Music merupakan Genre dengan tingkat Popularitas tertinggi. Hal ini dapat menjadi referensi utama kita di dalam menciptakan sebuah lagu agar memiliki kemungkinan besar untuk di sukai oleh banyak orang.

Clustering

Clustering adalah pengelompokan data berdasarkan karakteristiknya. Clustering bertujuan untuk menghasilkan cluster dimana:

  • tiap observasi di 1 cluster yang sama yang memiliki karakteristik yang mirip
  • tiap observasi dari cluster yang berbeda memiliki karakteristik yang berbeda.

Disini kita akan mencoba untuk melakukan Clusterisasi menggunakan K-means. K-Means adalah centroid-based clustering algorithms. Centroid adalah titik pusat.

K-means merupakan proses yang berulang dari:
  • Membuat pusat cluster (centroid) sebanyak k secara random
  • Menghitung jarak observasi ke pusat cluster
  • Assign tiap observasi ke masing-masing cluster
  • Menggeser centroid ke titik pusat cluster
  • Kembali ke tahap dua… . . Hingga observasi yang terassign ke tiap cluster tidak berubah lagi.

cluster k-means

RNGkind(sample.kind = "Rounding")
set.seed(100)

song_km <- kmeans(song_cluster_scale, centers = 5)
song_km$withinss # wss
#> [1] 368381.0 381937.3 264187.0 107129.7 285441.3
song_km$betweenss # bss
#> [1] 1152888
song_km$betweenss/song_km$totss # (between_SS / total_SS)
#> [1] 0.4503531

Terlihat dari hasil Algoritma K-Means kita diatas, dapat di lihat kita telah membuat 5 Cluster dan yang paling penting adalah hasil dari Total Sum of Square (between_SS / total_SS) yakni 45%. Yang kita inginkan adalah yang mendekati 1, artinya setiap cluster yang kita punya sudah tersebar dan terkelompok secara baik. Untuk WSS (Within Sum of Square) kita mengharap nilai yang kecil supaya jarak setiap observasi ke tiap Centroid Cluster semakin dekat dan terkahir BSS(Between Sum of Square) kita mengharap nilai besar supaya jarak centroid tiap cluster ke global sample mean (rata-rata data keseluruhan) semakin jauh.

Mari kita coba untuk melakukan meningkatkan Performasi dari K-Means kita, dengan merubah jumlah Cluster nya. Jumlah Cluster yang optimal dapat di cek melalui Elbow Method, dengan melihat Total Within Sum of Squares nya.

set.seed(123)
# menghitung jumlah k = 1 sampai dengan k = 30
k.max <- 30
data <- song_cluster_scale
wss <- sapply(1:k.max, 
              function(k){kmeans(data, k, iter.max = 30 )$tot.withinss})

plot(1:k.max, wss,
     type="b", pch = 19, frame = FALSE, 
     xlab="Number of clusters K",
     ylab="Total within-clusters sum of squares")

terlihat pada plot Elbow Method di atas Total Within Sum of Squares semakin melandai pada jumlah cluster = 9-15. Tetapi disini saya ingin mengambil jumlah Cluster = 15 karena menurut saya pada jumlah 15 cluster tidak terdapat penurunan yang signifikan pada Total Within Sum of Squares nya dan seperti yang kita ketahui juga, data yang kita miliki cukup banyak jadi dengan Cluster = 15 sepertinya cocok dengan kondisi data kita.

Mari kita lakukan Clusterisasi ulang dengan jumlah cluster = 15

song_km_tunning <- kmeans(song_cluster_scale, centers = 15)
song_km_tunning$betweenss/song_km_tunning$totss # (between_SS / total_SS)
#> [1] 0.6282952

Dapat kita lihat dari hasil (between_SS / total_SS) sudah semakin mendakati 1 (62.8%). Artinya persebaran data tiap cluster kita sudah semakin baik.

Mari kita satukan kembil kolom kategorik yang sudah kita take-out tadi dengan hasil Clusterisasi yang sudah kita lakukan

song_cluster_profiling <- song %>% 
  mutate(cluster = as.factor(song_km_tunning$cluster))

head(song_cluster_profiling)
#> # A tibble: 6 × 19
#>   genre artist_name  track_name   track_id  popularity acousticness danceability
#>   <chr> <chr>        <chr>        <chr>          <dbl>        <dbl>        <dbl>
#> 1 Movie Henri Salva… C'est beau … 0BRjO6ga…          0        0.611        0.389
#> 2 Movie Martin & le… Perdu d'ava… 0BjC1Nfo…          1        0.246        0.59 
#> 3 Movie Joseph Will… Don't Let M… 0CoSDzoN…          3        0.952        0.663
#> 4 Movie Henri Salva… Dis-moi Mon… 0Gc6TVm5…          0        0.703        0.24 
#> 5 Movie Fabien Nataf Ouverture    0IuslXpM…          4        0.95         0.331
#> 6 Movie Henri Salva… Le petit so… 0Mf1jKa8…          0        0.749        0.578
#> # … with 12 more variables: duration_ms <dbl>, energy <dbl>,
#> #   instrumentalness <dbl>, key <chr>, liveness <dbl>, loudness <dbl>,
#> #   mode <chr>, speechiness <dbl>, tempo <dbl>, time_signature <chr>,
#> #   valence <dbl>, cluster <fct>

Setelah kita satukan kembali kolom nya, kita dapat membuat System Recomendation berdasarkan Cluster dari setiap lagu yang ingin kita coba untuk berikan rekomendasi lagu serupa nya berdasarkan Cluster dari lagu itu sendiri.

Mari lakukan Profiling dari hasil clusterisasi yang sudah kita lakukan untuk mengtahui kriteria dari setiap Cluster.

song_cluster_profiling %>% 
  group_by(cluster) %>% 
  summarise_all(mean)
#> # A tibble: 15 × 19
#>    cluster genre artist_name track_name track_id popularity acousticness
#>    <fct>   <dbl>       <dbl>      <dbl>    <dbl>      <dbl>        <dbl>
#>  1 1          NA          NA         NA       NA       16.8       0.917 
#>  2 2          NA          NA         NA       NA       23.2       0.711 
#>  3 3          NA          NA         NA       NA       12.3       0.692 
#>  4 4          NA          NA         NA       NA       49.6       0.0941
#>  5 5          NA          NA         NA       NA       41.2       0.219 
#>  6 6          NA          NA         NA       NA       43.5       0.150 
#>  7 7          NA          NA         NA       NA       45.1       0.628 
#>  8 8          NA          NA         NA       NA       57.5       0.174 
#>  9 9          NA          NA         NA       NA       20.6       0.802 
#> 10 10         NA          NA         NA       NA       27.7       0.163 
#> 11 11         NA          NA         NA       NA       44.9       0.0608
#> 12 12         NA          NA         NA       NA       57.5       0.168 
#> 13 13         NA          NA         NA       NA       30.0       0.873 
#> 14 14         NA          NA         NA       NA       49.7       0.666 
#> 15 15         NA          NA         NA       NA       37.4       0.250 
#> # … with 12 more variables: danceability <dbl>, duration_ms <dbl>,
#> #   energy <dbl>, instrumentalness <dbl>, key <dbl>, liveness <dbl>,
#> #   loudness <dbl>, mode <dbl>, speechiness <dbl>, tempo <dbl>,
#> #   time_signature <dbl>, valence <dbl>

Dari hasil rata-rata setiap Cluster diatas dapat kita simpulkan :

Cluster 1 : cendrung memiliki popularitas yang rendah, tidak cocok untuk di gunakan sebagai musik dance karena dilihat dari danceability yang kecil. Lagu pada Cluster ini memiliki tingkat energy yang rendah tidak seperti lagu Metal dan sejenisnya serta positiveness yang dimiliki pun rendah jadi Cluster ini cendrung berisikan lagu yang bertemakan (sad, depressed, angry) Profiling disini hanya kita lakukan pada Cluster 1 saja karena cuman sebagai contoh. Mengingat jumlah kolom dan Cluster yang banyak akan memakan banyak waktu untuk melakukan Profiling pada semua Cluster. Tetapi kita dapat ide dari pada bagaimana cara untuk melakukan Profiling pada Cluster hasil K-Means

Mari kita lakukan Visualisasi terhadap hasil 15 Cluster yang kita miliki

fviz_cluster(song_km_tunning, data = song_cluster_scale)

Principal Component Analysis (PCA)

PCA merupakan salah satu metode untuk melakukan Dimensionality Reduction untuk mengurangi Dimensi (kolom pada data). Dengan PCA data kita jauh lebih sedikit tetapi tetap mampu untuk mempertahankan Informasi yang terkandung di dalam nya. Dimana hal itu berguna untuk meringan kan daya komputasi terhadap data kita.

Kita bisa menggunakan PCA untuk menghasilkan Hasil Visualisasi yang lebih baik juga. Mari kita coba untuk membuat hasil visualisasi dengan PCA berdasarkan Genre dan Hasil Cluster, lihat mana yang lebih baik

# PCA dengan genre sebagai variable kategorik nya

song_with_pca <- song_cluster %>% 
  mutate(genre = as.factor(song$genre))

song_pca_genre <- PCA(X = song_with_pca,
                scale.unit = T,
                quali.sup = c(12),
                graph = F,
                ncp = 10)
# hapus semua kolom kategorik selain dari pada kolom Cluster
song_cluster_profiling_pca<- song_cluster_profiling %>% 
  select(-genre, -track_id, -key, -mode, -artist_name, -track_name, -time_signature)

# PCA dengan hasil cluster sebagai variable kategorik nya
song_pca_cluster <- PCA(X = song_cluster_profiling_pca,
                        scale.unit = T,
                        quali.sup = c(12),
                        graph = F,
                        ncp = 10)

summary(song_pca_cluster)
#> 
#> Call:
#> PCA(X = song_cluster_profiling_pca, scale.unit = T, ncp = 10,  
#>      quali.sup = c(12), graph = F) 
#> 
#> 
#> Eigenvalues
#>                        Dim.1   Dim.2   Dim.3   Dim.4   Dim.5   Dim.6   Dim.7
#> Variance               3.610   1.710   1.171   1.000   0.862   0.757   0.638
#> % of var.             32.822  15.546  10.648   9.089   7.834   6.880   5.799
#> Cumulative % of var.  32.822  48.368  59.016  68.105  75.939  82.819  88.617
#>                        Dim.8   Dim.9  Dim.10  Dim.11
#> Variance               0.485   0.375   0.277   0.115
#> % of var.              4.413   3.411   2.516   1.043
#> Cumulative % of var.  93.030  96.441  98.957 100.000
#> 
#> Individuals (the 10 first)
#>                      Dist    Dim.1    ctr   cos2    Dim.2    ctr   cos2  
#> 1                |  4.033 |  0.990  0.000  0.060 |  0.999  0.000  0.061 |
#> 2                |  3.489 |  1.210  0.000  0.120 |  0.273  0.000  0.006 |
#> 3                |  3.503 | -2.112  0.001  0.364 |  0.353  0.000  0.010 |
#> 4                |  3.875 | -1.955  0.000  0.254 | -0.187  0.000  0.002 |
#> 5                |  4.023 | -2.936  0.001  0.532 |  0.392  0.000  0.009 |
#> 6                |  3.520 | -2.261  0.001  0.413 |  0.701  0.000  0.040 |
#> 7                |  5.377 | -0.757  0.000  0.020 |  3.417  0.003  0.404 |
#> 8                |  2.867 | -1.934  0.000  0.455 |  0.073  0.000  0.001 |
#> 9                |  3.115 |  0.768  0.000  0.061 | -0.056  0.000  0.000 |
#> 10               |  2.492 |  0.730  0.000  0.086 |  0.723  0.000  0.084 |
#>                   Dim.3    ctr   cos2  
#> 1                -0.160  0.000  0.002 |
#> 2                -0.684  0.000  0.038 |
#> 3                -1.867  0.001  0.284 |
#> 4                 0.251  0.000  0.004 |
#> 5                -1.123  0.000  0.078 |
#> 6                -1.731  0.001  0.242 |
#> 7                -1.498  0.001  0.078 |
#> 8                -0.452  0.000  0.025 |
#> 9                -1.178  0.001  0.143 |
#> 10               -0.614  0.000  0.061 |
#> 
#> Variables (the 10 first)
#>                     Dim.1    ctr   cos2    Dim.2    ctr   cos2    Dim.3    ctr
#> popularity       |  0.449  5.588  0.202 | -0.390  8.908  0.152 |  0.108  0.989
#> acousticness     | -0.798 17.659  0.638 |  0.245  3.524  0.060 | -0.226  4.349
#> danceability     |  0.635 11.172  0.403 |  0.079  0.362  0.006 | -0.488 20.314
#> duration_ms      | -0.114  0.360  0.013 | -0.043  0.107  0.002 |  0.642 35.227
#> energy           |  0.848 19.917  0.719 |  0.127  0.939  0.016 |  0.267  6.100
#> instrumentalness | -0.611 10.356  0.374 | -0.239  3.345  0.057 |  0.076  0.488
#> liveness         |  0.056  0.088  0.003 |  0.810 38.356  0.656 |  0.275  6.438
#> loudness         |  0.887 21.812  0.788 | -0.032  0.061  0.001 |  0.166  2.354
#> speechiness      |  0.056  0.088  0.003 |  0.844 41.675  0.713 |  0.031  0.081
#> tempo            |  0.299  2.471  0.089 | -0.195  2.231  0.038 |  0.280  6.707
#>                    cos2  
#> popularity        0.012 |
#> acousticness      0.051 |
#> danceability      0.238 |
#> duration_ms       0.413 |
#> energy            0.071 |
#> instrumentalness  0.006 |
#> liveness          0.075 |
#> loudness          0.028 |
#> speechiness       0.001 |
#> tempo             0.079 |
#> 
#> Supplementary categories (the 10 first)
#>                        Dist      Dim.1     cos2   v.test      Dim.2     cos2
#> cluster_1        |    3.543 |   -3.202    0.817 -188.136 |    0.351    0.010
#> cluster_2        |   13.792 |   -3.026    0.048  -30.534 |    0.675    0.002
#> cluster_3        |    2.469 |   -0.680    0.076  -35.358 |    0.518    0.044
#> cluster_4        |    1.563 |    0.829    0.281   69.394 |   -0.371    0.056
#> cluster_5        |    2.726 |    0.811    0.088   44.509 |    1.563    0.329
#> cluster_6        |    2.218 |    1.685    0.577  120.514 |   -0.265    0.014
#> cluster_7        |    1.769 |   -0.879    0.247  -53.513 |   -0.621    0.123
#> cluster_8        |    1.659 |    1.001    0.364   82.197 |   -0.405    0.060
#> cluster_9        |    5.305 |   -0.588    0.012  -30.927 |    5.116    0.930
#> cluster_10       |    1.866 |    1.239    0.441   95.717 |    0.140    0.006
#>                    v.test      Dim.3     cos2   v.test  
#> cluster_1          30.003 |   -0.204    0.003  -21.080 |
#> cluster_2           9.903 |    8.070    0.342  142.971 |
#> cluster_3          39.191 |   -1.759    0.508 -160.665 |
#> cluster_4         -45.142 |    0.461    0.087   67.716 |
#> cluster_5         124.703 |    1.062    0.152  102.416 |
#> cluster_6         -27.570 |    0.135    0.004   16.910 |
#> cluster_7         -54.941 |    0.378    0.046   40.409 |
#> cluster_8         -48.345 |   -0.125    0.006  -17.985 |
#> cluster_9         391.219 |    0.239    0.002   22.067 |
#> cluster_10         15.676 |   -0.665    0.127  -90.192 |

Dari hasil PCA diatas dapat kita lihat Cummulative of Variance nya, jika kita ingin mempertahankan Informasi sekitar 80% kita dapat menggunakan PC-1 sampai PC-6 / PC-7 dan jika kita ingin mempertahankan Informasi sekitar 90% kita dapat memilih PC-1 sampai PC-8

Visualisasi menggunakan PCA dengan variable kategorik Genre

plot.PCA(x = song_pca_genre, choix = "ind",label = "quali", habillage = 12)

Visualisasi menggunakan PCA dengan variable kategorik Cluster

plot.PCA(x = song_pca_cluster, choix = "ind",label = "quali", habillage = 12)

Terlihat dari hasil Visualisasi diatas menggunakan PCA dan kolom Cluster hasil nya jauh lebih rapi dari pada menggunakan kolom genre.

Mari kita coba explore lebih dalam hasil dari PCA kita untuk melihat dari segi High Dimensionality nya sehingga kita dapat lebih mudah melihat korelasi, variance dan outlier dari pada Data kita.

par(mfcol=c(1,2)) # graphical parameter to arrange plots
plot.PCA(
  x = song_pca_cluster,
  choix = "ind",
  label = "quali",
  habillage = 12,
  title = "Individual Factor Map"
)

plot.PCA(
  x = song_pca_cluster,
  choix = "var",
  axes = c(1, 2),
  title = "Variable Factor Map"
)

Dari hasil kedua Plot diatas dapat kita simpulkan :
  • Beberapa Outlier berasl dari Instrumentalness jika di lihat dari tanda panah yang mengarah kepada Cluster 2
  • Dim / PC-2 banyak merangkum Informasi dari kolom liveness dan speechiness
  • DIM / PC-1 banyak merangkum Informasi dari kolom acousticness, valence, energy dan dancebility. Sisa nya banyak dirangkum oleh kedua PC(1/2)
  • liveness dan speechiness memilii korelasi Positif Kuat. Begitu juga dengan valence, energy dan dancebility serta tempo dan popularity. Tetapi masih harus di pastikan dengan perhitungan korelasi menggunakan fungsi cor()

Untuk lebih jelas dan objektif, kontribusi/korelasi tiap variable ke tiap PC dapat dilihat menggunakan dimdesc(). Semakin tinggi nilai korelasi, semakin banyak informasi yang dirangkum pada PC tersebut.

dim <- dimdesc(song_pca_cluster) # objek pca
# variable yang berkontribusi untuk PC1
as.data.frame(dim$Dim.1$quanti) # quanti: variable numerik
#>                  correlation
#> loudness          0.88742278
#> energy            0.84799976
#> danceability      0.63509756
#> valence           0.61536866
#> popularity        0.44917562
#> tempo             0.29865947
#> speechiness       0.05644642
#> liveness          0.05634070
#> duration_ms      -0.11406412
#> instrumentalness -0.61147590
#> acousticness     -0.79848490
#>                                                                                                                                                                                      p.value
#> loudness         0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> energy           0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> danceability     0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> valence          0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> popularity       0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> tempo            0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> speechiness      0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001563996
#> liveness         0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006302335
#> duration_ms      0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> instrumentalness 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> acousticness     0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
as.data.frame(dim$Dim.2$quanti) # quanti: variable numerik
#>                  correlation
#> speechiness       0.84418926
#> liveness          0.80987677
#> acousticness      0.24548288
#> energy            0.12675019
#> valence           0.09164894
#> danceability      0.07871306
#> loudness         -0.03224192
#> duration_ms      -0.04283383
#> tempo            -0.19533975
#> instrumentalness -0.23915416
#> popularity       -0.39029083
#>                                                                                                                                                                                                                                                                                                                                               p.value
#> speechiness      0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> liveness         0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> acousticness     0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> energy           0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> valence          0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> danceability     0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001758662
#> loudness         0.0000000000000000000000000000000000000000000000000000014047938341516833927857673965414972159570820804264435363004167830057336695727469415936416215294287692238212698779983344440041228451426140802493769399461598368361592292785644531250000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> duration_ms      0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000605107615128800385942661564260539745469232518290857440183851218400082945253755652079762763518362441145690031850391696953106297741029068563088425774694303964542155732289598638486877582370329798050195326962723540717386701925744035
#> tempo            0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> instrumentalness 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#> popularity       0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Bisa dilihat dari hasil korelasi variable diatas pada PC-1, variable loudness, energy, danceaility dan valence memiliki kontribusi paling besar pada PC-1. Dan pada PC-2 speechiness dan liveness merupakan variable yang paling banyak di rangkum / berkontribusi pada PC-2.

System Recomendation Base on Cluster

Mari kita coba memasukan beberapa lagu untuk melihat rekomendasi lagu yang serupa dari Cluster yang di tampilkan.

song_cluster_profiling %>% 
  filter(track_name == "I Fall Apart", artist_name == "Post Malone") %>% 
  select("cluster", "genre")
#> # A tibble: 2 × 2
#>   cluster genre
#>   <fct>   <chr>
#> 1 8       Rap  
#> 2 8       Pop

Mari kita cek lagu yang serupa dengan Post Malone - I Fall Apart (Cluster 8)

song_cluster_profiling %>% 
  filter(cluster == 8, genre ==  "Rap") %>% 
  select(track_name, genre)
#> # A tibble: 3,840 × 2
#>    track_name                       genre
#>    <chr>                            <chr>
#>  1 Wow.                             Rap  
#>  2 MIDDLE CHILD                     Rap  
#>  3 SICKO MODE                       Rap  
#>  4 Thotiana                         Rap  
#>  5 a lot                            Rap  
#>  6 Drip Too Hard (Lil Baby & Gunna) Rap  
#>  7 Money                            Rap  
#>  8 Swervin (feat. 6ix9ine)          Rap  
#>  9 Better Now                       Rap  
#> 10 Pure Water (with Migos)          Rap  
#> # … with 3,830 more rows

Terlihat banyak lagu yang dapat di rekomendasikan yang sesuai dengan Cluster 8 dan ber-genre RAP.

Summary

Dari hasil clusterisasi dan EDA kita diatas, kita mengatahui jenis Genre yang paling banyak di minati oleh orang banyak berdasarkan rating / tingkat popularitas nya. Kita juga sudah mencoba untuk melakukan Clusterisasi menggunakan K-Means untuk melakukan Pengelompokan lagu berdasarkan tiap cluster dan kita juga sudah mencoba untuk melakukan PCA untuk mengurangi Dimensionality dari data kita agar dapat meringankan komputasi dan memperbagus hasil visualisasi. Dari hasil pengelompokkan yang telah kita lakukan kita sudah mencoba untuk memberikan System Recomendation lagu serupa (Post-Malone I Fall a Part) dari Cluter 8 dan ber-genre RAP. Terlihat kita mendapatkan banyak lagu serupa dengan Genre RAP dan berasal dari Cluster 8, yang dimana jika di lihat dari Profiling Cluster 8 yang cendrung berisikan lagu ber-energy, cocok digunakan untuk berdansa dan memiliki tingkat popularitas yang tinggi, cocok dengan Genre RAP dari setiap lagu nya. Dari sini saya sebagai seorang song-writer dan komposer dapat mengtahui Genre apa yang paling populer atau di sukai banyak orang serta saya juga mengatahui rekomendasi lagu serupa, misalnya berdasarkan salah satu Genre yang paling Populer yakni RAP. Maka dari situ saya bisa membuat lagu dengan vibes dan nuansa yang sama dan berkemungkinan besar untuk di sukai oleh banyak orang.