#Trabajo Final: laboratorio de datos

En el siguiente trabajo práctico final se estudiará un dataset de canciones de Spotify. El mismo fue obtenido de la página Kaggle.

Las variables presentes en el dataset y su explicación son:

https://www.kaggle.com/datasets/theoverman/the-spotify-hit-predictor-dataset

require(ggridges)
## Loading required package: ggridges
require(tidyverse)
## Loading required package: tidyverse
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.4.0      ✔ purrr   0.3.5 
## ✔ tibble  3.1.8      ✔ dplyr   1.0.10
## ✔ tidyr   1.2.1      ✔ stringr 1.4.1 
## ✔ readr   2.1.2      ✔ forcats 0.5.2 
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
require(rpart)
## Loading required package: rpart
require(rpart.plot)
## Loading required package: rpart.plot

Ordenamiento de Datos

data60 <- read.csv("archive/dataset-of-60s.csv")
data70 <- read.csv("archive/dataset-of-70s.csv")
data80 <- read.csv("archive/dataset-of-80s.csv")
data90 <- read.csv("archive/dataset-of-90s.csv")
data00 <- read.csv("archive/dataset-of-00s.csv")
data10 <- read.csv("archive/dataset-of-10s.csv")

variables <- intersect(intersect(intersect(colnames(data60),colnames(data70)),
                                 intersect(colnames(data80),colnames(data90))),
                       intersect(colnames(data00),colnames(data10)))

data60 <- data60 %>% select(all_of(variables))
data70 <- data70 %>% select(all_of(variables))
data80 <- data80 %>% select(all_of(variables))
data90 <- data90 %>% select(all_of(variables))
data00 <- data00 %>% select(all_of(variables))
data10 <- data10 %>% select(all_of(variables))

data60 <- data60 %>% mutate(decada = '1960s')
data70 <- data70 %>% mutate(decada = '1970s')
data80 <- data80 %>% mutate(decada = '1980s')
data90 <- data90 %>% mutate(decada = '1990s')
data00 <- data00 %>% mutate(decada = '2000s')
data10 <- data10 %>% mutate(decada = '2010s')

df_list <- list(data60,
data70,
data80,
data90,
data00,
data10)

data <- rbind(data60,data70)
data <- rbind(data,data80)
data <- rbind(data,data90)
data <- rbind(data,data00)
data <- rbind(data,data10)

data$decada <- factor(data$decada)

data$mode<-replace(data$mode, data$mode == 1,"M") 
data$mode<-replace(data$mode, data$mode == 0,"m")

colnames(data)[19] <- 'Hit'
data$Hit <- as.numeric(data$Hit)

data$Hit <-replace(data$Hit, data$Hit == 1,"Sí") 
data$Hit <-replace(data$Hit, data$Hit == 0,"No")

data$Hit <- factor(data$Hit)
data$mode <- factor(data$mode)

Modificamos la variable target y la llamamos Hit, además modificamos los valores, reemplazando ‘1’ por ‘Sí’ y ‘0’ por ‘No’.

Agregamos la variable ‘decada’ que nos servirá para filtrar los datos correspondientes a cada una de las décadas a las que pertenecen las canciones. Esto lo hacemos ya que juntamos en un solo dataset, todos los datasets correspondientes.

tabla <- summarise_at(group_by(data,decada),vars(Hit),funs(sum(. == 'Sí',na.rm=TRUE),sum(. == 'No',na.rm=TRUE)))
colnames(tabla) <- c("Decada","Hit","No hit")
tabla

Notamos también que en todas las décadas el 50% de las canciones son Hit y el otro 50% no Hit.

Preguntas

Nuestro trabajo se centrará en la variable Hit, queremos entender cómo se relacionan las otras variables con la misma. Por ende, nos formulamos las siguientes preguntas:

Vamos a intentar responder estas preguntas, las primeras dos corresponden a la exploración, mientras que la segunda corresponde al modelado.

Exploración

Primero buscamos familiarizarnos con el dataset y sus variables.

data[grepl('Arctic Monkeys', data$artist),]
head(data %>% arrange(desc(energy)) %>% filter(decada == '2010s'))

Observamos la decada del 2010. Podemos apreciar que de alguna manera la variable energy tiene algo de sentido tras escuchar algunas de las canciones. Si bien no consideramos generos en nuestro analisis, vemos que el death metal es uno de los generos que mas figuran en estas canciones.

data %>% arrange(valence) %>% filter(Hit == 'Sí')

