SETUP

library(tidyverse)
library(ggplot2)
library(GGally)
library(FactoMineR)
library(factoextra)
options(scipen=999)
R.version
##                _                                
## platform       x86_64-w64-mingw32               
## arch           x86_64                           
## os             mingw32                          
## crt            ucrt                             
## system         x86_64, mingw32                  
## status                                          
## major          4                                
## minor          2.1                              
## year           2022                             
## month          06                               
## day            23                               
## svn rev        82513                            
## language       R                                
## version.string R version 4.2.1 (2022-06-23 ucrt)
## nickname       Funny-Looking Kid

LBB ini mendemonstrasikan penggunaan PCA dan KMeans (unsupervised learning).

Github
Untuk file .Rmd atau styles.css bisa melihat github proyek ini di taruma/mls-lbb-4.
Notasi \(\text{matematika}\)
Untuk ribuan akan menggunakan pemisah koma (\(,\)), sedangkan pemisah untuk angka dibelakang koma (desimal/mantissa) menggunakan titik (\(.\)).
Catatan
Pada LBB ini untuk piping akan menggunakan native piping |> yang tersedia di R 4.1+ untuk versi selain itu gunakan %>%.

Dataset

Data yang digunakan adalah data Spotify Tracks DB yang diperoleh di Kaggle. Dataset ini berisikan sejumlah lagu dengan karakteristik musiknya. Berikut 6 baris pertama dari dataset:

spotify <- read.csv("data/SpotifyFeatures.csv")
spotify |> head()

Dan berikut struktur data:

spotify |> str()
## 'data.frame':    232725 obs. of  18 variables:
##  $ genre           : chr  "Movie" "Movie" "Movie" "Movie" ...
##  $ artist_name     : chr  "Henri Salvador" "Martin & les fées" "Joseph Williams" "Henri Salvador" ...
##  $ track_name      : chr  "C'est beau de faire un Show" "Perdu d'avance (par Gad Elmaleh)" "Don't Let Me Be Lonely Tonight" "Dis-moi Monsieur Gordon Cooper" ...
##  $ track_id        : chr  "0BRjO6ga9RKCKjfDqeFgWV" "0BjC1NfoEOOusryehmNudP" "0CoSDzoNIKCRs124s9uTVy" "0Gc6TVm52BwZD07Ki6tIvf" ...
##  $ popularity      : int  0 1 3 0 4 0 2 15 0 10 ...
##  $ acousticness    : num  0.611 0.246 0.952 0.703 0.95 0.749 0.344 0.939 0.00104 0.319 ...
##  $ danceability    : num  0.389 0.59 0.663 0.24 0.331 0.578 0.703 0.416 0.734 0.598 ...
##  $ duration_ms     : int  99373 137373 170267 152427 82625 160627 212293 240067 226200 152694 ...
##  $ energy          : num  0.91 0.737 0.131 0.326 0.225 0.0948 0.27 0.269 0.481 0.705 ...
##  $ instrumentalness: num  0 0 0 0 0.123 0 0 0 0.00086 0.00125 ...
##  $ key             : chr  "C#" "F#" "C" "C#" ...
##  $ liveness        : num  0.346 0.151 0.103 0.0985 0.202 0.107 0.105 0.113 0.0765 0.349 ...
##  $ loudness        : num  -1.83 -5.56 -13.88 -12.18 -21.15 ...
##  $ mode            : chr  "Major" "Minor" "Minor" "Major" ...
##  $ speechiness     : num  0.0525 0.0868 0.0362 0.0395 0.0456 0.143 0.953 0.0286 0.046 0.0281 ...
##  $ tempo           : num  167 174 99.5 171.8 140.6 ...
##  $ time_signature  : chr  "4/4" "4/4" "5/4" "4/4" ...
##  $ valence         : num  0.814 0.816 0.368 0.227 0.39 0.358 0.533 0.274 0.765 0.718 ...

Untuk keterangan setiap kolomnya bisa dilihat disini. Berikut penjelasan beberapa kolom yang diambil dari halaman tersebut.

  • acousticness: A confidence measure from 0.0 to 1.0 of whether the track is acoustic. 1.0 represents high confidence the track is acoustic.
  • danceability: Danceability describes how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is least danceable and 1.0 is most danceable.
  • energy: Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity. Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy, while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute include dynamic range, perceived loudness, timbre, onset rate, and general entropy.
  • instrumentalness: Predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as instrumental in this context. Rap or spoken word tracks are clearly “vocal”. The closer the instrumentalness value is to 1.0, the greater likelihood the track contains no vocal content. Values above 0.5 are intended to represent instrumental tracks, but confidence is higher as the value approaches 1.0.
  • key: The key the track is in. Integers map to pitches using standard Pitch Class notation. E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on. If no key was detected, the value is -1.
  • liveness: Detects the presence of an audience in the recording. Higher liveness values represent an increased probability that the track was performed live. A value above 0.8 provides strong likelihood that the track is live.
  • loudness: The overall loudness of a track in decibels (dB). Loudness values are averaged across the entire track and are useful for comparing relative loudness of tracks. Loudness is the quality of a sound that is the primary psychological correlate of physical strength (amplitude). Values typically range between -60 and 0 db.
  • mode: Mode indicates the modality (major or minor) of a track, the type of scale from which its melodic content is derived. Major is represented by 1 and minor is 0.
  • speechiness: Speechiness detects the presence of spoken words in a track. The more exclusively speech-like the recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values above 0.66 describe tracks that are probably made entirely of spoken words. Values between 0.33 and 0.66 describe tracks that may contain both music and speech, either in sections or layered, including such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like tracks.
  • tempo: The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a given piece and derives directly from the average beat duration.
  • time_signature: An estimated time signature. The time signature (meter) is a notational convention to specify how many beats are in each bar (or measure). The time signature ranges from 3 to 7 indicating time signatures of “3/4”, to “7/4”.
  • valence: A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry).

Dilihat secara sekilas datasetnya:

spotify |> glimpse()
## 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       <int> 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      <int> 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…

Setelah ini masuk ketahapan R1 - Pemrosesan Data


R1 - Pemrosesan Data

