true
# load libraries
library(dplyr)
library(factoextra)
library(FactoMineR)

Read Data

spotify <- read.csv("SpotifyFeatures.csv")

head(spotify)
#>   ï..genre        artist_name                       track_name
#> 1    Movie     Henri Salvador      C'est beau de faire un Show
#> 2    Movie Martin & les fées Perdu d'avance (par Gad Elmaleh)
#> 3    Movie    Joseph Williams   Don't Let Me Be Lonely Tonight
#> 4    Movie     Henri Salvador   Dis-moi Monsieur Gordon Cooper
#> 5    Movie       Fabien Nataf                        Ouverture
#> 6    Movie     Henri Salvador   Le petit souper aux chandelles
#>                 track_id popularity acousticness danceability duration_ms
#> 1 0BRjO6ga9RKCKjfDqeFgWV          0        0.611        0.389       99373
#> 2 0BjC1NfoEOOusryehmNudP          1        0.246        0.590      137373
#> 3 0CoSDzoNIKCRs124s9uTVy          3        0.952        0.663      170267
#> 4 0Gc6TVm52BwZD07Ki6tIvf          0        0.703        0.240      152427
#> 5 0IuslXpMROHdEPvSl1fTQK          4        0.950        0.331       82625
#> 6 0Mf1jKa8eNAf1a4PwTbizj          0        0.749        0.578      160627
#>   energy instrumentalness key liveness loudness  mode speechiness   tempo
#> 1 0.9100            0.000  C#   0.3460   -1.828 Major      0.0525 166.969
#> 2 0.7370            0.000  F#   0.1510   -5.559 Minor      0.0868 174.003
#> 3 0.1310            0.000   C   0.1030  -13.879 Minor      0.0362  99.488
#> 4 0.3260            0.000  C#   0.0985  -12.178 Major      0.0395 171.758
#> 5 0.2250            0.123   F   0.2020  -21.150 Major      0.0456 140.576
#> 6 0.0948            0.000  C#   0.1070  -14.970 Major      0.1430  87.479
#>   time_signature valence
#> 1            4/4   0.814
#> 2            4/4   0.816
#> 3            5/4   0.368
#> 4            4/4   0.227
#> 5            4/4   0.390
#> 6            4/4   0.358

Berdasarkan data spotify di atas, saya ingin menganalisa fitur audio apa yang paling berpengaruh dalam popularitas sebuah lagu. Pertama-tama langsung melakukan proses EDA.

Exploratory Data Analysis

