La base de datos utilizada fue publicada en Kaggle por el usuario Noor Rehman, bajo el título de Spotify Songs and Artists Dataset | Audio Features, y es actualizada con regularidad.

El presente análisis tiene como objetivo construir un Modelo de Regresión Lineal Múltiple (MRLM) que permita explorar los factores que influyen en la popularidad de una canción de Spotify. Particularmente, se busca evaluar el efecto conjunto de variables cuantitativas como la bailabilidad y la valencia emocional, junto con una variable categórica: el género musical.

Dando inicio con el análisis, se muestran las librerías utilizadas:

library(readr)
library(ggplot2)
library(knitr)
library(kableExtra)
library(Amelia)
library(VIM)
library(dplyr)
library(corrplot)
library(lmtest)
library(PerformanceAnalytics)

Cargamos el dataset original:

data <- read_csv("spotifydataset.csv")

Primeros 10 registros de la base de datos:

kable(head(data, 10))
…1 artist_name genres followers artist_popularity artist_url track_name album_name release_date duration_ms explicit track_popularity danceability energy key loudness mode speechiness acousticness instrumentalness liveness valence tempo
0 Ariana Grande pop 98934105 89 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR we can’t be friends (wait for your love) eternal sunshine 2024-03-08 228639 FALSE 89 0.645 0.646 5 -8.334 1 0.0427 0.0615 3.04e-05 0.0740 0.295 115.842
1 Ariana Grande pop 98934105 85 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR the boy is mine eternal sunshine 2024-03-08 173639 TRUE 85 0.795 0.630 7 -5.854 0 0.0434 0.1570 0.00e+00 0.0732 0.447 97.998
2 Ariana Grande pop 98934105 83 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR intro (end of the world) eternal sunshine 2024-03-08 92400 TRUE 83 0.506 0.362 10 -9.480 1 0.0416 0.6700 0.00e+00 0.1760 0.385 84.726
3 Ariana Grande pop 98934105 80 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR Save Your Tears (Remix) (with Ariana Grande) - Bonus Track After Hours (Deluxe) 2020-03-20 191013 FALSE 80 0.650 0.825 0 -4.645 1 0.0325 0.0215 2.44e-05 0.0936 0.593 118.091
4 Ariana Grande pop 98934105 79 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR yes, and? eternal sunshine 2024-03-08 214994 TRUE 79 0.787 0.775 1 -6.614 1 0.0548 0.1900 6.54e-05 0.1130 0.787 118.998
5 Ariana Grande pop 98934105 82 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR One Last Time My Everything - Deluxe 2014-08-22 197266 FALSE 82 0.628 0.593 8 -5.036 1 0.0323 0.0930 1.70e-06 0.0960 0.104 125.025
6 Ariana Grande pop 98934105 78 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR Die For You (with Ariana Grande) - Remix Starboy (Deluxe) 2023-03-14 232857 FALSE 78 0.568 0.485 1 -7.635 0 0.0582 0.2760 0.00e+00 0.3230 0.517 66.961
7 Ariana Grande pop 98934105 82 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR 7 rings thank u, next 2019-02-08 178626 TRUE 82 0.777 0.317 1 -10.732 0 0.3080 0.5910 0.00e+00 0.0881 0.330 139.848
8 Ariana Grande pop 98934105 70 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR Dangerous Woman Dangerous Woman 2016-05-20 235946 FALSE 70 0.664 0.602 4 -5.369 0 0.0412 0.0529 0.00e+00 0.3560 0.289 134.049
9 Ariana Grande pop 98934105 70 https://open.spotify.com/artist/66CXWjxzNUsdJxJ2JdwvnR Into You Dangerous Woman 2016-05-20 244453 FALSE 70 0.623 0.734 9 -5.948 1 0.1070 0.0162 1.80e-06 0.1450 0.370 107.853

Esta base de datos está compuesta por 1000 filas y 23 columnas en total.

dim(data)
## [1] 1000   23

El dataset en cuestión, contiene 23 variables diferentes, que a continuación, se definen y clasifican según sus tipos:

