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))
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:
Para una canción del género Pop, con danceability = 0 y valence = 0, se espera una popularidad promedio de 58.57 puntos.
Por cada unidad adicional de bailabilidad, se espera que la popularidad aumente en aproximadamente 15.75 puntos, manteniendo constante el resto de variables.
Por cada unidad adicional de valencia, se espera que la popularidad disminuya en aproximadamente 1.40 puntos, manentiendo constante el resto de variables.
Una canción de HipHop/Rap es, en promedio 1.998 puntos más poplar que una cancipon de Pop.
Una canción de Electrónica es, en promedio 0.24 puntos menos popular que una canción de Pop.
Una canción de Latina es, en promedio 2.58 puntos más popular que una canción de Pop.
Una canción que no perteneza a ninguno de estos géneros es, en promedio 1.36 puntos, menos popular que una canción de Pop.
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.