En cuanto a la valence, que refería a cuánta positividad expresa una cancion, podemos ver que de alguna manera tiene sentido la variable ya que aparecen canciones que ciertamente expresan enojo, miedo, entre otras emociones mas negativas. Por ejemplo, vemos la cancion de la pelicula “Close Encounters Of The Third Kind” o por ejemplo la cancion “Hypnotize” de SOAD. La primera ciertamente busca expresar emociones de terror, suspenso y desconcierto; la segunda expresa enojo y una critica social muy marcada.

Exploramos las variables y sus relaciones

ggplot(data,
         aes(x = energy, y = decada, fill=Hit)) +
  geom_density_ridges(
                      alpha=.5, scale = 1, color=NA) +
  theme_ridges(grid = FALSE)  +
  labs(x = "Energy", y = "Decada", title="Energy por Decada")

Se puede apreciar que el valor de la energía aumenta con el correr de las décadas. Además los hits en los 60s y 70s tenían en general más energy que los no hits. Sin embargo, luego se empieza a apreciar que los no hits empiezan a superar en energía a los hits, si bien estos también aumentan y se empiezan a concentrar más cerca del 0.75. Se ve poco cambio en esta varaible a partir de los 80s, parece ser que aumentar demasiado la energía de una canción no es positivo para su popularidad.

data %>% ggplot(aes(x = instrumentalness, y = loudness, color=Hit)) +
  geom_point(alpha=.5) +
  labs(x = "Instrumentalness", y = "Loudness", title="Loudness & Instrumentalness") +
  theme(plot.title = element_text(face="bold"))

Vemos que a partir de 0.25 de Instrumentalness la cantidad de hits empieza a disminuir hasta llegar a cantidades muy bajas a partir de los 0.75. En cuanto a Loudness, vemos que es muy dificil encontrar un hit por debajo de los -20 db.

ggplot(data,
         aes(x = instrumentalness, y = decada, fill=Hit)) +
  geom_density_ridges(
                      color = "pink", alpha = .5, scale = 1) +
  theme_ridges(grid = FALSE) +
  labs(x = "Instrumentalness", y = "Decada", title="Instrumentalness por Decada")

En este gráfico podemos observar la densidad de Instrumentalness por decada. Vemos que la mayor cantidad de puntos está concentrada cerca del 0.0 tanto para hit como no hit. Hay una densidad para valores muy altos de esta variable que corresponden a no hit casi exclusivamente.

data %>% ggplot(aes(x = danceability, y = tempo, color=Hit)) +
  geom_point(alpha=.5) +
  geom_vline(xintercept = 0.40) +
  labs(x = "Danceability", y = "Tempo", title="Tempo & Danceability") +
  theme(plot.title = element_text(face="bold"))

Graficamos una recta vertical a ojo, que nos muestra que los hits parecieran tener un valor de danceability mínimo de 0.40. Además vemos que hay un rango de tempos que son comunes a los hits y a los no hits. Es muy raro que las canciones se salgan del mismo. Podemos ver que a medida que aumenta el Danceability el Tempo se concentra entre los 100 y los 130.

data %>% filter(duration_ms < 1e+06) %>% ggplot(aes(x = duration_ms, y = danceability, color=Hit)) +
  geom_point() + 
  geom_vline(xintercept = 150000) + 
  geom_vline(xintercept = 300000) +
  ggtitle("Duration & Danceability") +
  labs(x = "Duration [ms]", y = "Danceability", title="Duration & Danceability") +
  theme(plot.title = element_text(face="bold"))

Se puede apreciar que existe un rango para la Duration de las canciones, graficamos dos recta a ojo para intentar mostrar esto.

ggplot(data,
         aes(x = chorus_hit, y = decada, fill=Hit)) +
  geom_density_ridges(
                      color = "pink", alpha = .5, scale = 1) +
  xlim(0,100) +
  theme_ridges(grid = FALSE) +
  labs(x = "Chorus-hit", y = "Decada", title="Chorus-hit por Decada")

Vemos que la variable Chorus-hit no parece ser relevante a la hora de distinguir entre un hit o un no hit, ya que sus distribuciones son muy similares a lo largo de todas las décadas.

data %>% ggplot(
         aes(x = valence, y = decada, fill=Hit)) +
  geom_density_ridges(
                      color = NA, alpha = .5, scale = 1) +
  theme_ridges(grid = FALSE) +
  labs(x = "Valence", y = "Decada", title="Valence por Decada")

