Pendahuluan

Dalam era digital saat ini, industri musik mengalami perubahan yang signifikan. Platform streaming seperti Spotify telah merevolusi cara kita mendengarkan dan menemukan musik. Jutaan lagu dari berbagai genre dan artis tersedia dengan mudah, menghadirkan tantangan baru dalam mengelompokkan dan memahami preferensi musik pengguna.

Klasterisasi musik adalah salah satu metode yang digunakan untuk menganalisis dan mengelompokkan lagu berdasarkan karakteristik musiknya. Salah satu pendekatan yang umum digunakan adalah penggunaan metode KMeans dan teknik reduksi dimensi PCA (Principal Component Analysis).

Metode KMeans adalah algoritma klasterisasi yang populer dalam dunia data mining dan pembelajaran mesin. Metode ini mengelompokkan data ke dalam kelompok-kelompok yang memiliki kemiripan tertentu berdasarkan fitur-fitur yang diberikan. Dalam konteks klasterisasi musik Spotify, fitur-fitur ini dapat meliputi tempo, energi, danceability, valence, dan banyak lagi. Dengan menggunakan metode KMeans, kita dapat mengidentifikasi kelompok-kelompok musik yang memiliki karakteristik yang serupa.

Langkah pertama dalam analisis klasterisasi musik Spotify adalah menerapkan metode KMeans. Kita akan menggunakan fitur-fitur musik yang disediakan oleh Spotify API untuk menggambarkan setiap lagu. Fitur-fitur ini dapat mencakup informasi seperti tempo, durasi, instrumen, dan banyak lagi. Dengan menggunakan metode KMeans, kita dapat mengelompokkan lagu-lagu berdasarkan karakteristik musik mereka. Kelompok-kelompok ini akan membantu kita mengidentifikasi kesamaan dalam preferensi musik dan mengungkapkan pola-pola yang mungkin tersembunyi dalam data musik Spotify.

Setelah melakukan klasterisasi dengan KMeans, langkah selanjutnya adalah menerapkan PCA. PCA akan membantu mengurangi dimensi data musik ke beberapa komponen utama yang paling signifikan. Reduksi dimensi ini berguna untuk menghilangkan kelebihan variabel dan memperoleh representasi yang lebih sederhana namun informatif dari data musik. Dengan menggunakan PCA, kita dapat memvisualisasikan data musik dalam ruang yang lebih rendah dimensi dan mengidentifikasi fitur-fitur yang paling berkontribusi terhadap variasi dalam dataset.

Dengan memahami dan mengelompokkan musik berdasarkan karakteristiknya menggunakan metode KMeans, kemudian mengurangi dimensi data menggunakan PCA, kita dapat membantu pengguna Spotify menemukan lagu-lagu baru yang sesuai dengan preferensi mereka. Analisis klasterisasi musik Spotify dengan metode KMeans dan PCA tidak hanya memberikan wawasan tentang preferensi musik individu, tetapi juga dapat digunakan untuk mengidentifikasi tren dan pola dalam musik secara keseluruhan.

Dalam penelitian ini, kami akan menerapkan metode KMeans dan PCA pada data musik Spotify untuk mengungkapkan struktur dan kelompok musik yang ada. Melalui kombinasi kedua metode ini, kita akan memperoleh pemahaman yang lebih baik tentang preferensi musik pengguna dan karakteristik musik yang saling terkait.

#Data Preparation

Import Library

# Import library
library(tidyverse)
library(FactoMineR)
library(factoextra)
library(plotly)

Import Dataset

