Pengenalan

Apa itu Spotify?

Spotify merupakan layanan musik digital yang dapat memberikan kita jutaan lagu dari kreator di seluruh dunia. Spotify memudahkan dalam menemukan musik sesuai dengan preferensi penggunanya. Saat ini, terdapat jutaan lagu yang dapat didengar dari beberapa perangkat seperti ponsel, komputer, tablet, dll.

Spotify Top Chart

Setiap tahunnya, spotify memberikan daftar lagu-lagu yang termasuk kedalam kategori populer (top chart). Pada kali ini, kita akan mencoba mengeksplorasi bagaimana pola dari lagu-lagu yang dapat masuk ke dalam kategori top chart.

Dataset

Dataset yang digunakan berisikan lagu-lagu dari tahun 1970 hingga 2021 yang pernah memasuki top chart spotify. Berikut ini adalah informasi mengenai feature yang ada pada dataset:

  • title Title

  • artist Artist

  • genre Genre of the song

  • year Year of the song (due to re-releases, the year might not correspond to the release year of the original song)

  • bpm Beats per minute

  • nrgy Energy of a song, the higher the value the more energetic the song is

  • dnce The higher the value, the easier it is to dance to this song.

  • dB The higher the value, the louder the song

  • live The higher the value, the more likely the song is a live recording.

  • val The higher the value, the more positive the mood for the song

  • dur The duration of the song

  • acous The higher the value the more acoustic the song is.

  • spch The higher the value the more spoken words the song contains.

  • popularity The higher the value the more popular the song is.

  • has_win_award Boolean value to indicate if the song has won an award or not. Value of 1 if the song has already won one or more awards otherwise 0 if the song hasn’t won any awards.

Import Data dan Package

# Library yang akan digunakan
library(tidyverse)
library(readr)
library(ggplot2)
library(plotly)
library(glue)
spotify <- read.csv("datasets/spotify.csv")

Data Preparation

Inspeksi Data

Kita akan melihat bagaimana struktur dari data, dimensi data, serta tipe data yang digunakan. Hal ini akan sangat membantu kita dalam melakukan data preparation sebelum data digunakan dalam pemodelan machine learning.

# Inspeksi Struktur dan tipe data
glimpse(spotify)
## Rows: 1,270
## Columns: 16
## $ X             <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16…
## $ title         <chr> "Love The Way You Lie", "TiK ToK", "Bad Romance", "Just …
## $ artist        <chr> "Eminem", "Kesha", "Lady Gaga", "Bruno Mars", "Justin Bi…
## $ genre         <chr> "detroit hip hop", "dance pop", "dance pop", "pop", "can…
## $ year          <int> 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 20…
## $ bpm           <int> 87, 120, 119, 109, 65, 120, 148, 93, 126, 128, 145, 130,…
## $ nrgy          <int> 93, 84, 92, 84, 86, 78, 76, 37, 72, 87, 83, 82, 83, 84, …
## $ dnce          <int> 75, 76, 70, 64, 73, 75, 52, 48, 79, 62, 62, 77, 83, 44, …
## $ dB            <int> -5, -3, -4, -5, -5, -4, -6, -8, -4, -4, -5, -5, -6, -8, …
## $ live          <int> 52, 29, 8, 9, 11, 4, 12, 12, 7, 6, 10, 70, 11, 12, 36, 1…
## $ val           <int> 64, 71, 71, 43, 54, 82, 38, 14, 61, 47, 48, 63, 71, 78, …
## $ dur           <int> 263, 200, 295, 221, 214, 203, 225, 216, 235, 235, 230, 2…
## $ acous         <int> 24, 10, 0, 2, 4, 0, 7, 74, 13, 3, 33, 18, 1, 1, 20, 5, 7…
## $ spch          <int> 23, 14, 4, 4, 14, 9, 4, 3, 4, 3, 4, 5, 4, 45, 3, 3, 7, 5…
## $ popularity    <int> 82, 80, 79, 78, 77, 77, 77, 76, 73, 73, 73, 73, 73, 72, …
## $ has_win_award <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…

Pengecekan Tipe Data

# Melihat jumlah unique value setiap kolom
spotify %>% 
  summarise_each(funs = n_distinct)

Dari informasi diatas, kita akan mengubah tipe data dari genre menjadi factor. Kita juga akan menghapus kolom X dikarenakan kolom ini merupakan kolom tambahan hasil merge dari beberapa dataset yang telah saya lakukan sebelumnya. Selain itu, kita juga akan mengubah tipe data dari has_win_award menjadi integer (diskret).

spotify <- spotify %>% 
  select(-X) %>% 
  mutate(genre = as.factor(genre), has_win_award = as.integer(has_win_award))