# untuk menyimpan objek original
spotify_original <- spotify

Pada bab ini akan dievaluasi datasetnya seperti kelengkapannya, tipe data sebelum memasuki eksplorasi dataset maupun pemodelan.

Tipe Kolom/Data

Karena kolom dataset masih tergolong sedikit, jadi untuk pemisahan kolom numerik dan kategori bisa dilakukan secara manual.

Kategori/Factor

Dari kilasan data diatas, bisa disimpulkan bahwa kolom genre, key, mode, dan time_signature merupakan kolom yang berupa kategori. Dan dengan informasi tersebut, kolom diperbarui dengan mengubah kolom tersebut menjadi factor dan dsimpan sebagai objek spotify_factored.

factor_columns <- c("genre", "key", "mode", "time_signature")

spotify_factored <- spotify |> 
  mutate(across(all_of(factor_columns), as.factor))

spotify_factored |> head()

Perlu dicatat bahwa kolom seperti artist_name, track_name, track_id bukanlah sebuah kategori meski bertipe character. Hal tersebut dikarenakan nilainya sangat unik untuk setiap observasi. Untuk pemeriksaan melalui kode, bisa diperiksa setiap kolom dengan unique() |> length(). Tapi karena ini dataset yang cukup familiar dan bisa ditebak dengan nalar, proses yang dilakukan manual. Setelah menentukan kolom kategori dilanjutkan ke kolom berupa angka/numerik.

Angka/Numerik

Menggunakan is.numeric, dibuat variabel baru bernama numeric_columns yang berisikan nama-nama kolom yang berupa angka. Berikut variabel numeric_columns:

numeric_columns <- spotify |> select(where(is.numeric)) |> colnames()
numeric_columns
##  [1] "popularity"       "acousticness"     "danceability"     "duration_ms"     
##  [5] "energy"           "instrumentalness" "liveness"         "loudness"        
##  [9] "speechiness"      "tempo"            "valence"

Setelah selesai dengan mengubah dataset menjadi tipe data yang seharusnya, bisa dilanjutkan dengan mengecek kelengkapan datanya.

Kelengkapan Data

Pada langkah ini akan diperiksa kelengkapan data. Kelengkapan data di cek dengan anyNA. Berikut hasilnya:

anyNA(spotify_factored)
## [1] FALSE

Diperoleh FALSE, maka data spotify tidak ada memiliki nilai yang kosong. Dan bisa dilanjutkan ke proses berikutnya, yaitu mengeksplorasi dataset.

Eksplorasi Dataset

Hasil dari kelengkapan data, digunakan untuk eksplorasi dataset. Diperiksa dimensi (observasi dan variabel/baris dan kolom) dengan dim():

dim(spotify_factored)
## [1] 232725     18

Diketahui terdapat \(232,725\) baris dan \(18\) kolom, yang masing-masing menunjukkan observasi dan variabel. Selanjutnya dilihat ringkasan dataset spotify_factored. Pertama, dilihat ringkasan untuk kolom berkategori:

spotify_factored |> 
  select(all_of(factor_columns)) |> 
  summary()
##         genre             key           mode        time_signature
##  Comedy    :  9681   C      :27583   Major:151744   0/4:     8    
##  Soundtrack:  9646   G      :26390   Minor: 80981   1/4:  2608    
##  Indie     :  9543   D      :24077                  3/4: 24111    
##  Jazz      :  9441   C#     :23201                  4/4:200760    
##  Pop       :  9386   A      :22671                  5/4:  5238    
##  Electronic:  9377   F      :20279                                
##  (Other)   :175651   (Other):88524