df_spotify <- read_csv("SpotifyFeatures.csv")
head(df_spotify)
## # A tibble: 6 × 18
##   genre artist_name     track_name track_id popularity acousticness danceability
##   <chr> <chr>           <chr>      <chr>         <dbl>        <dbl>        <dbl>
## 1 Movie Henri Salvador  C'est bea… 0BRjO6g…          0        0.611        0.389
## 2 Movie Martin & les f… Perdu d'a… 0BjC1Nf…          1        0.246        0.59 
## 3 Movie Joseph Williams Don't Let… 0CoSDzo…          3        0.952        0.663
## 4 Movie Henri Salvador  Dis-moi M… 0Gc6TVm…          0        0.703        0.24 
## 5 Movie Fabien Nataf    Ouverture  0IuslXp…          4        0.95         0.331
## 6 Movie Henri Salvador  Le petit … 0Mf1jKa…          0        0.749        0.578
## # ℹ 11 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>
glimpse(df_spotify)
## 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.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 1.23e-01, 0.0…
## $ 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…
summary(df_spotify)
##     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.   :2.03e-05   Min.   :0.0000000   Length:232725      Min.   :0.00967  
##  1st Qu.:3.85e-01   1st Qu.:0.0000000   Class :character   1st Qu.:0.09740  
##  Median :6.05e-01   Median :0.0000443   Mode  :character   Median :0.12800  
##  Mean   :5.71e-01   Mean   :0.1483012                      Mean   :0.21501  
##  3rd Qu.:7.87e-01   3rd Qu.:0.0358000                      3rd Qu.:0.26400  
##  Max.   :9.99e-01   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
anyNA(df_spotify)
## [1] FALSE

Exploratory Data Analysis

# songs per genre
df_spotify %>% group_by(Genre = genre) %>%
  summarise(No_of_tracks = n()) %>% knitr::kable()
Genre No_of_tracks
A Capella 119
Alternative 9263
Anime 8936
Blues 9023
Children’s Music 5403
Children’s Music 9353
Classical 9256
Comedy 9681
Country 8664
Dance 8701
Electronic 9377
Folk 9299
Hip-Hop 9295
Indie 9543
Jazz 9441
Movie 7806
Opera 8280
Pop 9386
R&B 8992
Rap 9232
Reggae 8771
Reggaeton 8927
Rock 9272
Ska 8874
Soul 9089
Soundtrack 9646
World 9096
spotify_top80 <- 
df_spotify %>% 
  filter(popularity >= 80)

spotify_top80
## # A tibble: 1,239 × 18
##    genre    artist_name track_name track_id popularity acousticness danceability
##    <chr>    <chr>       <chr>      <chr>         <dbl>        <dbl>        <dbl>
##  1 Alterna… Joji        Sanctuary  3xaugmC…         83       0.422         0.552
##  2 Alterna… Joji        SLOW DANC… 0rKtyWc…         81       0.544         0.515
##  3 Alterna… Linkin Park In the End 60a0Rd6…         80       0.0103        0.542
##  4 Dance    Ariana Gra… break up … 4kV4N9D…         99       0.0421        0.726
##  5 Dance    Ariana Gra… 7 rings    14msK75…        100       0.578         0.725
##  6 Dance    Halsey      Without Me 5p7ujcr…         97       0.297         0.752
##  7 Dance    Ariana Gra… needy      1TEL6Ml…         92       0.78          0.647
##  8 Dance    Ariana Gra… NASA       4uTvPEr…         91       0.451         0.747
##  9 Dance    Ariana Gra… thank u, … 2rPE9A1…         95       0.28          0.724
## 10 Dance    Ariana Gra… bloodline  2hloaUo…         91       0.0815        0.758
## # ℹ 1,229 more rows
## # ℹ 11 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>

Terdapat 1239 track musik yang memiliki popularitas diatas 80. mari kita lihat jenis genre apa saja yang populer

popular_genre <- 
spotify_top80 %>%
  group_by(genre) %>% 
  summarise(count = n()) %>% 
  arrange(desc(count))
popular_genre
## # A tibble: 16 × 2
##    genre            count
##    <chr>            <int>
##  1 Pop                462
##  2 Rap                209
##  3 Dance              167
##  4 Hip-Hop            146
##  5 Rock                64
##  6 Reggaeton           61
##  7 R&B                 58
##  8 Indie               28
##  9 Children’s Music    21
## 10 Folk                 7
## 11 Soul                 5
## 12 Alternative          3
## 13 Country              3
## 14 Electronic           3
## 15 Blues                1
## 16 Movie                1
plot_ly(popular_genre, x = ~reorder(genre, -count), y = ~count, type = "bar",
        marker = list(color = "steelblue")) %>%
  layout(title = "Jumlah Lagu Populer Berdasarkan Genre",
         xaxis = list(title = "Genre"),
         yaxis = list(title = "Jumlah Lagu"))