str(spotify)
## 'data.frame':    1270 obs. of  15 variables:
##  $ title        : chr  "Love The Way You Lie" "TiK ToK" "Bad Romance" "Just the Way You Are" ...
##  $ artist       : chr  "Eminem" "Kesha" "Lady Gaga" "Bruno Mars" ...
##  $ genre        : Factor w/ 137 levels "","acoustic blues",..: 82 77 77 131 57 77 77 111 25 77 ...
##  $ year         : int  2010 2010 2010 2010 2010 2010 2010 2010 2010 2010 ...
##  $ bpm          : int  87 120 119 109 65 120 148 93 126 128 ...
##  $ nrgy         : int  93 84 92 84 86 78 76 37 72 87 ...
##  $ dnce         : int  75 76 70 64 73 75 52 48 79 62 ...
##  $ dB           : int  -5 -3 -4 -5 -5 -4 -6 -8 -4 -4 ...
##  $ live         : int  52 29 8 9 11 4 12 12 7 6 ...
##  $ val          : int  64 71 71 43 54 82 38 14 61 47 ...
##  $ dur          : int  263 200 295 221 214 203 225 216 235 235 ...
##  $ acous        : int  24 10 0 2 4 0 7 74 13 3 ...
##  $ spch         : int  23 14 4 4 14 9 4 3 4 3 ...
##  $ popularity   : int  82 80 79 78 77 77 77 76 73 73 ...
##  $ has_win_award: int  1 0 0 0 0 0 0 0 0 0 ...

Penanganan Missing Value

Missing value merupakan kondisi yang harus ditangani sebelum nantinya data digunakan untuk pemodelan. Alasan mengapa missing value perlu ditangani dikarenakan hal tersebut dapat mengurangi akurasi dari model machine learning.

spotify %>% 
  is.na() %>% 
  colSums()
##         title        artist         genre          year           bpm 
##             0             0             0             0             0 
##          nrgy          dnce            dB          live           val 
##             0             0             0             0             0 
##           dur         acous          spch    popularity has_win_award 
##             0             0             0             0             0

Berdasarkan hasil diatas, dataset kita saat ini tidak memiliki missing value.

Penanganan Data Duplikat

spotify %>% 
  distinct() %>% 
  dim()
## [1] 1265   15

Awalnya kita memiliki dataset dengan dimensi (1270, 15) dan setelah fungsi distinct() dijalankan dimensi dari dataset menjadi (1265, 15). Berarti, sebelumnya terdapat 5 baris yang teridentifikasi sebagai data duplikat. Kita akan menghapus semua baris yang teridentifikasi sebagai data duplikat.

spotify <- spotify %>% 
  distinct()

Exploratory Data Analysis

Hubungan antar fitur dari lagu

library(GGally)
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
spotify %>% 
  select_if(is.numeric) %>% 
  select(-has_win_award) %>% 
  ggcorr(label=T)

Apakah terdapat durasi tertentu yang dapat mempengaruhi popularitas lagu?

library(ggthemes)
spotify %>% 
  ggplot(aes(x = dur, y = popularity)) + 
  geom_point() + 
  geom_smooth(formula=y~x,  method = lm, color="red", fill="#69b3a2", se=TRUE) + 
  theme_clean() + 
  labs(
    title = "Korelasi Durasi dan Popularitas",
    subtitle = glue("Corr Value: {round(cor(spotify$dur, spotify$popularity), 2)}"),
    x = "Durasi",
    y = "Popularitas"
  )

cor(spotify$dur, spotify$popularity)
## [1] 0.1394496

Dari hasil diatas, dapat diketahui bahwa korelasi / hubungan antara Durasi dan Popularitas cukup kecil. Lalu, apa saja komponen yang menentukan popularitas suatu lagu? Kita akan cari tahu pada bagian pemodelan di bawah.

Siapa Artis Paling Populer?

popular_artist <- spotify %>% 
  group_by(artist) %>% 
  summarise(n_popular_song = n(), .groups = "drop") %>% 
  arrange(desc(n_popular_song)) %>% 
  head(15)
library(ggthemes)
library(glue)
popular_artist_plot <- popular_artist %>% 
  ggplot(aes(x = n_popular_song, 
             y = reorder(artist, n_popular_song),
             text = glue("Jumlah lagu populer: {n_popular_song}"))) +
  geom_segment( aes(x = 0, xend=n_popular_song, yend = artist), color="skyblue") +
  geom_point( color="orange", size=2.5, alpha=0.6) +
  theme_clean() + 
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank()
  ) + 
  labs(
    title = "Artist dengan Jumlah Lagu Populer Terbanyak",
    subtitle = "Tahun 1960an - 2020an",
    x = "Jumlah Lagu Populer",
    y = "Artist"
  )
ggplotly(popular_artist_plot, tooltip = "text")

Genre Lagu Apa yang Terkenal Pada Era Tertentu?

Feature Engineering

convert_year <- function(year) {
  if(year >= 2020) {
    year <- "2020an"
  } else if (year >= 2010) {
    year <- "2010an"
  } else if (year >= 2000) {
    year <- "2000an"
  } else if (year >= 1990) {
    year <- "1990an"
  } else if (year >= 1980) {
    year <- "1980an"
  } else if (year >= 1970) {
    year <- "1970an"
  } else if (year >= 1960) {
    year <- "1960an"
  } else {
    year <- "Sebelum 1960"
  }
}
# Membuat kolom baru 'era'
spotify$era <- sapply(X = spotify$year, 
                      FUN = convert_year)
spotify$era <- as.factor(spotify$era)

