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.
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 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.
# Library yang akan digunakan
library(tidyverse)
library(readr)
library(ggplot2)
library(plotly)
library(glue)spotify <- read.csv("datasets/spotify.csv")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,…
# 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 ...
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.
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()library(GGally)## Registered S3 method overwritten by 'GGally':
## method from
## +.gg ggplot2
spotify %>%
select_if(is.numeric) %>%
select(-has_win_award) %>%
ggcorr(label=T)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.
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")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)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.
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
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$popularitytest <- 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$popularitylinreg = 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.
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.
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.