Podemos apreciar que a medida que pasan las décadas, la Valence empieza a acercarse más al 0.0 tanto para canciones que son Hit como para las que no lo son. Sin embargo, se puede notar más claramente para el caso de los hits. En las décadas anteriores a los 90s, se puede ver que las canciones que eran Hit tenían valores superiores a 0.8, pero a partir de esta, los mismos empiezan a caer. Al llegar al los 2000s vemos que la mayoría de los hits poseen valores un poco mayores a 0.4. Otro punto importante es que también notamos un solapamiento entre hits y no hits bastante importante por lo que creemos que más adelante podría no ser una variable relevante para un modelo de clasificación.

library(colorspace)

data %>% ggplot(aes(decada, valence)) +
  geom_boxplot(aes(color = Hit,
                   fill = after_scale(desaturate(lighten(color, .6), .6))),
               size = 1) +
  scale_color_brewer(palette = "Dark2") +
  labs(x = "Valence", y = "Decada", title="Valence por Decada") +
  theme(plot.title = element_text(face="bold"))

En este boxplot se puede ver lo comentado anteriormente, tanto para los hits como para los no hits, la mediana de la Valence va cayendo a partir de los 90s. Además se puede dilucidar el solapamiento de los no hits y los hits.

ggplot(data,
         aes(x = liveness, y = decada, fill=Hit)) +
  geom_density_ridges(
                      color = "pink", alpha = .5, scale = 1) +
  theme_ridges(grid = FALSE) +  
  labs(x = "Liveness", y = "Decada", title="Liveness por Decada") +
  theme(plot.title = element_text(face="bold"))

Acá tenemos otro ejemplo de una variable que vemos que no nos va a servir para un modelo de clasficación ya que, para todas las decadas, tanto los hits como los no hits están muy solapados.

library(colorspace)
library(cowplot)

cow <- data %>% filter(loudness > -25) %>% ggplot(aes(decada, loudness)) +
  geom_boxplot(aes(color = decada,
                   fill = after_scale(desaturate(lighten(color, .6), .6))),
               size = 1) +
  scale_color_brewer(palette = "Dark2", guide = "none") +
  labs(x = "Decada", y = "Loudness", title="Loudness por Decada") +
  theme(plot.title = element_text(face="bold"))

cow2 <- data %>% ggplot(aes(decada, energy)) +
  geom_boxplot(aes(color = decada,
                   fill = after_scale(desaturate(lighten(color, .6), .6))),
               size = 1) +
  scale_color_brewer(palette = "Dark2", guide = "none") +
  labs(x = "Decada", y = "Energy", title="Energy por Decada") +
  theme(plot.title = element_text(face="bold"))


plot_grid(cow,cow2)

Vemos que ambas variables muestran un incremento a medida que pasan las Decadas.

Resumiendo

Podemos observar que las variables que parecen ser mas relevantes a la hora de decidir si una canción es hit o no son: danceability, loudness, energy, duration_ms e instrumentalness.

Modelado

El approach que vamos a seguir es el siguiente: realizaremos un modelo de clasificación para cada una de las décadas, obteniendo el mejor posible para cada una con cross validation. Utilizaremos la función rpart que nos permite construir un arbol de clasificación/decisión.

El modelado buscará predecir si una canción es hit o no en base a las variables que en la exploración encontramos que eran relevantes para diferenciar un hit de un no hit.

lista_decadas <- c('1960s', '1970s', '1980s', '1990s', '2000s')
accuracy <- function(x){sum(diag(x)/(sum(rowSums(x)))) * 100}

lista_mejores_arboles <- list()

for(deca in lista_decadas){

  lista_arboles <- list()
  lista_accuracies <- c()

  rdata <- data %>% filter(decada == deca)

  set.seed(1)
  for(i in 1:30){

    sample <- sample(c(TRUE, FALSE), nrow(rdata), replace=TRUE, prob=c(0.8,0.2))

    train  <- rdata[sample, ]
    test   <- rdata[!sample, ]

    train_category <- rdata[sample, ]$Hit

    test_category <- rdata[!sample, ]$Hit

    tree <- rpart(data = rdata, formula = "Hit ~ loudness + danceability + energy + duration_ms + instrumentalness")

    clases_predichas <- predict(tree,test,type = "class")

    ac <- accuracy(table(test_category,clases_predichas))

    ac

    lista_arboles[[i]] <- tree
    lista_accuracies <- c(lista_accuracies,ac)

  }

  l <- which.max(lista_accuracies)

  tree <- lista_arboles[[l]]

  lista_mejores_arboles[[deca]] <- tree
}