Ekplorasi

popular_genre <- spotify %>% 
  group_by(era, genre) %>% 
  summarise(freq = n(), .groups = "drop") %>% 
  ungroup() %>% 
  arrange(desc(freq))
popular_genre[match(unique(popular_genre$era), popular_genre$era), ]

Contoh Lagu yang memang trending di era 2010an adalah:

  • Lose You To Love Me - Selena Gomez

  • 7 rings - Ariana Grande

  • etc.

Pemodelan

Prediksi Popularitas dari Sebuah Lagu

Membuat Partisi Data

set.seed(100)

index = sample(1: nrow(spotify), 0.7*nrow(spotify))

train = spotify[index, ]
test = spotify[-index, ]
print(dim(train))
## [1] 885  16
print(dim(test))
## [1] 380  16

Normalisasi Fitur Numerik

normalize <- function(x) {
  return((x - min(x)) / (max(x) - min(x)))
}
# Select all numeric feature from spotify
train <- train %>% 
  select_if(is.numeric)
data_train <- lapply(
  train[, c("year", "bpm","nrgy",
                      "dnce", "dB", "live", "val",
                      "dur", "acous", "spch")], 
  FUN = normalize) %>% 
  as.data.frame()
data_train$popularity = train$popularity
test <- test %>% 
  select_if(is.numeric)

data_test <- lapply(
  test[, c("year", "bpm","nrgy",
                      "dnce", "dB", "live", "val",
                      "dur", "acous", "spch")], 
  FUN = normalize) %>% 
  as.data.frame()
data_test$popularity = test$popularity

Multiple Regression

linreg = lm(data = data_train, 
            formula = popularity ~ year + bpm + nrgy + dnce + dB + live + val + dur + acous + spch)

summary(linreg)
## 
## Call:
## lm(formula = popularity ~ year + bpm + nrgy + dnce + dB + live + 
##     val + dur + acous + spch, data = data_train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -68.640  -6.693   1.663   8.615  36.467 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   -3.226      7.944  -0.406  0.68476    
## year          -3.039      2.381  -1.276  0.20220    
## bpm           10.169      3.680   2.763  0.00585 ** 
## nrgy         -16.226      3.772  -4.301 1.89e-05 ***
## dnce          17.405      3.643   4.778 2.08e-06 ***
## dB            76.118     10.076   7.555 1.06e-13 ***
## live          -3.963      2.963  -1.337  0.18149    
## val           -7.222      2.324  -3.108  0.00194 ** 
## dur           11.168      3.609   3.095  0.00203 ** 
## acous        -12.413      2.320  -5.350 1.12e-07 ***
## spch           1.996      3.161   0.631  0.52796    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 12.83 on 874 degrees of freedom
## Multiple R-squared:  0.1933, Adjusted R-squared:  0.1841 
## F-statistic: 20.95 on 10 and 874 DF,  p-value: < 2.2e-16

Simbol significance code *** pada hasil diatas menandakan kalau feature tersebut adalah feature / predictor yang penting. Sehingga, kita dapat menyimpulkan komponen yang mempengaruhi popularitas suatu lagu adalah:

  • nrgy : Seberapa energic lagu tersebut

  • dnce: Apakah lagu dapat digunakan untuk menari

  • dB: Tingkat kekerasan lagu

  • acous: Seberapa akustik lagu tersebut.

Model Evaluation Metrics

eval_metrics <- function(model, df, predictions, target){
  resids = df[, target] - predictions
  resids2 = resids ** 2
  N = length(predictions)
  r2 = as.character(round(summary(model)$r.squared, 2))
  adj_r2 = as.character(round(summary(model)$adj.r.squared, 2))
  print(adj_r2)
  print(as.character(round(sqrt(sum(resids2) / N), 2))) # RMSE
}
# Evaluation on train data
predictions = predict(linreg, newdata = data_train)
eval_metrics(linreg, data_train, predictions, target = "popularity")
## [1] "0.18"
## [1] "12.75"
# Evaluation on test data
predictions = predict(linreg, newdata = test)
eval_metrics(linreg, test, predictions, target = "popularity")
## [1] "0.18"
## [1] "3810.65"

Dari hasil-hasil diatas, kita memiliki suatu model regresi yang R^2 (coefficient of determination) nya 0.18. Artinya, hanya 18% dari target / dependent variabel yang dapat dijelaskan oleh independent variable nya. Dalam kasus ini, dependent variable kita adalah popularity sedangka feature yang digunakan untuk memprediksi adalah independent variable nya.

Tambahan

RMarkdown ini belum sempurna, berikut ini beberapa hal yang bisa saya kembangkan kedepannya:

  • Menambah eksplorasi lebih lanjut mengenai data spotify dikarenakan masih banyak hal yang bisa digali dari data tersebut.

  • Hasil evaluasi dari model regresi masih jelek, hal ini bisa dikarenakan proses data preprocessing yang kurang tepat. Bisa juga dengan mencoba model regresi yang lain seperti Random Forest, Support Vector Machine, dll.

  • Buat model klasifikasi untuk memprediksi kecenderungan suatu lagu memenangkan award atau tidak.