variables_spotify <- data.frame(
  Variable = c("artist_name", "genres", "followers", "artist_popularity",
               "artist_url", "track_name", "album_name", "release_date",
               "duration_ms", "explicit", "track_popularity", "danceability",
               "energy", "key", "loudness", "mode", "speechiness",
               "acousticness", "instrumentalness", "liveness", "valence", "tempo", "...1"),
  
  Definición = c("Nombre del artista.",
                 "Género o géneros musicales asociados al artista.",
                 "Número de seguidores del artista.",
                 "Popularidad general del artista (escala de 0 a 100).",
                 "URL del perfil del artista.",
                 "Nombre de la canción.",
                 "Nombre del álbum al que pertenece la canción.",
                 "Fecha de lanzamiento de la canción.",
                 "Duración de la canción en milisegundos.",
                 "Indica si la canción contiene contenido explícito.",
                 "Popularidad de la canción (escala de 0 a 100).",
                 "Qué tan bailable es una canción (0 a 1).",
                 "Medida de energía o intensidad (0 a 1).",
                 "Clave musical.",
                 "Volumen promedio en decibelios.",
                 "Modo: mayor (1) o menor (0).",
                 "Proporción de contenido hablado.",
                 "Presencia de componentes acústicos.",
                 "Probabilidad de que la canción sea instrumental.",
                 "Nivel de actividad en la canción (presencia en vivo, público, etc.).",
                 "Carácter emocional positivo (0 tristeza, 1 felicidad).",
                 "Tempo de la canción en BPM (pulsos por minuto).",
                 "Índice numérico del registro en el dataset original."),
  
  Tipo = c("Cualitativa, nominal", "Cualitativa, nominal", "Cuantitativa, discreta", 
           "Cuantitativa, continua", "Cualitativa, nominal", "Cualitativa, nominal", 
           "Cualitativa, nominal", "Cuantitativa, continua", "Cuantitativa, continua", 
           "Cualitativa, dicotómica", "Cuantitativa, continua", "Cuantitativa, continua", 
           "Cuantitativa, continua", "Cuantitativa, discreta", "Cuantitativa, continua", 
           "Cualitativa, dicotómica", "Cuantitativa, continua", "Cuantitativa, continua", 
           "Cuantitativa, continua", "Cuantitativa, continua", "Cuantitativa, continua", 
           "Cuantitativa, continua", "Cuantitativa, discreta")
)

kable(variables_spotify, format = "html", escape = FALSE,
      col.names = c("Variable", "Definición", "Tipo")) %>%
  kable_styling(full_width = F, position = "left") %>%
  add_header_above(c("Variables - Spotify Songs Dataset" = 3))
Variables - Spotify Songs Dataset
Variable Definición Tipo
artist_name Nombre del artista. Cualitativa, nominal
genres Género o géneros musicales asociados al artista. Cualitativa, nominal
followers Número de seguidores del artista. Cuantitativa, discreta
artist_popularity Popularidad general del artista (escala de 0 a 100). Cuantitativa, continua
artist_url URL del perfil del artista. Cualitativa, nominal
track_name Nombre de la canción. Cualitativa, nominal
album_name Nombre del álbum al que pertenece la canción. Cualitativa, nominal
release_date Fecha de lanzamiento de la canción. Cuantitativa, continua
duration_ms Duración de la canción en milisegundos. Cuantitativa, continua
explicit Indica si la canción contiene contenido explícito. Cualitativa, dicotómica
track_popularity Popularidad de la canción (escala de 0 a 100). Cuantitativa, continua
danceability Qué tan bailable es una canción (0 a 1). Cuantitativa, continua
energy Medida de energía o intensidad (0 a 1). Cuantitativa, continua
key Clave musical. Cuantitativa, discreta
loudness Volumen promedio en decibelios. Cuantitativa, continua
mode Modo: mayor (1) o menor (0). Cualitativa, dicotómica
speechiness Proporción de contenido hablado. Cuantitativa, continua
acousticness Presencia de componentes acústicos. Cuantitativa, continua
instrumentalness Probabilidad de que la canción sea instrumental. Cuantitativa, continua
liveness Nivel de actividad en la canción (presencia en vivo, público, etc.). Cuantitativa, continua
valence Carácter emocional positivo (0 tristeza, 1 felicidad). Cuantitativa, continua
tempo Tempo de la canción en BPM (pulsos por minuto). Cuantitativa, continua
…1 Índice numérico del registro en el dataset original. Cuantitativa, discreta

Para mayor comprensión, se hace un resumen de los datos:

summary(data)
##       ...1       artist_name           genres            followers        
##  Min.   :  0.0   Length:1000        Length:1000        Min.   :        2  
##  1st Qu.:249.8   Class :character   Class :character   1st Qu.:  1038784  
##  Median :499.5   Mode  :character   Mode  :character   Median :  3923159  
##  Mean   :499.5                                         Mean   : 11506548  
##  3rd Qu.:749.2                                         3rd Qu.: 11421316  
##  Max.   :999.0                                         Max.   :119428689  
##  artist_popularity  artist_url         track_name         album_name       
##  Min.   : 0.00     Length:1000        Length:1000        Length:1000       
##  1st Qu.:57.00     Class :character   Class :character   Class :character  
##  Median :67.00     Mode  :character   Mode  :character   Mode  :character  
##  Mean   :59.43                                                             
##  3rd Qu.:74.00                                                             
##  Max.   :92.00                                                             
##  release_date        duration_ms      explicit       track_popularity
##  Length:1000        Min.   : 51680   Mode :logical   Min.   : 0.00   
##  Class :character   1st Qu.:173083   FALSE:778       1st Qu.:57.00   
##  Mode  :character   Median :204382   TRUE :222       Median :67.00   
##                     Mean   :212369                   Mean   :59.43   
##                     3rd Qu.:244193                   3rd Qu.:74.00   
##                     Max.   :707160                   Max.   :92.00   
##   danceability        energy             key            loudness      
##  Min.   :0.0690   Min.   :0.00354   Min.   : 0.000   Min.   :-39.482  
##  1st Qu.:0.5055   1st Qu.:0.53950   1st Qu.: 2.000   1st Qu.: -8.334  
##  Median :0.6340   Median :0.68050   Median : 5.000   Median : -5.944  
##  Mean   :0.6114   Mean   :0.66056   Mean   : 5.243   Mean   : -7.160  
##  3rd Qu.:0.7282   3rd Qu.:0.82000   3rd Qu.: 9.000   3rd Qu.: -4.582  
##  Max.   :0.9640   Max.   :0.99800   Max.   :11.000   Max.   :  0.273  
##       mode        speechiness       acousticness       instrumentalness   
##  Min.   :0.000   Min.   :0.02320   Min.   :0.0000052   Min.   :0.0000000  
##  1st Qu.:0.000   1st Qu.:0.03798   1st Qu.:0.0283250   1st Qu.:0.0000000  
##  Median :1.000   Median :0.05480   Median :0.1525000   Median :0.0000037  
##  Mean   :0.613   Mean   :0.09140   Mean   :0.2658398   Mean   :0.0747001  
##  3rd Qu.:1.000   3rd Qu.:0.10600   3rd Qu.:0.4162500   3rd Qu.:0.0005862  
##  Max.   :1.000   Max.   :0.87400   Max.   :0.9960000   Max.   :0.9720000  
##     liveness         valence           tempo       
##  Min.   :0.0260   Min.   :0.0326   Min.   : 49.30  
##  1st Qu.:0.0957   1st Qu.:0.3435   1st Qu.: 99.98  
##  Median :0.1290   Median :0.5220   Median :122.07  
##  Mean   :0.1888   Mean   :0.5166   Mean   :122.14  
##  3rd Qu.:0.2465   3rd Qu.:0.6863   3rd Qu.:138.67  
##  Max.   :0.9840   Max.   :0.9730   Max.   :201.78

Es necesario, hacer una revisión de valores faltantes.

colSums(is.na(data))
##              ...1       artist_name            genres         followers 
##                 0                 0               163                 0 
## artist_popularity        artist_url        track_name        album_name 
##                 0                 0                 0                 0 
##      release_date       duration_ms          explicit  track_popularity 
##                 0                 0                 0                 0 
##      danceability            energy               key          loudness 
##                 0                 0                 0                 0 
##              mode       speechiness      acousticness  instrumentalness 
##                 0                 0                 0                 0 
##          liveness           valence             tempo 
##                 0                 0                 0
sum(is.na(data))
## [1] 163

Los 163 datos faltantes, están presentes en la columna correspondiente a la variable “genres”.

Antes de realizar el tratamiento de datos NA, se generan gráficos, para explorar la proporción de datos faltantes:

aggr(data, col = c('darkred','red'), numbers = TRUE, sortVars = FALSE, labels = names(data), cex.axis = .7, gap = 3, ylab = c("Proportion of Missingness","Missingness Pattern"))

Con el mismo propósito, se genera un Missingness Map:

missmap(data, col = c("red", "darkred"), legend = TRUE)

Estos gráficos, indican que el 1% de los datos corresponden a valores NA.

Ahora, se eliminan únicamente estas observaciones:

dataset <- data %>% filter(!is.na(genres))