observation <- 
spotify_top80 %>% 
  filter(genre %in% c("Pop", "Rap", "Dance"))

observation
## # A tibble: 838 × 18
##    genre artist_name   track_name  track_id popularity acousticness danceability
##    <chr> <chr>         <chr>       <chr>         <dbl>        <dbl>        <dbl>
##  1 Dance Ariana Grande break up w… 4kV4N9D…         99       0.0421        0.726
##  2 Dance Ariana Grande 7 rings     14msK75…        100       0.578         0.725
##  3 Dance Halsey        Without Me  5p7ujcr…         97       0.297         0.752
##  4 Dance Ariana Grande needy       1TEL6Ml…         92       0.78          0.647
##  5 Dance Ariana Grande NASA        4uTvPEr…         91       0.451         0.747
##  6 Dance Ariana Grande thank u, n… 2rPE9A1…         95       0.28          0.724
##  7 Dance Ariana Grande bloodline   2hloaUo…         91       0.0815        0.758
##  8 Dance Ariana Grande bad idea    5Il6Oe7…         91       0.0268        0.847
##  9 Dance benny blanco  Eastside (… 0d2iYfp…         91       0.555         0.56 
## 10 Dance Ariana Grande fake smile  3wFLWP0…         90       0.329         0.45 
## # ℹ 828 more rows
## # ℹ 11 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>

Our data has 838 rows and 18 column, we want to reduce the dimension without losing information contain in data. We can make Principal Component Analysis to reduce data dimension without losing information. We want to keep 85% of information from our data.

Data Preprocessing

spotify <-  df_spotify %>% select(-c(1,4,8,11,14,17)) %>% filter(popularity > 80) %>% mutate(track_name = as.factor(track_name))
glimpse(spotify)
## Rows: 989
## Columns: 12
## $ artist_name      <chr> "Joji", "Joji", "Ariana Grande", "Ariana Grande", "Ha…
## $ track_name       <fct> "Sanctuary", "SLOW DANCING IN THE DARK", "break up wi…
## $ popularity       <dbl> 83, 81, 99, 100, 97, 92, 91, 95, 91, 91, 91, 90, 93, …
## $ acousticness     <dbl> 0.42200, 0.54400, 0.04210, 0.57800, 0.29700, 0.78000,…
## $ danceability     <dbl> 0.552, 0.515, 0.726, 0.725, 0.752, 0.647, 0.747, 0.72…
## $ energy           <dbl> 0.650, 0.479, 0.554, 0.321, 0.488, 0.309, 0.458, 0.64…
## $ instrumentalness <dbl> 2.75e-04, 5.98e-03, 0.00e+00, 0.00e+00, 9.11e-06, 7.4…
## $ liveness         <dbl> 0.3720, 0.1910, 0.1060, 0.0884, 0.0936, 0.2020, 0.252…
## $ loudness         <dbl> -7.199, -7.458, -5.290, -10.744, -7.050, -7.948, -6.8…
## $ speechiness      <dbl> 0.1280, 0.0261, 0.0917, 0.3230, 0.0705, 0.0366, 0.303…
## $ tempo            <dbl> 167.788, 88.964, 169.999, 70.142, 136.041, 87.045, 75…
## $ valence          <dbl> 0.316, 0.284, 0.335, 0.319, 0.533, 0.195, 0.470, 0.43…
spotify_scaled <- scale(spotify[,-c(1,2)])
hist(spotify_scaled)

Clustering

wss <- function(data, maxCluster = 9) {
    # Initialize within sum of squares
    SSw <- (nrow(data) - 1) * sum(apply(data, 2, var))
    SSw <- vector()
    set.seed(100)
    for (i in 2:maxCluster) {
        SSw[i] <- sum(kmeans(data, centers = i)$withinss)
    }
    plot(1:maxCluster, SSw, type = "o", xlab = "Number of Clusters", ylab = "Within groups sum of squares", pch=19)
}
wss(spotify_scaled)

Model Fitting