Dari summary() diatas, bisa sedikit diterka bahwa genre dan key lumayan merata (hampir 5 kategori terbanyak memiliki jumlah yang tidak jauh berbeda). Akan tetapi, hal tersebut masih perlu diinvestigasi lebih lanjut. Diketahui juga dari ringkasan diatas, pada time_signature, tanda birama \(\frac{4}{4}\) lebih banyak dan \(\frac{0}{4}\) sangat sedikit dan bisa dihapus (untuk sementara tetap digunakan.

Selanjutnya melihat ringkasan untuk setiap kolom numerik dengan cara yang serupa:

spotify_factored |> 
  select(all_of(numeric_columns)) |> 
  summary()
##    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

Dari penjelasan tiap kolom di halaman bantuan spotify, bisa diketahui juga batasan nilai setiap kolom. Tapi tidak semua nilai numerik ini bisa merepresentasikan karakteristik musik. Semisal informasi duration_ms bukanlah karakteristik dari musik sendiri. Sehingga, masih bisa untuk dieksplorasi lebih lanjut terkait dataset numerik. Karakteristik musik disini diasumsikan sebagai suatu nilai yang bisa merepresentasikan/merefleksikan musik dengan range nilai \([0, 1]\) (seperti energy, instrumentalness, liveness, dll).

Kolom Numerik

Sebagai langkah percobaan, kolom numerik yang memiliki range \([0, 1]\) dipisahkan dengan kolom numerik yang diluar tersebut. Hal tersebut, dikarenakan karakteristik musik/lagu, biasanya memiliki range \([0,1]\). Nama kolom yang memiliki range tersebut disimpan sebagai variabel num_compare_columns.

num_compare_columns <- spotify_factored |>
  select(all_of(numeric_columns)) |> 
  select_if(function(.) max(.) <= 1) |> 
  colnames()
num_compare_columns
## [1] "acousticness"     "danceability"     "energy"           "instrumentalness"
## [5] "liveness"         "speechiness"      "valence"

Karena diketahui dataset memiliki lebih dari \(200,000\) (dua ratus ribu) baris, maka digunakan random sampling sebanyak \(1,000\) baris dengan asumsinya \(1,000\) baris tersebut mampu mewakili dataset. (Saat melakukan random sampling seperti ini bisa juga menggunakan teknik lain. Akan tetapi, dengan waktu yang terbatas, untuk sementara digunakan sampling yang sederhana menggunakan slice_sample())

spotify_sample_eda <- spotify_factored |>
  slice_sample(n = 1000)
spotify_sample_eda |> head()

Setelah melakukan sampling, data bisa divisualisasikan untuk melihat seperti apa data yang diperoleh dan hubungannya antar variabel.

Boxplot / Distribusi

Salah satu visualisasi yang bisa digunakan untuk kolom num_compare_columns yaitu boxplot. Berikut boxplot untuk kolom yang berupak karakteristik musik.

spotify_sample_eda |> select(all_of(num_compare_columns)) |>
  stack() |> 
  ggplot(aes(x = ind, y = values)) +
  geom_boxplot() +
  labs(
    title = "Boxplot Karakteristik",
    x = "Karakteristik",
    y = "Nilai"
  ) +
  theme_bw() +
  coord_flip()
Boxplot Karakteristik Musik

Boxplot Karakteristik Musik

Dari boxplot sederhana diatas, bisa menduga seperti apa distribusi untuk setiap kolom numerik pada range \([0, 1]\). Dilihat dari instrumentalness, bisa diduga bahwa dataset yang diperoleh ini cenderung sebuah lagu (musik yang diiringi vokal). Tapi bisa dilihat juga memiliki beberapa data yang berpotensi outlier yang dapat diartikan sebagai di dataset terdapat beberapa track yang memiliki musik instrumental. Interpretasi tersebut bisa juga diterapkan pada kolom speechiness yang mengartikan 0 sebagai bukan spoken track, sedangkan 1 menunjukkan track tersebut berupa spoken track seperti talk show atau podcast1.

Correlogram

Correlogram merupakan visualisasi untuk melihat korelasi antar kolom. Korelasi ini bisa mengidentifikasi apakah terjadi multicollinearity atau melihat hubungan korelasi positif/negatif terhadap setiap variabel yang ada di dataset.

Matriks Korelasi

Matriks korelasi dibangkitkan menggunakan ggcorr().

ggcorr(spotify_sample_eda |> select(all_of(numeric_columns)),
       method = "pairwise", geom = "tile", 
       label = TRUE, label_size = 3, label_alpha = TRUE,
       hjust = 0.9, layout.exp = 2,
       nbreaks = 7,
       low = "#2980B9", mid = "#ECF0F1", high = "#2C3E50") +
  labs(title = "Matriks Korelasi")
Matriks Korelasi Kolom Numerik

Matriks Korelasi Kolom Numerik

Dari hasil diatas, yang paling terlihat jelas hubungannya adalah energy dan loudness yang berkorelasi positif. Terdapat juga yang berkorelasi negatif seperti energy dan acousticness.


Karakteristik Musik

Karakteristik musik didefinisikan sebelumnya sebagai kolom yang termasuk num_compare_columns (yang memiliki nilai pada range \([0, 1]\)). Dari grafik sebelumnya, bisa juga divisualisasikan lebih menarik lagi dengan ggpairs(). Berikut hasilnya pada kolom karakteristik musik.

ggpairs(spotify_sample_eda |> select(all_of(num_compare_columns)),
        title = "Correlogram Antar Karakteristik Musik")
Hubungan antara karakteristik musik

Hubungan antara karakteristik musik

Nilai korelasi dari grafik diatas sama saja dengan grafik sebelumnya yang membedakan hanya pembulatannya saja.


Deskripsi Musik

Berikut korelasi untuk kolom numerik yang selain dari karakteristik musik.

ggpairs(spotify_sample_eda |> select(all_of(numeric_columns)) |> select(-all_of(num_compare_columns)),
        title = "Correlogram Antar Deskripsi Musik")
Korelasi Deskripsi Musik

Korelasi Deskripsi Musik

Setelah selesai mengeksplorasi untuk data numerik, dilanjutkan untuk data kategori.


Kolom Kategori

Pada bagian ini divisualisasikan dan dieksplorasi mengenai kolom yang berupa kategori. Berikut kolom yang berupa kategori (dalam variabel factor_columns).

factor_columns
## [1] "genre"          "key"            "mode"           "time_signature"

Karena dari summary() sebelumnya diketahui bahwa genre memiliki kategori yang banyak. Sebagai memperjelas, disimpan genre musik tersebut sebagai variabel genre_music kemudian dicek jumlah genre yang tersedia di dataset.

genre_music <- spotify_factored$genre |> levels()
length(genre_music)
## [1] 27

Terdapat \(27\) kategori musik. Untuk data tipe kategori juga bisa divisualisasikan dengan melihat jumlahnya setiap observasi, baik dari kolom tunggal maupun lebih. Jumlah penghitungan disimpan dalam objek spotify_factored_count yang dibuat menggunakan count(). Berikut sampel 10 baris dari kategorinya.

spotify_factored_count <- spotify_factored |> select(all_of(factor_columns)) |> 
  count(genre, key, mode, time_signature)
spotify_factored_count |> slice_sample(n = 10)

Selain itu, visualisasinya bisa berupa barplot dengan berbagai konfigurasi. Berikut beberapa konfigurasi visualisasi untuk kolom kategori.

Visualisasi Data Kategori
Genre

Visualisasi ini untuk melihat seberapa banyak musik yang dikelompokkan berdasarkan genre.

spotify_factored_count |> group_by(genre) |> summarise(freq = sum(n)) |>
  ggplot(aes(genre, freq)) +
  geom_col(aes(fill = freq), show.legend = FALSE) + theme_bw() + coord_flip() +
  labs(title = "Banyaknya musik untuk setiap genre", y = 'Frekuensi')
Jumlah musik untuk setiap genre

Jumlah musik untuk setiap genre

Dari grafik diatas diketahui bahwa musik berjenis Children’s Music dan A Capella cukup sedikit dibandingkan jenis lainnya. Dari informasi ini, bisa dilakukan tindakan untuk mengabaikan jenis musik tersebut, tapi untuk sementara jenis musik tersebut akan dilibatkan dalam proses analisis ataupun pemodelan.

Kekeliruan pada pembacaan dataset
Dilihat terdapat dua kategori yang namanya sama yaitu Children’s Music. Seharusnya kedua kategori itu digabungkan menjadi satu atau salah satunya dihapus dari dataset. Untuk proses berikutnya, diasumsikan Children’s Music memang memiliki dua kategori terpisah. Karena jika digabung kategori tersebut jadi lebih banyak dibandingkan kategori lainnya.

Key

Berikut jumlah musik yang digunakan untuk setiap kunci key.

spotify_factored_count |> group_by(key) |> summarise(freq = sum(n)) |>
  ggplot(aes(key, freq)) +
  geom_col(aes(fill = freq), show.legend = FALSE) + theme_bw() +
  labs(title = "Banyaknya musik untuk setiap key", y = "Frekuensi")
Jumlah musik untuk setiap key

Jumlah musik untuk setiap key

Dari gambar diatas, diketahui kunci D# jarang digunakan. Dan yang paling populer digunakan adalah kunci C.


Mode

Visualisasi ini berdasarkan mode.

spotify_factored_count |> group_by(mode) |> summarise(freq = sum(n)) |>
  ggplot(aes(mode, freq)) +
  geom_col(aes(fill = freq), show.legend = FALSE) + theme_bw() +
  labs(title = "Banyaknya musik untuk setiap mode", y = "Frekuensi")
Jumlah musik untuk setiap mode

Jumlah musik untuk setiap mode

Kebanyakan musik menggunakan mayor.


Time Signature

Visualisasi ini berdasarkan tanda birama.

spotify_factored_count |> group_by(time_signature) |> summarise(freq = sum(n)) |>
  ggplot(aes(time_signature, freq)) +
  geom_col(aes(fill = freq), show.legend = FALSE) + theme_bw() +
  labs(title = "Banyaknya musik untuk setiap tanda birama", y = "Frekuensi")
Jumlah musik untuk setiap time_signature

Jumlah musik untuk setiap time_signature

Dari sini juga diperoleh tanda birama \(\frac{4}{4}\) lebih banyak digunakan.


Distribusi Genre Pop

Visualisasi ini meringkas semua tipe kategori berdasarkan genre. genre yang digunakan sebagai contoh adalah Pop dan Classical. Berikut visualisasi untuk genre Pop.

# genre_i <- 7
genre_picked <- "Pop"
spotify_factored_count |> 
  filter(genre == genre_picked) |> 
  ggplot(aes(x = key, y = n, fill = mode)) +
  geom_bar(position = "dodge", stat = "identity") +
  facet_wrap(~time_signature, ncol = 3) +
  labs(
    title = paste("Musik", genre_picked),
    x = "Key",
    y = "Frekuensi"
  ) +
  theme_bw()
Distribusi di musik Pop

Distribusi di musik Pop

Banyak yang dapat diceritakan terkait grafik diatas. Untuk mempersingkat, dapat diketahui bahwa musik Pop sering menggunakan tanda birama \(\frac{4}{4}\), dengan penggunaan mode Major tertinggi di kunci C.


Distribusi Genre Classical

Berikut visualisasi yang serupa tapi pada genre Classical.

genre_picked <- "Classical"
spotify_factored_count |> 
  filter(genre == genre_picked) |> 
  ggplot(aes(x = key, y = n, fill = mode)) +
  geom_bar(position = "dodge", stat = "identity") +
  facet_wrap(~time_signature, ncol = 2) +
  labs(
    title = paste("Musik", genre_picked),
    x = "Key",
    y = "Frekuensi"
  ) +
  theme_bw()
Distribusi di musik Classical

Distribusi di musik Classical

Berbeda dengan pop, untuk musik Classical tidak didominasi oleh tanda birama \(\frac{4}{4}\). Dan pada tanda birama lain dapat terlihat meski jumlahnya tidak sebanyak \(\frac{4}{4}\). Dan di mode Major, ada tiga kunci yang populer yaitu C, D, G.


Kesimpulan EDA

Dari eksplorasi diatas, dapat beberapa informasi mengenai dataset. Hanya saja, fokus untuk LBB ini yaitu pemodelan menggunakan PCA dan K-means. Sehingga, sebagai catatan saja, eksplorasi dataset masih bisa di jelajah lebih lanjut sebelum menyentuh pemodelan.


R2 - Pemilihan Parameter

Pada bab ini akan ditentukan pemilihan parameter untuk PCA dan K-means. Dataset yang telah diolah dan dieksplorasi sebelumnya disimpan sebagai objek baru bernama spotify_r2. Dan berikut datasetnya secara sekilas.

spotify_r2 <- spotify_factored
spotify_r2 |> glimpse()
## Rows: 232,725
## Columns: 18
## $ genre            <fct> Movie, Movie, Movie, Movie, Movie, Movie, Movie, Movi…
## $ 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       <int> 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      <int> 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              <fct> C#, F#, C, C#, F, C#, C#, F#, C, G, E, C, F#, D#, 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             <fct> Major, Minor, Minor, Major, Major, Major, Major, Majo…
## $ 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   <fct> 4/4, 4/4, 5/4, 4/4, 4/4, 4/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…

Sebelumnya sudah dibahas mana saja kolom yang berupa numerik dan kategori. Karena PCA dan K-means fokus pada yang bersifat numerik. Maka data yang digunakan nanti sebagai input adalah kolom numerik. Sekedar mengingat saja, berikut variabel kolom-kolom yang menunjukkan jenis kolomnya.

info_columns <- c("artist_name", "track_name", "track_id")
# info_columns
# factor_columns
# numeric_columns
# num_compare_columns

sq <- seq(length(numeric_columns))
data.frame(info_columns[sq], factor_columns[sq], numeric_columns[sq], num_compare_columns[sq])

Dimensionality Reduction

Tahap ini dilakukan pertama untuk melihat karakteristik musik dan yang lainnya bisa diekspresikan sebagai Principal Component. Berikut dataset spotify_r2 secara sekilas.

glimpse(spotify_r2)
## Rows: 232,725
## Columns: 18
## $ genre            <fct> Movie, Movie, Movie, Movie, Movie, Movie, Movie, Movi…
## $ 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       <int> 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      <int> 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              <fct> C#, F#, C, C#, F, C#, C#, F#, C, G, E, C, F#, D#, 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             <fct> Major, Minor, Minor, Major, Major, Major, Major, Majo…
## $ 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   <fct> 4/4, 4/4, 5/4, 4/4, 4/4, 4/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…

Karena untuk PCA hanya menggunakan kolom yang berupa angka, dipilih kolom yang numerik saja. Dan disimpan sebagai objek spotify_pca.

spotify_pca <- spotify_r2 |> select(all_of(numeric_columns))
spotify_pca |> glimpse()
## Rows: 232,725
## Columns: 11
## $ popularity       <int> 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      <int> 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…

Dilakukan normalisasi menggunakan scale().

spotify_pca_scaled <- spotify_pca |> scale() |> as.data.frame()
spotify_pca_scaled |> glimpse()
## Rows: 232,725
## Columns: 11
## $ popularity       <dbl> -2.261002, -2.206026, -2.096075, -2.261002, -2.041100…
## $ acousticness     <dbl> 0.68337483, -0.34546644, 1.64456626, 0.94269920, 1.63…
## $ danceability     <dbl> -0.8909329, 0.1919933, 0.5852948, -1.6936990, -1.2034…
## $ duration_ms      <dbl> -1.14136546, -0.82186566, -0.54529654, -0.69529329, -…
## $ energy           <dbl> 1.2869052, 0.6302479, -1.6699502, -0.9297874, -1.3131…
## $ instrumentalness <dbl> -0.48981747, -0.48981747, -0.48981747, -0.48981747, -…
## $ liveness         <dbl> 0.66065975, -0.32283477, -0.56492573, -0.58762176, -0…
## $ loudness         <dbl> 1.2907007, 0.6686811, -0.7184009, -0.4348159, -1.9305…
## $ speechiness      <dbl> -0.3679692, -0.1830817, -0.4558311, -0.4380431, -0.40…
## $ tempo            <dbl> 1.59560388, 1.82324947, -0.58832454, 1.75059318, 0.74…
## $ valence          <dbl> 1.38074126, 1.38843163, -0.33421142, -0.87638256, -0.…

Setelah selesai memilih kolom mana saja yang akan digunakan, dilakukan PCA menggunakan PCA(). Berikut ringkasan dari PCA.

pca_spotify <- PCA(spotify_pca_scaled, 
                   scale.unit=FALSE, graph = FALSE, ncp = length(numeric_columns))
pca_spotify |> summary()
## 
## Call:
## PCA(X = spotify_pca_scaled, scale.unit = FALSE, ncp = length(numeric_columns),  
##      graph = FALSE) 
## 
## 
## 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 |

Setelah melakukan PCA, baru dievaluasi setiap komponen yang ada di PCA.

Kontribusi pada setiap PC

Untuk melihat kontribusi setiap kolom/variabel terhadap Principal Component, dapat diketahui langsung dengan mengambil nilai contrib dari pca_spotify$var.

pca_var_contrib <- pca_spotify$var$contrib |> as.data.frame()
pca_var_contrib

Sebelum memasuki secara visual, bisa juga dilihat menggunakan tabel. Setelah menyimpan nilai contrib dari PCA, bisa dilihat kontribusinya dengan mengurutkannya untuk komponen yang dipilih. Berikut contoh melihat kontribusi terbesar pada PC1.

pca_var_contrib |> 
  arrange(desc(Dim.1))

Diketahui bahwa loudness, energy, acousticness merupakan tiga variabel yang paling berpengaruh pada PC1. Dari sini juga bisa dilihat nilai eigen dari PCA dengan get_eig().

pca_eig <-  get_eig(pca_spotify) |> as.data.frame()
pca_eig

Jika diinginkan untuk mempertahankan informasi dari dataset aslinya sebesar \(80\%\), bisa diambil berdasarkan nilai cumulative variance. Berikut dimensi yang setidaknya memiliki cumulative variance 80%.

pca_eig |>
  mutate(cumul = round(cumulative.variance.percent,-1)) |> 
  filter(cumul < 90) |> 
  select(-cumul)

Disini 6 komponen bisa mewakili hampir 80% informasi yang tersedia dari kolom numerik. Eksplorasi diatas juga bisa dipermudah dengan dibuatkan grafik menggunakan fviz_screeplot().

fviz_screeplot(pca_spotify, addlabels = TRUE) + theme_bw()
Screeplot PCA

Screeplot PCA

Dari scree plot diatas sama saja dengan pernyataan sebelumnya menggunakan tabel. Berikut visualisasi pengaruh tiap variabel/kolom yang ada di dataset terhadap komponen PC1 dan PC2.

plot.PCA(pca_spotify, choix = "var")
Grafik PCA variabel terhadap 2 PC

Grafik PCA variabel terhadap 2 PC

Berdasarkan pernyataan sebelumnya, pada PC1 tiga terbesar kontribusinya yaitu loudness, energy, acousticness. Tapi dengan melihat grafik diatas, bisa dilihat korelasi antara komponen tersebut negatif ataupun positif. Untuk melihat besarnya korelasi bisa diakses di $var$corr

pca_spotify$var$cor |> as.data.frame()

Terdapat fungsi bernama fviz_contrib() untuk melihat kontribusinya, seperti scree plot diatas. Berikut untuk komponen pertama PC1 axes = 1.

fviz_contrib(pca_spotify, choice = "var", axes = 1, 
             ggtheme = theme_bw())
Grafik kontribusi setiap variabel di PC1

Grafik kontribusi setiap variabel di PC1

Dari grafik diatas diketahui yang bisa dibilang cukup mempengaruhi yaitu \(6\) variabel yaitu loudness, energy, acousticness, danceability, valence, dan instrumentalness. Akan tetapi yang terlihat jelas dan dominan adalah tiga pertama. Berikut untuk PC2 axes = 2.

fviz_contrib(pca_spotify, choice = "var", axes = 2, top = 10, 
             ggtheme = theme_bw())
Grafik kontribusi setiap variabel di PC2

Grafik kontribusi setiap variabel di PC2

Berbeda dengan PC1, disini yang dianggap signifikan hanya speechiness dan liveness.

Evaluasi ini bisa dilanjutkan hingga menemukan seluruh PC yang mewakili signifikannya terhadap semua variabel. Dan tergantung kebutuhan juga, penggunaan PC untuk analisis/pemodelan berikutnya bisa ditentukan tergantung tujuan analisis. Misal, saya tidak perlu PC yang variabel speechiness dan liveness yang signifikan. Mungkin saja, diinginkan popularity yang signifikan. Bisa dieksplorasi dengan melihat tabel contrib tadi.

pca_var_contrib |> 
  rownames_to_column(var = 'indicator') |> 
  filter(indicator %in% "popularity") |>
  column_to_rownames("indicator") |> t() |> as.data.frame() |> 
  arrange(desc(popularity)) |> head()

Dari informasi diatas diketahui bahwa PC5, PC7, dan PC4 merupakan komponen dimana popularity memiliki pengaruh yang besar dengan PC5 yang terbesar. Berikut dievaluasi kontribusi di PC5.

fviz_contrib(pca_spotify, choice = "var", axes = 5, top = 10, 
             ggtheme = theme_bw())
Grafik kontribusi setiap variabel di PC5

Grafik kontribusi setiap variabel di PC5

Disini diperoleh bahwa popularity di PC5 lebih tinggi daripada PC yang lain, tapi tidak lebih signifikan daripada duration_ms dan valance. Dari sini, bisa diduga bahwa variabel popularity tidak begitu berpengaruh di dataset kita. Selain popularity muncul tertingginya mulai dari di PC5 (yang informasinya sudah tergolong kecil), popularity juga bukan yang mendominasi di PC tersebut (PC5).

Dari sini akan dilanjutkan dengan informasi PC1 dan PC2 untuk Kmeans.

K-Means

Dengan dataset yang sama, diinginkan untuk dilakukan Kmeans dan membandingkannya antara dengan pengelompokkan musik dengan klusternya. Oleh karena itu, informasi dan data yang telah dirangkum oleh PCA akan digunakan di bagian K-Means. Yang perlu dicatat adalah pada bagian ini hanya fokus menggunakan PC1 dan PC2. Informasi koordinat dari PCA untuk PC1 dan PC2 ditambahkan ke dataset dan disimpan kedalam objek spotify_km. Berikut beberapa baris pertamanya (PC1 dan PC2 disimpan di kolom terakhir):

spotify_km <- spotify_r2
pca_spotify_ind <- get_pca_ind(pca_spotify)
spotify_km$PC1 <- pca_spotify_ind$coord[,"Dim.1"]
spotify_km$PC2 <- pca_spotify_ind$coord[,"Dim.2"]
spotify_km |> head()

Karena untuk K-Means ini dieksekusi dan diplotkan setiap individual observasinya. Sehingga untuk menampikan lebih dari \(200,000\) baris itu sangat memberatkan dan menyulitkan untuk dibaca. Oleh karena itu dilakukan random sampling seperti pada bagian awal. Hanya saja pengambilannya berdasarkan genre dan diambil sebanyak \(100\) data untuk setiap genre. Pengambilan sebanyak \(100\) dikarenakan pada genre A Capella tidak memiliki observasi lebih dari \(100\). Berikut 6 baris data yang diambil secara acak setelah menyimpan hasil sampling di objek spotify_km_sample:

set.seed(41908481)
spotify_km_sample <- spotify_km |> 
  group_by(genre) |> 
  slice_sample(n = 100) |> # sample ini bisa diperbesar jika mengabaikan A Capella
  ungroup()
spotify_km_sample |> slice_sample(n = 6)

Dan berikut dimensi untuk sample.

dim(spotify_km_sample)
## [1] 2700   20

Karena objek spotify_km_sample menyimpan seluruh kolom seperti original dataset, harus dibuat objek baru lagi yang hanya melihat dua variabel (atau lebih) yang akan dievaluasi menggunakan K-means. Karena fokus di LBB ini hanya dua variabel/komponen yaitu PC1 dan PC2. Objek input yaitu spotify_km_sample_input dengan 6 baris sampel sebagai berikut:

spotify_km_sample_input <- spotify_km_sample |> select(PC1, PC2)
spotify_km_sample_input |> slice_sample(n = 10)

Berikut visualisasinya dataset yang digunakan untuk input K-Means.

spotify_km_sample_input |> ggplot(aes(PC1, PC2)) +
  geom_point() +
  theme_bw()
Grafik PC1 vs PC2 untuk analisis K-Means

Grafik PC1 vs PC2 untuk analisis K-Means

K-Optimum

Dalam mencari nilai K Optimum, bisa dibantu dengan fungsi fviz_nbclust(). Berikut mencari optimum berdasarkan WSS (Weight Sum of Square).

fviz_nbclust(spotify_km_sample_input, kmeans, method = "wss")
Mencari nilai K optimal dengan WSS

Mencari nilai K optimal dengan WSS

Dari grafik diatas, menggunakan elbow method, bisa diduga jumlah kluster yang tepat itu 3. Untuk meastikan juga bisa menggunakan method silhouette di fviz_nbclust.

fviz_nbclust(spotify_km_sample_input, kmeans, method = "silhouette")
Mencari nilai K optimal dengan Silhouette

Mencari nilai K optimal dengan Silhouette

Dari dua grafik diatas disimpulkan menggunakan \(K = 3\) sebagai jumlah kluster yang digunakan pada pemodelan K-Means.

Model

K-Means dilakukan menggunakan fungsi kmeans() yang hasilnya disimpan pada objek km_spotify. Berikut hasil dari km_spotify.

set.seed(41608481)
km_spotify <- kmeans(spotify_km_sample_input, 3)
km_spotify
## K-means clustering with 3 clusters of sizes 688, 130, 1882
## 
## Cluster means:
##          PC1        PC2
## 1 -2.8301908 -0.2355595
## 2 -0.4910752  4.3917534
## 3  0.9703514 -0.2237285
## 
## Clustering vector:
##    [1] 1 3 1 1 1 1 1 1 3 1 3 1 1 3 1 3 3 1 1 1 3 1 1 1 3 1 1 1 1 1 1 1 3 3 1 1 1
##   [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 1 3 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 1 3 1 1 1
##   [75] 1 1 1 3 3 1 1 1 1 1 3 1 1 1 1 1 3 3 1 3 3 3 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3
##  [112] 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [149] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [186] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 3 3 1 3 3 3 1 3 3 3 3 3 3 3 3 3 1 1 1
##  [223] 3 1 3 3 3 3 3 3 3 3 1 3 1 3 1 3 1 3 1 3 3 3 3 1 1 3 3 3 3 1 3 3 1 3 3 1 3
##  [260] 3 3 1 3 3 3 3 1 1 1 3 3 3 1 3 3 3 3 3 3 1 3 1 3 3 3 1 1 3 3 3 3 3 1 3 3 3
##  [297] 3 3 3 3 3 3 3 3 1 3 3 1 3 2 3 2 3 3 3 3 3 3 3 2 3 3 3 1 3 3 3 3 3 3 3 3 3
##  [334] 3 3 3 3 3 3 3 3 1 3 1 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3
##  [371] 3 2 3 3 3 2 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 2 3 3 3 3 1 3 1
##  [408] 1 3 2 3 3 3 3 1 3 3 1 3 3 3 3 3 3 1 1 3 3 3 3 1 2 3 3 1 3 1 3 3 1 1 3 3 1
##  [445] 3 1 2 3 3 3 1 1 3 1 1 3 2 3 3 3 1 2 1 1 3 1 3 3 3 1 1 3 1 3 3 3 1 3 3 1 3
##  [482] 1 3 3 1 1 3 3 2 3 3 1 2 3 3 1 1 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [519] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [556] 3 3 3 3 3 1 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [593] 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [630] 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 1 1 1 1 1
##  [667] 1 1 3 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 3 1 2 2 2
##  [704] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 1 2 2
##  [741] 2 3 2 2 2 2 2 2 2 2 2 2 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2
##  [778] 2 2 2 2 2 2 2 2 2 2 2 2 3 2 2 2 2 2 3 2 2 2 1 3 3 3 3 3 1 1 3 3 3 1 3 3 3
##  [815] 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3
##  [852] 3 3 3 3 3 3 1 3 3 3 3 3 3 1 3 3 3 3 1 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1
##  [889] 3 3 3 3 3 1 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [926] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [963] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1000] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1037] 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 1 3 3 3 3 3 1 3 3 3 1 3 3
## [1074] 3 1 3 3 3 3 3 3 3 1 1 1 3 3 3 3 3 3 3 3 3 3 1 3 3 2 3 3 1 3 3 1 1 3 3 1 1
## [1111] 3 3 3 1 1 3 3 3 1 1 1 2 3 3 1 3 3 1 3 3 1 3 1 3 3 3 1 3 3 3 3 3 3 3 3 1 3
## [1148] 1 3 1 3 3 3 1 3 3 3 1 3 3 3 1 1 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 1 3 3 3 3 1
## [1185] 3 3 1 3 3 3 3 3 3 1 3 1 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1222] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1259] 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1296] 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 1 1 3 3 3 3 3 1 1 3 3 1 3 3 3 3 3 3 3 3 3
## [1333] 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 1 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 1
## [1370] 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 1 3 3 3 3 1 3 1 3 3 1 3 3 3 1 3 3 3 3 1
## [1407] 3 3 3 1 3 3 1 3 3 3 3 3 3 3 3 1 3 1 1 3 3 1 3 3 3 1 3 3 1 1 3 1 1 1 1 3 1
## [1444] 3 3 3 1 3 3 1 3 1 1 1 3 3 1 3 3 3 3 1 1 3 3 1 3 3 1 3 1 1 3 3 3 3 1 3 1 3
## [1481] 3 3 3 1 3 3 3 1 1 3 3 3 1 3 1 3 1 3 1 3 1 1 3 1 1 1 1 1 3 1 2 1 1 3 1 1 3
## [1518] 1 3 3 1 1 1 1 2 1 3 3 1 2 1 3 3 2 2 1 1 3 3 3 2 3 2 1 1 1 1 1 1 1 1 3 2 2
## [1555] 1 1 3 1 1 1 2 2 1 3 1 1 3 1 1 1 1 1 3 1 3 1 1 1 3 1 1 3 3 1 2 1 2 1 1 1 1
## [1592] 1 1 1 1 1 3 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [1629] 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [1666] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3
## [1703] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1740] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1777] 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3
## [1814] 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 1 3 3 3 1 1 3 1 3 3 3 3
## [1851] 3 3 1 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 1 1 3 3 3 3 3
## [1888] 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1925] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1962] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [1999] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [2036] 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [2073] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3 3 3 3
## [2110] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3
## [2147] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [2184] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 1 1 3 3
## [2221] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 1 3 3 3 3 3 3 3 3 3
## [2258] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [2295] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [2332] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2
## [2369] 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1
## [2406] 3 3 1 3 3 3 3 1 1 3 3 3 1 3 1 3 3 3 3 3 1 3 1 3 3 3 3 1 3 1 3 1 3 3 1 3 3
## [2443] 3 1 3 3 3 3 3 3 3 1 3 1 3 3 3 1 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3
## [2480] 3 1 3 3 3 3 3 3 1 3 1 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [2517] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [2554] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [2591] 1 1 1 1 1 1 1 1 1 1 3 1 3 2 3 3 1 3 3 3 3 1 1 1 3 3 1 3 1 1 3 1 3 1 1 3 1
## [2628] 3 1 3 1 1 1 3 1 1 3 3 3 3 3 3 1 3 1 3 3 3 3 1 3 3 3 3 1 3 3 1 3 3 3 1 1 1
## [2665] 3 1 3 3 3 3 3 3 3 3 1 3 3 1 3 3 3 3 3 3 3 3 3 1 3 1 3 3 3 3 3 1 1 3 3 3
## 
## Within cluster sum of squares by cluster:
## [1] 1568.3207  320.3051 2266.4622
##  (between_SS / total_SS =  70.5 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Hasil $cluster diatas digabungkan di spotify_km_sample untuk visualisasi dan memperoleh insight yang bisa ditemukan. Berikut baris awal dari spotify_km_sample dengan kolom cluster.

spotify_km_sample$cluster <- km_spotify$cluster
spotify_km_sample |> head()

Dari grafik antara PC1 vs PC2 juga bisa divisualisasikan dengan menampilkan hasil cluster dengan bantuan fungsi fviz_cluster().

fviz_cluster(km_spotify, data = spotify_km_sample_input,
             repel = TRUE, ellipse = TRUE, ellipse.type = "norm", ellipse.alpha = 0.1) +
  theme_bw()
Kluster PC1 vs PC2

Kluster PC1 vs PC2

Karena K-Means yang dilakukan hanya dua variabel, rasanya sudah cukup melakukan visualiasasi diatas. Selanjutnya, memperoleh insight dari hasil analisis melalui grafik/visualisasi ataupun tabel/angka.


R3 - Visualisasi Insight

Pada bab ini, dari analisis maupun pemodelan yang dilakukan sebelumnya ingin diperoleh insight atau informasi yang tidak dapat diperoleh sebelumnya melalui eksplorasi dataset.

Kluster untuk setiap genre

Disimpan objek spotify_km_sample ke nama baru yaitu spotify_r3_sample. Berikut beberapa baris pertamanya:

spotify_r3_sample <- spotify_km_sample
spotify_r3_sample |> head()

Setelah memperoleh informasi cluster sebanyak tiga, ingin diketahui jika cluster tersebut dikelompokkan berdasarkan genre. Berikut frekuensi untuk setiap genre dan cluster.

spotify_r3_genre <- table(spotify_r3_sample |> select(genre, cluster)) |> as.data.frame() |> 
  arrange(genre, cluster)
spotify_r3_genre

Untuk memudahkan memperoleh gambaran besarnya, dilakukan visualisasi sebagai berikut:

spotify_r3_genre |> 
  ggplot(aes(x = genre, y = Freq, fill = cluster)) +
  geom_col() +
  labs(
    title = "Klustering untuk setiap genre",
    y = "Frekuensi"
  ) +
  theme_bw() + 
  coord_flip()
Proporsi cluster untuk setiap genre

Proporsi cluster untuk setiap genre

Dari grafik diatas bisa dilihat lebih jelas mengenai pembagian cluster untuk setiap genre. Berikut informasi yang bisa diperoleh dari grafik diatas:

  • Cluster 2, cenderung pada genre Comedy.
  • Cluster 1, cenderung pada genre Soundtrack, Opera, Classical, dan Acapella.
  • Sisanya masuk ke kluster 3.

Berikut jika dalam bentuk dataframe:

spotify_r3_genre |> 
  group_by(genre) |> 
  mutate(prop = Freq / sum(Freq)) |> 
  slice_max(Freq == max(Freq)) |>
  ungroup() |> 
  arrange(cluster)

Dari informasi diatas bisa berhipotesis bahwa bagi yang menyukai genre tertentu, kemungkinan menyukai genre yang lain dalam satu kluster. Tapi pernyataan itu cukup meragukan karena, pada cluster 3, kategorinya melingkupi hampir musik pada umumnya. Sehingga, belum tentu orang yang menyukai genre di cluster 3 menyukai genre lainnya meskipun di satu cluster.

Hal ini bisa diteliti lebih jauh jika melakukan pemecahan / breakdown terhadap komponen-komponen lainnya. Misalkan, dibandingkan melakukan K-Means menggunakan PCA, bisa juga langsung menggunakan variabel aslinya. Atau bisa dilihat karakteristik tiap PC yang digunakan di K-Means, untuk diekstrak lagi informasi terkait variabel mana yang signifikan di komponen tersebut.

Kluster untuk setiap key

Sekarang dilakukan hal yang serupa tetapi pada kolom key.

spotify_r3_key <- table(spotify_r3_sample |> select(key, cluster)) |> as.data.frame() |> 
  arrange(key, cluster)
spotify_r3_key

Dalam bentuk visualisasinya:

spotify_r3_key |> 
  ggplot(aes(x = key, y = Freq, fill = cluster)) +
  geom_col() +
  labs(
    title = "Klustering untuk setiap key",
    y = "Frekuensi"
  ) +
  theme_bw()
Proporsi cluster untuk setiap genre

Proporsi cluster untuk setiap genre

Untuk key sendiri, tidak dapat informasi menarik yang bisa diperoleh. Karena, tidak bisa melihat proporsi yang konsisten antar key. Mengingat dilakukannya random sampling yang mengacu pada genre, informasi diatas sulit untuk diambil kesimpulannya. Sehingga, ide untuk eksplorasi selanjutnya bisa mengacu pada key.

Kesimpulan

Informasi diatas bisa digunakan untuk berbagai hal semisal:

  • Menentukan rekomendasi awal (tanpa input customer) berdasarkan karakteristik musik yang dianalisis. Jadi, jika pengguna senang mendengarkan musik Classical, tidak ada salahnya merekomendasikan dengan musik Opera atau Soundtrack.
  • Mengetahui bahwa genre Comedy tidak dapat ditawarkan berdasarkan kemiripan karakteristik musiknya karena pada cluster tersebut hanya Comedy saja yang dominan.

Berikut yang bisa dilakukan selanjutnya:

  • Memodelkan K-Means tapi dengan variabel asli. Hanya saja harus dilakukan sampling yang tepat agar informasinya tetap relevan ketika dibandingkan dengan dataset secara keseluruhannya.
  • Menganalisis PC yang signifikan, dan dilakukan PCA terkait komponen yang relevan saja. Atau mereduksi dimensi dengan seleksi manual (memilih dengan penalaran dan pemahaman bidangnya).
  • Menghapus kolom/baris yang diduga dapat mempengaruhi analisis. Contohnya, pada eksplorasi awal, diketahui terdapat kategori Children’s Music yang duplikat. Sebaiknya kategori tersebut harus diolah lebih lanjut baik digabungkan atau dihapus. Selain itu, kategori A Capella sangat sedikit sehingga untuk pemodelan hanya terbatas \(100\) sample. Jika kategori tersebut dihapus, sample yang dilakukan bisa melebihi dari \(100\) karena kategori lain memiliki jumlah track yang banyak dari ribuan.

  1. Terdapat informasi lebih detail di referensi API-nya.↩︎