Se genera un nuevo Missingness Map, ahora sin datos faltantes:

missmap(dataset, col = c("red", "darkred"), legend = TRUE)

Como se esperaba, este gráfico arroja un 0% de datos faltantes.

Lo anterior, permite afirmar que la base de datos está lista para construir el MRLM.

Iniciando con la construcción del Modelo de Regresión Lineal Múltiple (inlcuyendo variables categóricas), se genera la Matriz de Correlación a manera de descriptivo.

Antes de analizar el gráfico, y con el fin de verificar normalidad, se realizará un test de Shapiro-Wilk:

shapiro.test(dataset$track_popularity)
## 
##  Shapiro-Wilk normality test
## 
## data:  dataset$track_popularity
## W = 0.93462, p-value < 2.2e-16
shapiro.test(dataset$danceability)
## 
##  Shapiro-Wilk normality test
## 
## data:  dataset$danceability
## W = 0.97915, p-value = 1.465e-09
shapiro.test(dataset$valence)
## 
##  Shapiro-Wilk normality test
## 
## data:  dataset$valence
## W = 0.98422, p-value = 7.6e-08

Como todos los p-values son menores que 0.05, se puede afirmar que los datos no siguen una distribución normal, por lo que de ahora en adelante se debe utilizar el Coeficiente de Correlación de Spearman.

En este gráfico, se tendrán en cuenta únicamente las variables numéricas necesarias para cumplir el objetivo de este estudio.

chart.Correlation(dataset[, c("track_popularity", "danceability", "valence")],
                  histogram = TRUE, method = "spearman")

En la matriz de correlación se observan relaciones débiles entre las variables, siendo danceability y valence las que muestran mayor asociación con la popularidad. Esto se debe a que tienen una correlación del 43% con tres asteriscos, lo que le da un nivel de significancia alto. Por su parte, los histogramas reflejan distribuciones aproximadamente simétricas. A su vez, en los gráficos de dispersión no se aprecia una homocedasticidad clara, por lo que será necesario realizar un análisis de residuos posteriormente.

Aunque las correlaciones individuales entre las variables explicativas y la variable de respuesta son bajas (ninguna supera el 70%), se justifica el uso del modelo de regresión lineal múltiple, ya que el objetivo es explorar posibles asociaciones conjuntas que puedan influir en la popularidad de una canción.

Dado que la variable genres contiene más de 50 combinaciones distinta, muchas de ellas poco frecuentes o con nombres compuestos, se procedió a reclasificarla en cinco categorías generales que representen los estilos predominantes. Esta reducción facilita la interpretación y el análisis estadístico del modelo.

dataset$top_generos <- case_when(
  grepl("rap|hip hop|trap|urban contemporary", dataset$genres, ignore.case = TRUE) ~ "HipHop/Rap",
  grepl("reggaeton|urbano latino|latin|bachata|musica mexicana", dataset$genres, ignore.case = TRUE) ~ "Latina",
  grepl("edm|electro|house|trance|dance", dataset$genres, ignore.case = TRUE) ~ "Electronica",
  grepl("pop", dataset$genres, ignore.case = TRUE) ~ "Pop",
  TRUE ~ "Otros"
)

dataset$top_generos <- as.factor(dataset$top_generos)

Luego, se crean los dummys, es decir, las variables indicadoras que toman valores de 0 o 1, con el fin de re-expresar variables categóricas o cualitativas.

dataset$dummy_HipHop <- ifelse(dataset$top_generos == "HipHop/Rap", 1, 0)
dataset$dummy_Electro <- ifelse(dataset$top_generos == "Electronica", 1, 0)
dataset$dummy_Latina <- ifelse(dataset$top_generos == "Latina", 1, 0)
dataset$dummy_Otros <- ifelse(dataset$top_generos == "Otros", 1, 0)

Una vez creadas las variables dummy, se construye la matriz de diseño X y se define el vector que contiene a la variable respuesta Y:

X <- cbind(
  Intercepto = 1,
  danceability = dataset$danceability,
  valence = dataset$valence,
  dummy_HipHop = dataset$dummy_HipHop,
  dummy_Electro = dataset$dummy_Electro,
  dummy_Latina = dataset$dummy_Latina,
  dummy_Otros = dataset$dummy_Otros
)

y <- dataset$track_popularity

Se procede a calcular el vector de coeficientes estimados “Beta Gorro”, utilizando la fórmula matricial de mínimos cuadrados ordinarios:

X_mat <- as.matrix(X)
y_vec <- as.matrix(y)

beta_hat <- solve(t(X_mat) %*% X_mat) %*% t(X_mat) %*% y_vec
beta_hat
##                     [,1]
## Intercepto    58.5662222
## danceability  15.7471574
## valence       -1.4048630
## dummy_HipHop   1.9978864
## dummy_Electro -0.2435742
## dummy_Latina   2.5820654
## dummy_Otros   -1.3663518

Se construye la Ecuación de Regresión Múltiple:

nombres <- c("danceability", "valence", "dummy_HipHop", "dummy_Electro", "dummy_Latina", "dummy_Otros")

cat("Ecuación de regresión múltiple:\n")
## Ecuación de regresión múltiple:
cat("Y =", round(beta_hat[1,1], 4))  
## Y = 58.5662
for (i in 2:nrow(beta_hat)) {
  signo <- ifelse(beta_hat[i,1] >= 0, "+", "-")
  cat(" ", signo, " ", abs(round(beta_hat[i,1], 4)), "*", nombres[i - 1])
}
##   +   15.7472 * danceability  -   1.4049 * valence  +   1.9979 * dummy_HipHop  -   0.2436 * dummy_Electro  +   2.5821 * dummy_Latina  -   1.3664 * dummy_Otros

De esta ecuación, se sacan las siguientes conclusiones:

Ahora, se construirá el MRLM, utilizando las funciones de R:

modelo_multiple <- lm(track_popularity ~ danceability + valence + dummy_HipHop + dummy_Electro + dummy_Latina + dummy_Otros, data = dataset)
summary(modelo_multiple)
## 
## Call:
## lm(formula = track_popularity ~ danceability + valence + dummy_HipHop + 
##     dummy_Electro + dummy_Latina + dummy_Otros, data = dataset)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -51.159  -5.412   0.885   6.658  27.166 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)    58.5662     1.5715  37.268  < 2e-16 ***
## danceability   15.7472     2.6702   5.897 5.37e-09 ***
## valence        -1.4049     1.8197  -0.772   0.4403    
## dummy_HipHop    1.9979     0.9969   2.004   0.0454 *  
## dummy_Electro  -0.2436     1.1148  -0.218   0.8271    
## dummy_Latina    2.5821     2.4708   1.045   0.2963    
## dummy_Otros    -1.3664     0.9868  -1.385   0.1665    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 10.37 on 830 degrees of freedom
## Multiple R-squared:  0.09124,    Adjusted R-squared:  0.08467 
## F-statistic: 13.89 on 6 and 830 DF,  p-value: 4.328e-15

Como se puede observar, se obtuvo la misma ecuación que fue calculada de manera manual.

Para finalzar, se calcual el Coeficiente de Determinación:

summary(modelo_multiple)$r.squared
## [1] 0.09123752

El modelo sugiere que la bailabilidad de una canción tiene un efecto positivo y estadísticamente significativo sobre su popularidad. Por otro lado, ni la valencia emocional ni el género musical mostraron una relación significativa con la variable de respuesta.

Además, si bien el Coeficiente de Determinación indica que el modelo explica únicamente el 9.1% de la variabilidad total de la popularidad, este valor se considera razonable teniendo en cuenta que la popularidad de una canción está influida por numerosos factores externos no contemplados en el modelo, como la fama del artista, la viralidad en redes sociales, el engagement del público, y las estrategias de marketing, entre otros.

Con el fin de complementar esta conclusión, se realizará un ANOVA:

modelo_reducido <- lm(track_popularity ~ danceability + valence, data = dataset)
modelo_completo <- lm(track_popularity ~ danceability + valence + top_generos, data = dataset)
anova(modelo_reducido, modelo_completo)
## Analysis of Variance Table
## 
## Model 1: track_popularity ~ danceability + valence
## Model 2: track_popularity ~ danceability + valence + top_generos
##   Res.Df   RSS Df Sum of Sq      F  Pr(>F)  
## 1    834 90289                              
## 2    830 89210  4    1078.7 2.5091 0.04063 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

El análisis de varianza confirma que la inclusión del género musical mejora significativamente el ajuste del modelo, con un p-value de 0.0406, inferior al umbral del 5%. Esto sugiere que, aunque las variables indicadoras individuales del género no hayan resultado estadísticamente significativas por separado, el género como conjunto sí aporta información relevante para explicar la popularidad de una canción. Por tanto, se justifica su permanencia en el modelo.