set.seed(1000)
spotify_km <- kmeans(spotify_scaled, centers = 7)
spotify_km$size
## [1] 118 111 165 241 135  14 205
spotify_km$centers
##    popularity acousticness danceability     energy instrumentalness   liveness
## 1  1.94159213    0.1507840    0.4788512 -0.1470717      -0.03024675 -0.2058154
## 2 -0.01808653   -0.4666430    0.2256204  0.3575940      -0.04632162  2.0952297
## 3 -0.33474773   -0.5176881    0.7558293 -0.3485679      -0.14598928 -0.1901233
## 4 -0.36047053   -0.1453734    0.1720450  0.7769837      -0.13655910 -0.4142955
## 5 -0.19282994    1.7561217   -0.4712820 -1.2606631      -0.04465374 -0.2498212
## 6 -0.51655074    0.5111424   -0.3988484  0.1176130       7.02789630 -0.4619475
## 7 -0.25234020   -0.4379221   -0.8708099  0.0803176      -0.13001220 -0.1798808
##      loudness speechiness      tempo     valence
## 1  0.17306110 -0.02168820 -0.4642504 -0.06908389
## 2  0.28865453 -0.09310844 -0.0092440  0.16564508
## 3 -0.29179676  1.45612037  0.4470577 -0.02481384
## 4  0.64745198 -0.37822414 -0.2785129  0.91577121
## 5 -1.24573210 -0.27161454 -0.1560615 -0.55877648
## 6 -0.65029565 -0.49504939  0.3075627 -0.15018209
## 7  0.08256902 -0.45178021  0.3215959 -0.72831175
fviz_cluster(spotify_km, data=spotify_scaled)

Principal Component Analysis

spotify_pca <- PCA(spotify_scaled, 
                graph = F, 
                ncp = 5)

plot.PCA(spotify_pca, 
         choix = c("ind"),
         habillage = 1,
         select = "contrib7",
         invisible = "quali",)

plot.PCA(spotify_pca, choix = c("var"))

# include the cluster column into Spotify data
spotify$cluster <- spotify_km$cluster

# subset data to variables we need
spoti_cluster <- spotify %>% group_by(cluster) %>% select(popularity, danceability, speechiness) %>% summarise_all("mean")
## Adding missing grouping variables: `cluster`
spoti_cluster
## # A tibble: 7 × 4
##   cluster popularity danceability speechiness
##     <int>      <dbl>        <dbl>       <dbl>
## 1       1       92.5        0.761      0.122 
## 2       2       84.8        0.729      0.114 
## 3       3       83.6        0.796      0.280 
## 4       4       83.5        0.722      0.0833
## 5       5       84.1        0.641      0.0947
## 6       6       82.9        0.650      0.0707
## 7       7       83.9        0.590      0.0754
spotify %>% filter(cluster == "5") %>% arrange(desc(popularity))
## # A tibble: 135 × 13
##    artist_name   track_name          popularity acousticness danceability energy
##    <chr>         <fct>                    <dbl>        <dbl>        <dbl>  <dbl>
##  1 Ariana Grande needy                       92        0.78         0.647  0.309
##  2 Alec Benjamin Let Me Down Slowly          92        0.74         0.652  0.557
##  3 Ariana Grande needy                       92        0.78         0.647  0.309
##  4 Alec Benjamin Let Me Down Slowly          92        0.74         0.652  0.557
##  5 Dean Lewis    Be Alright                  92        0.697        0.553  0.586
##  6 Lewis Capaldi Someone You Loved           91        0.751        0.501  0.405
##  7 Lady Gaga     Shallow - Radio Ed…         90        0.416        0.575  0.33 
##  8 XXXTENTACION  Jocelyn Flores              90        0.469        0.872  0.391
##  9 XXXTENTACION  Jocelyn Flores              90        0.469        0.872  0.391
## 10 Lady Gaga     Shallow - Radio Ed…         90        0.416        0.575  0.33 
## # ℹ 125 more rows
## # ℹ 7 more variables: instrumentalness <dbl>, liveness <dbl>, loudness <dbl>,
## #   speechiness <dbl>, tempo <dbl>, valence <dbl>, cluster <int>