str(spotify)
#> '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 ...
# Mengetahui apakah terdapat missing value di setiap kolom
colSums(is.na(spotify))
#>         ï..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
# Cek summary dari data spotify
summary(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.   :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
# Cek faktor unik per kolom dari ï..genre, artist_name, key, mode, dan time_signature
length(unique(spotify$ï..genre))
#> [1] 27
length(unique(spotify$artist_name))
#> [1] 14564
length(unique(spotify$key))
#> [1] 12
length(unique(spotify$mode))
#> [1] 2
length(unique(spotify$time_signature))
#> [1] 5

Pre processing Data

Mengecek outlier karena outlier sangat mempengaruhi hasil k-means clustering. Maka dari itu saya akan menghilangkan outlier yang ada pada data spotify. Outlier dapat dideteksi menggunakan biplot dari hasil PCA. Scaling dilakukan agar antar variabel memiliki skala yang tidak jauh berbeda.

# Membuang kolom factor agar seluruh data menjadi kolom numerik
spotify_no_chr <- spotify %>% 
  select(-c("ï..genre", "artist_name", "track_name", "track_id", "key", "mode", "time_signature"))


# Sclaing dengan data spotify numerik
spotify_scale <- scale(spotify_no_chr)

PCA dengan data scale

spotify_scale_pca <-  PCA(spotify_scale,
    scale.unit = TRUE,
    graph = FALSE,
    ncp = 6)
# Ploting PCA
plot.PCA(
  x = spotify_scale_pca,
  choix = "ind", 
  select = "contrib 5"
)

Menghilangkan outliers yang ada pada data spotify

outliers <- c(218541, 210710, 210094,217130)
spotify_clean <- spotify_no_chr[-outliers,]

Clustering

glimpse(spotify_clean)
#> Rows: 232,721
#> 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~

Melakukan scaling dengan data yang sudah tidak memiliki outlier.

spotify_clean_scale <- scale(spotify_clean)

Choose optimum K

Menggunakan elbow method untuk menentukan \(k\) optimum. Fungsi kmeansTunning berikut dibuat untuk menampilkan penurunan nilai WSS dari k-means clustering dengan nilai \(k\) dari 2 sampai maxK.

# fungsi untuk plot elbow method
RNGkind(sample.kind = "Rounding")
kmeansTunning <- function(data, maxK) {
  withinall <- NULL
  total_k <- NULL
  for (i in 2:maxK) {
    set.seed(567)
    temp <- kmeans(data,i)$tot.withinss
    withinall <- append(withinall, temp)
    total_k <- append(total_k,i)
  }
  plot(x = total_k, y = withinall, type = "o", xlab = "Number of Cluster", ylab = "Total within")
}
kmeansTunning(data = spotify_clean_scale, maxK = 20)

Berdasarkan hasil kmeansTunning di atas, saya menentukan \(k\) optimum 15 yang menurut saya paling cocok untuk digunakan sebagai center dalam melakukan K-means clustering.

K-means clustering

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

# k-means clustering
spotify_km <- kmeans(x = spotify_clean_scale, centers = 15)

str(spotify_km)
#> List of 9
#>  $ cluster     : Named int [1:232721] 9 9 13 13 13 13 1 13 6 6 ...
#>   ..- attr(*, "names")= chr [1:232721] "1" "2" "3" "4" ...
#>  $ centers     : num [1:15, 1:11] -1.1286 -0.5635 -0.6307 0.0163 0.3475 ...
#>   ..- attr(*, "dimnames")=List of 2
#>   .. ..$ : chr [1:15] "1" "2" "3" "4" ...
#>   .. ..$ : chr [1:11] "popularity" "acousticness" "danceability" "duration_ms" ...
#>  $ totss       : num 2559920
#>  $ withinss    : num [1:15] 58710 47588 60622 63246 65367 ...
#>  $ tot.withinss: num 934690
#>  $ betweenss   : num 1625230
#>  $ size        : int [1:15] 9564 11220 10941 10615 19482 11870 26693 25813 21561 104 ...
#>  $ iter        : int 9
#>  $ ifault      : int 0
#>  - attr(*, "class")= chr "kmeans"

Cluster Profiling

Melakukan profiling menggunakan data spotify_clean karena data tersebut sudah tidak memiliki outlier namun belum dilakukan scaling. Kemudian menambahkan kolom kelompok pada data tersebut.

spotify_clean$kelompok <- as.factor(spotify_km$cluster)

Melakukan profiling cluster dengan mencari nilai rata-rata untuk masing-masing produk.

spotify_centroid <-  spotify_clean %>% 
  group_by(kelompok) %>% 
  summarise_all(mean)

spotify_centroid
#> # A tibble: 15 x 12
#>    kelompok popularity acousticness danceability duration_ms energy
#>    <fct>         <dbl>        <dbl>        <dbl>       <dbl>  <dbl>
#>  1 1              20.6       0.802         0.562     218932. 0.670 
#>  2 2              30.9       0.902         0.226     217545. 0.0826
#>  3 3              29.7       0.775         0.402     203658. 0.278 
#>  4 4              41.4       0.217         0.541     257891. 0.722 
#>  5 5              47.4       0.121         0.436     258888. 0.707 
#>  6 6              14.5       0.570         0.691     170511. 0.448 
#>  7 7              59.5       0.179         0.737     217845. 0.587 
#>  8 8              46.0       0.0932        0.521     235463. 0.758 
#>  9 9              40.6       0.123         0.561     215561. 0.803 
#> 10 10             18.1       0.689         0.541    3051794. 0.490 
#> 11 11             26.9       0.818         0.288     697809. 0.211 
#> 12 12             38.2       0.173         0.589     264108. 0.673 
#> 13 13             16.4       0.912         0.325     219354. 0.166 
#> 14 14             47.4       0.177         0.725     226473. 0.718 
#> 15 15             49.2       0.682         0.533     235238. 0.351 
#> # ... with 6 more variables: instrumentalness <dbl>, liveness <dbl>,
#> #   loudness <dbl>, speechiness <dbl>, tempo <dbl>, valence <dbl>

Visualizing

Agar dapat lebih mudah memahami clustering data spotify, dilakukan visualisasi bar plot dari nilai centroid.

library(tidyr)
spotify_centroid %>% 
  pivot_longer(-kelompok) %>% 
  ggplot(aes(x = kelompok, y = value, fill = kelompok)) +
  geom_col() +
  facet_wrap(~name, scales = "free_y")

# menampilkan kelompok dengan nilai terendah dan tertinggi untuk masing-masing fitur audio
spotify_centroid %>% 
  pivot_longer(-kelompok) %>% 
  group_by(name) %>% 
  summarize(kelompok_min = which.min(value),
            kelompok_max = which.max(value))
#> # A tibble: 11 x 3
#>    name             kelompok_min kelompok_max
#>    <chr>                   <int>        <int>
#>  1 acousticness                8           13
#>  2 danceability                2            7
#>  3 duration_ms                 6           10
#>  4 energy                      2            9
#>  5 instrumentalness            1            2
#>  6 liveness                    2            1
#>  7 loudness                    2            9
#>  8 popularity                  6            7
#>  9 speechiness                 2            1
#> 10 tempo                       2            9
#> 11 valence                     2           14

Insight:

Berdasarkan hasil dari tampilan 15 kelompok dari masing-masing fitur audio, menghasilkan:

  1. Cluster 2 memiliki 7 fitur audio rendah pada danceability, energy, liveness, loudness, speechiness, tempo, dan valance. Hal tersebut dapat diartikan bahwa lagu dengan cluster nomor 2 memiliki lagu yang lebih lembut dan memiliki nuansa tenang.
  2. Cluster 9 memiliki 3 fitur audio yang tinggi pada kelompok energy, loudness, dan tempo.
  3. Cluster 3, 4, 5, 8, 11, 12 memiliki fitur audio yang relatif rendah pada kelompok instrumentalness, duration_ms, dan speechiness.