test <- data %>% filter(decada == "2010s")
test_category <- test$Hit
accuracies <- c()
for(i in 1:length(lista_mejores_arboles)){
  clases_predichas <- predict(lista_mejores_arboles[[i]],test,type = "class")
  ac <- accuracy(table(test_category,clases_predichas))
  accuracies <- c(accuracies,ac)
}

accuracy_modelo_decada <- data.frame(lista_decadas,accuracies)

colnames(accuracy_modelo_decada) <- c("Decada","Accuracy")
rpart.plot(lista_mejores_arboles[[1]])

rpart.plot(lista_mejores_arboles[[2]])

rpart.plot(lista_mejores_arboles[[3]])

rpart.plot(lista_mejores_arboles[[4]])

rpart.plot(lista_mejores_arboles[[5]])

accuracy_modelo_decada

Sorprendentemente podemos observar que a partir de los 70s todos los modelos predicen bastante bien la década del 2010, con porcentajes mayores al 70%. Pero particularmente lo que más llama la atención es que el modelo de los 70s predice casi tan bien como el modelo de los 2000s (peor por ~3%).

accuracy_modelo_decada %>% ggplot(aes(x = Decada, y = Accuracy)) +
  geom_point(size=3) +
  theme(panel.grid = element_blank(), plot.title = element_text(face="bold")) +
  ggtitle("Modelos por Decada")

Vemos que la accuracy no es estrictamente creciente. Pega un salto en los 70s y luego baja en los 80s.

v1 <- "energy + duration_ms + instrumentalness"
v2 <- "danceability + energy + duration_ms + instrumentalness"
v3 <- "loudness + danceability + duration_ms + instrumentalness"
v4 <- "loudness + danceability + energy + duration_ms + instrumentalness"
v5 <- "danceability + duration_ms + instrumentalness"

accuracy_modelo_decada <- cbind(accuracy_modelo_decada,c(v1,v2,v3,v4,v5))

colnames(accuracy_modelo_decada)[3] <- "Variables clave"

accuracy_modelo_decada

En la tabla de arriba agregamos las variables clave de cada arbol. Vemos que la variable danceability empieza a tener importancia a partir de la década de los 70s y es entonces que el accuracy aumenta considerablemente.

Instrumentalness y duration estan en todos los arboles, esto puede deberse al hecho de que son variables fundamentales. Una canción debe tener una cantidad de presencia de instrumentos determinada para ser un hit, y lo mismo con su duración. También notamos que el árbol más simple es el de los 2000s.

¿Por qué predice tan bien el modelo de los 70s?

Las variables del modelo de los 70s son danceability, energy, duration_ms e instrumentalness. Vamos a graficar las mismas para tener una idea de qué tanto se parecen con las de la decada del 2010.

data10y60y70 <- data %>% filter(decada %in% c('1960s','2010s','1970s'))

No nos resultó trivial observar el porqué del salto en el accuracy de la decada del 70. Sin embargo, expondremos una idea de por qué creemos que mejora tanto en relación con el modelo de los 60s.

data10y60y70 %>% filter(Hit == "Sí") %>% ggplot(aes(x = instrumentalness, y = duration_ms)) +
  geom_point(alpha=.5) +
  ylim(0,1e+06) +
  labs(x = "Instrumentalness", y = "Duration_ms", title="Hits por Decada") +
  theme(plot.title = element_text(face="bold")) +
  facet_wrap(~decada)

Graficamos los hits de todas las décadas. Para el instrumentalness que es una variable importante dentro de todos los árboles notamos que en los 60s se puede observar que hay una concentración de hits considerable cerca del 1, algo que no pasa en los 70s ni en los 2010s. Creemos que esto podría explicar la mejoría en el desempeño del modelo de los 70s.

Conclusiones

Los hits mostraron tener a lo largo de las décadas: un rango para la duración, un valor mínimo de danceability y energy, y un valor máximo de instrumentalness. Las canciones a medida que pasan las decadas parecen volverse más tristes al caer la valence de las mismas. Además, en los 2000s y 2010s, hay una gran cantidad de canciones que no son hits que adquieren valores de energy muy elevados.

Las variables importantes a la hora de clasificar una canción como hit, independientemente de las decadas, son instrumentalness y duration_ms. Tiene sentido que a medida que pasan las decadas los modelos sean más certeros en predecir los hits del 2010s. Esto se debe a que las canciones tienden a ser más similares en decadas mas proximas. Sin embargo, sorprende el accuracy del modelo de la decada de los 70s, que es solamente un 3% menor que el de los 2000s. Fue posible construir un modelo con datos que no eran próximos a la decada del 2010s, que aun así sea muy certero.