Este conjunto de datos contiene 114.000 canciones de Spotify que abarcan 114 géneros musicales únicos, recopiladas a través de la API Web de Spotify. Cada pista incluye un conjunto de características de audio calculadas por el motor interno de análisis de audio de Spotify.
##Fuente: https://www.kaggle.com/datasets/saichaitanyareddyai/spotify-tracks-dataset-audio-features ##Archivo: spotify-tracks-dataset-detailed.csv (19.43 MB)
##Importación de la base de datos
Base_spotify <- read_csv(
"C:/Users/Juan_Cruz/Desktop/Carrera de especialización en estadística para las ciencias de salud/Multivariado/spotify-tracks-dataset-detailed.csv"
)
## Rows: 114000 Columns: 20
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (5): track_id, artists, album_name, track_name, track_genre
## dbl (14): popularity, duration_ms, danceability, energy, key, loudness, mode...
## lgl (1): explicit
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(Base_spotify)
## Rows: 114,000
## Columns: 20
## $ track_id <chr> "5SuOikwiRyPMVoIQDJUgSV", "4qPNDBW1i3p13qLCt0Ki3A", "…
## $ artists <chr> "Gen Hoshino", "Ben Woodward", "Ingrid Michaelson;ZAY…
## $ album_name <chr> "Comedy", "Ghost (Acoustic)", "To Begin Again", "Craz…
## $ track_name <chr> "Comedy", "Ghost - Acoustic", "To Begin Again", "Can'…
## $ popularity <dbl> 73, 55, 57, 71, 82, 58, 74, 80, 74, 56, 74, 69, 52, 6…
## $ duration_ms <dbl> 230666, 149610, 210826, 201933, 198853, 214240, 22940…
## $ explicit <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALS…
## $ danceability <dbl> 0.676, 0.420, 0.438, 0.266, 0.618, 0.688, 0.407, 0.70…
## $ energy <dbl> 0.4610, 0.1660, 0.3590, 0.0596, 0.4430, 0.4810, 0.147…
## $ key <dbl> 1, 1, 0, 0, 2, 6, 2, 11, 0, 1, 8, 4, 7, 3, 2, 4, 2, 1…
## $ loudness <dbl> -6.746, -17.235, -9.734, -18.515, -9.681, -8.807, -8.…
## $ mode <dbl> 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,…
## $ speechiness <dbl> 0.1430, 0.0763, 0.0557, 0.0363, 0.0526, 0.1050, 0.035…
## $ acousticness <dbl> 0.0322, 0.9240, 0.2100, 0.9050, 0.4690, 0.2890, 0.857…
## $ instrumentalness <dbl> 1.01e-06, 5.56e-06, 0.00e+00, 7.07e-05, 0.00e+00, 0.0…
## $ liveness <dbl> 0.3580, 0.1010, 0.1170, 0.1320, 0.0829, 0.1890, 0.091…
## $ valence <dbl> 0.7150, 0.2670, 0.1200, 0.1430, 0.1670, 0.6660, 0.076…
## $ tempo <dbl> 87.917, 77.489, 76.332, 181.740, 119.949, 98.017, 141…
## $ time_signature <dbl> 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 3, 4, 4, 4, 3, 4, 4,…
## $ track_genre <chr> "acoustic", "acoustic", "acoustic", "acoustic", "acou…
Base_spotify <- Base_spotify %>%
mutate(
popularity = as.numeric(popularity),
duration_ms = as.numeric(duration_ms),
track_genre = as.factor(track_genre),
explicit = factor(
explicit,
levels = c(FALSE, TRUE),
labels = c("0", "1")
),
key = as.factor(key),
mode = as.factor(mode),
time_signature = as.factor(time_signature)
)
generos_seleccionados <- c(
"reggae", "electronic", "heavy-metal", "country",
"pop", "tango", "jazz", "reggaeton",
"blues", "hip-hop"
)
Base_spotify_reducida <- Base_spotify %>%
filter(track_genre %in% generos_seleccionados)
Base_spotify_reducida %>%
count(track_genre, sort = TRUE)
## # A tibble: 10 × 2
## track_genre n
## <fct> <int>
## 1 blues 1000
## 2 country 1000
## 3 electronic 1000
## 4 heavy-metal 1000
## 5 hip-hop 1000
## 6 jazz 1000
## 7 pop 1000
## 8 reggae 1000
## 9 reggaeton 1000
## 10 tango 1000
La base de datos contiene 104977 observaciones con 22 variables sin ningún dato faltante
diccionario <- tibble(
grupo = c(
rep("Variables descriptivas", 8),
rep("Variables de audio", 12)
),
variable = c(
"track_id", "artists", "album_name", "track_name",
"track_genre", "popularity", "duration_ms", "explicit",
"danceability", "energy", "key", "loudness", "mode",
"speechiness", "acousticness", "instrumentalness",
"liveness", "valence", "tempo", "time_signature"
),
tipo = c(
"carácter", "carácter", "carácter", "carácter",
"factor", "numérico", "numérico", "factor",
"numérico", "numérico", "factor", "numérico", "factor",
"numérico", "numérico", "numérico",
"numérico", "numérico", "numérico", "factor"
),
descripcion = c(
"Identificador único de la canción en Spotify.",
"Artista o artistas que interpretan la canción.",
"Nombre del álbum en el que se incluye la canción.",
"Nombre de la canción.",
"Género musical asignado a la canción.",
"Popularidad de la canción en Spotify medida en una escala de 0 (baja) a 100 (alta).",
"Duración de la canción en milisegundos.",
"Indica si la canción contiene contenido explícito: 0 = no contiene, 1 = contiene.",
"Grado en que una canción es adecuada para bailar. Rango: 0.0 a 1.0.",
"Medida de intensidad y actividad percibida de la canción. Rango: 0.0 a 1.0.",
"Tonalidad musical estimada de la canción, codificada de 0 a 11.",
"Sonoridad promedio de la canción en decibeles (dB).",
"Modalidad musical de la canción: 0 = menor, 1 = mayor.",
"Medida de presencia de palabras habladas en la canción. Rango: 0.0 al 1.0.",
"Medida de confianza de que la canción sea acústica. Rango: 0.0 a 1.0.",
"Probabilidad de que la canción no contenga voces. Valores cercanos a 1.0 indican que la canción es instrumental.",
"Probabilidad de que la canción haya sido interpretada en vivo. Valores mayores a 0.8 sugieren registros en vivo.",
"Positividad emocional de la canción. Rango: 0.0 (triste) a 1.00 (feliz).",
"Velocidad estimada de la canción, medida en beats por minuto (BPM).",
"Compás estimado de la canción, representado como número de pulsos por compás."
)
)
diccionario %>%
gt(groupname_col = "grupo") %>%
cols_label(
variable = "Variable",
tipo = "Tipo",
descripcion = "Descripción"
) %>%
tab_header(
title = "Diccionario de variables",
subtitle = "Base de datos Spotify"
) %>%
cols_width(
variable ~ px(180),
tipo ~ px(120),
descripcion ~ px(650)
)
| Diccionario de variables | ||
| Base de datos Spotify | ||
| Variable | Tipo | Descripción |
|---|---|---|
| Variables descriptivas | ||
| track_id | carácter | Identificador único de la canción en Spotify. |
| artists | carácter | Artista o artistas que interpretan la canción. |
| album_name | carácter | Nombre del álbum en el que se incluye la canción. |
| track_name | carácter | Nombre de la canción. |
| track_genre | factor | Género musical asignado a la canción. |
| popularity | numérico | Popularidad de la canción en Spotify medida en una escala de 0 (baja) a 100 (alta). |
| duration_ms | numérico | Duración de la canción en milisegundos. |
| explicit | factor | Indica si la canción contiene contenido explícito: 0 = no contiene, 1 = contiene. |
| Variables de audio | ||
| danceability | numérico | Grado en que una canción es adecuada para bailar. Rango: 0.0 a 1.0. |
| energy | numérico | Medida de intensidad y actividad percibida de la canción. Rango: 0.0 a 1.0. |
| key | factor | Tonalidad musical estimada de la canción, codificada de 0 a 11. |
| loudness | numérico | Sonoridad promedio de la canción en decibeles (dB). |
| mode | factor | Modalidad musical de la canción: 0 = menor, 1 = mayor. |
| speechiness | numérico | Medida de presencia de palabras habladas en la canción. Rango: 0.0 al 1.0. |
| acousticness | numérico | Medida de confianza de que la canción sea acústica. Rango: 0.0 a 1.0. |
| instrumentalness | numérico | Probabilidad de que la canción no contenga voces. Valores cercanos a 1.0 indican que la canción es instrumental. |
| liveness | numérico | Probabilidad de que la canción haya sido interpretada en vivo. Valores mayores a 0.8 sugieren registros en vivo. |
| valence | numérico | Positividad emocional de la canción. Rango: 0.0 (triste) a 1.00 (feliz). |
| tempo | numérico | Velocidad estimada de la canción, medida en beats por minuto (BPM). |
| time_signature | factor | Compás estimado de la canción, representado como número de pulsos por compás. |
# Conteo de NA por variable
colSums(is.na(Base_spotify_reducida))
## track_id artists album_name track_name
## 0 0 0 0
## popularity duration_ms explicit danceability
## 0 0 0 0
## energy key loudness mode
## 0 0 0 0
## speechiness acousticness instrumentalness liveness
## 0 0 0 0
## valence tempo time_signature track_genre
## 0 0 0 0
# Porcentaje de NA
porcentaje_na <- colMeans(is.na(Base_spotify_reducida)) * 100
round(porcentaje_na, 2)
## track_id artists album_name track_name
## 0 0 0 0
## popularity duration_ms explicit danceability
## 0 0 0 0
## energy key loudness mode
## 0 0 0 0
## speechiness acousticness instrumentalness liveness
## 0 0 0 0
## valence tempo time_signature track_genre
## 0 0 0 0
audio_vars <- Base_spotify_reducida %>%
select(
danceability, energy, loudness, speechiness,
acousticness, instrumentalness, liveness,
valence, tempo)
desc_vars <- Base_spotify_reducida %>%
select(track_id, artists, album_name, track_name,
track_genre, popularity,
duration_ms, explicit)
gg_miss_var(audio_vars)
gg_miss_var(desc_vars)
Base_spotify_reducida %>%
filter(if_any(everything(), is.na))
## # A tibble: 0 × 20
## # ℹ 20 variables: track_id <chr>, artists <chr>, album_name <chr>,
## # track_name <chr>, popularity <dbl>, duration_ms <dbl>, explicit <fct>,
## # danceability <dbl>, energy <dbl>, key <fct>, loudness <dbl>, mode <fct>,
## # speechiness <dbl>, acousticness <dbl>, instrumentalness <dbl>,
## # liveness <dbl>, valence <dbl>, tempo <dbl>, time_signature <fct>,
## # track_genre <fct>
skim(Base_spotify_reducida)
| Name | Base_spotify_reducida |
| Number of rows | 10000 |
| Number of columns | 20 |
| _______________________ | |
| Column type frequency: | |
| character | 4 |
| factor | 5 |
| numeric | 11 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| track_id | 0 | 1 | 22 | 22 | 0 | 8678 | 0 |
| artists | 0 | 1 | 2 | 352 | 0 | 3179 | 0 |
| album_name | 0 | 1 | 1 | 101 | 0 | 4128 | 0 |
| track_name | 0 | 1 | 1 | 111 | 0 | 6367 | 0 |
Variable type: factor
| skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
|---|---|---|---|---|---|
| explicit | 0 | 1 | FALSE | 2 | 0: 8978, 1: 1022 |
| key | 0 | 1 | FALSE | 12 | 7: 1093, 1: 1045, 0: 1031, 9: 991 |
| mode | 0 | 1 | FALSE | 2 | 1: 5980, 0: 4020 |
| time_signature | 0 | 1 | FALSE | 5 | 4: 9079, 3: 706, 5: 124, 1: 90 |
| track_genre | 0 | 1 | FALSE | 10 | blu: 1000, cou: 1000, ele: 1000, hea: 1000 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| popularity | 0 | 1 | 28.21 | 27.73 | 0.00 | 0.00 | 21.00 | 54.00 | 100.00 | ▇▃▂▃▁ |
| duration_ms | 0 | 1 | 214214.96 | 65806.23 | 43266.00 | 173947.00 | 203392.50 | 241068.25 | 1534315.00 | ▇▁▁▁▁ |
| danceability | 0 | 1 | 0.61 | 0.16 | 0.00 | 0.50 | 0.62 | 0.74 | 0.98 | ▁▂▆▇▃ |
| energy | 0 | 1 | 0.62 | 0.23 | 0.01 | 0.47 | 0.65 | 0.80 | 1.00 | ▁▃▆▇▆ |
| loudness | 0 | 1 | -7.36 | 3.57 | -34.66 | -9.18 | -6.51 | -4.80 | -0.08 | ▁▁▁▆▇ |
| speechiness | 0 | 1 | 0.08 | 0.08 | 0.00 | 0.04 | 0.05 | 0.09 | 0.90 | ▇▁▁▁▁ |
| acousticness | 0 | 1 | 0.34 | 0.33 | 0.00 | 0.04 | 0.19 | 0.64 | 1.00 | ▇▂▂▂▂ |
| instrumentalness | 0 | 1 | 0.06 | 0.19 | 0.00 | 0.00 | 0.00 | 0.00 | 0.97 | ▇▁▁▁▁ |
| liveness | 0 | 1 | 0.19 | 0.16 | 0.01 | 0.10 | 0.13 | 0.25 | 0.99 | ▇▂▁▁▁ |
| valence | 0 | 1 | 0.53 | 0.23 | 0.00 | 0.35 | 0.54 | 0.71 | 0.99 | ▃▆▇▇▅ |
| tempo | 0 | 1 | 119.37 | 30.20 | 0.00 | 95.00 | 116.39 | 138.02 | 243.37 | ▁▅▇▃▁ |
# 1. Crear base analítica sin NA
datos_pca <- Base_spotify_reducida %>%
select(
track_genre,
danceability, energy, loudness, speechiness,
acousticness, instrumentalness, liveness,
valence, tempo
) %>%
drop_na()
audio_vars <- datos_pca %>%
select(-track_genre)
# 2. Correlaciones
cor_matrix <- cor(audio_vars)
corrplot(cor_matrix, method = "color", type = "upper", tl.cex = 0.8)
# 3. Adecuación para PCA
cortest.bartlett(cor_matrix, n = nrow(audio_vars))
## $chisq
## [1] 25189.62
##
## $p.value
## [1] 0
##
## $df
## [1] 36
# 4. PCA
pca_model <- prcomp(audio_vars, center = TRUE, scale. = TRUE)
summary(pca_model)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 1.6525 1.1765 1.0533 1.0099 0.9716 0.91235 0.69814
## Proportion of Variance 0.3034 0.1538 0.1233 0.1133 0.1049 0.09249 0.05416
## Cumulative Proportion 0.3034 0.4572 0.5805 0.6938 0.7987 0.89116 0.94532
## PC8 PC9
## Standard deviation 0.59415 0.37301
## Proportion of Variance 0.03922 0.01546
## Cumulative Proportion 0.98454 1.00000
fviz_eig(pca_model, addlabels = TRUE)
# 5. Loadings
loadings <- as.data.frame(pca_model$rotation)
loadings %>%
rownames_to_column("variable") %>%
select(variable, PC1, PC2)
## variable PC1 PC2
## 1 danceability 0.2741510 0.57598688
## 2 energy 0.5365574 -0.24281089
## 3 loudness 0.5206706 -0.09155632
## 4 speechiness 0.2038612 0.17846826
## 5 acousticness -0.4809455 0.25774190
## 6 instrumentalness -0.1514850 -0.22612732
## 7 liveness 0.0601182 -0.29595355
## 8 valence 0.2183569 0.55924890
## 9 tempo 0.1368936 -0.22613317
# 6. Scores
pca_scores <- as.data.frame(pca_model$x) %>%
mutate(genre = datos_pca$track_genre)
# 7. Visualización PCA por género
ggplot(pca_scores, aes(PC1, PC2, color = genre)) +
geom_point(alpha = 0.5, size = 1.8) +
theme_minimal()
# 8. Clustering
vars_scaled <- scale(audio_vars)
fviz_nbclust(vars_scaled, kmeans, method = "wss")
fviz_nbclust(vars_scaled, kmeans, method = "silhouette")
# 8. Clustering con K = 3
set.seed(123)
kmeans_model <- kmeans(
vars_scaled,
centers = 3,
nstart = 25
)
# 9. Agregar clusters a las bases
pca_scores$cluster <- factor(kmeans_model$cluster)
datos_pca$cluster <- factor(kmeans_model$cluster)
# 10. Visualizar clusters sobre PC1 y PC2
ggplot(pca_scores, aes(x = PC1, y = PC2, color = cluster)) +
geom_point(alpha = 0.5, size = 1.8) +
theme_minimal() +
labs(
title = "Clusters acústicos proyectados sobre PC1 y PC2",
x = "PC1: energía acústica",
y = "PC2: bailabilidad / valencia",
color = "Cluster"
)
# 11. Relación entre cluster y género
cluster_genero <- datos_pca %>%
count(cluster, track_genre) %>%
group_by(cluster) %>%
mutate(
prop = n / sum(n),
prop_porcentaje = round(prop * 100, 1)
) %>%
arrange(cluster, desc(prop))
cluster_genero
## # A tibble: 28 × 5
## # Groups: cluster [3]
## cluster track_genre n prop prop_porcentaje
## <fct> <fct> <int> <dbl> <dbl>
## 1 1 reggaeton 984 0.156 15.6
## 2 1 reggae 960 0.152 15.2
## 3 1 hip-hop 905 0.143 14.3
## 4 1 heavy-metal 884 0.140 14
## 5 1 pop 663 0.105 10.5
## 6 1 country 625 0.0989 9.9
## 7 1 electronic 613 0.0970 9.7
## 8 1 blues 540 0.0855 8.5
## 9 1 jazz 108 0.0171 1.7
## 10 1 tango 36 0.00570 0.6
## # ℹ 18 more rows
# 12. Perfil acústico de cada cluster
# Valores positivos = por encima del promedio global
# Valores negativos = por debajo del promedio global
cluster_profile <- as.data.frame(vars_scaled) %>%
mutate(cluster = factor(kmeans_model$cluster)) %>%
group_by(cluster) %>%
summarise(
across(everything(), mean),
.groups = "drop"
)
cluster_profile
## # A tibble: 3 × 10
## cluster danceability energy loudness speechiness acousticness instrumentalness
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 0.275 0.559 0.500 0.196 -0.566 -0.254
## 2 2 -0.156 -0.334 -0.834 -0.274 0.217 3.54
## 3 3 -0.541 -1.09 -0.863 -0.350 1.14 -0.236
## # ℹ 3 more variables: liveness <dbl>, valence <dbl>, tempo <dbl>
# 13. Tamaño de cada cluster
datos_pca %>%
count(cluster) %>%
mutate(prop_porcentaje = round(n / sum(n) * 100, 1))
## # A tibble: 3 × 3
## cluster n prop_porcentaje
## <fct> <int> <dbl>
## 1 1 6318 63.2
## 2 2 656 6.6
## 3 3 3026 30.3
#Graficos extra
cluster_profile_long <- cluster_profile %>%
pivot_longer(-cluster, names_to = "variable", values_to = "valor")
ggplot(cluster_profile_long, aes(x = variable, y = cluster, fill = valor)) +
geom_tile() +
scale_fill_gradient2(low = "blue", mid = "white", high = "red") +
theme_minimal() +
labs(
title = "Perfil acústico de cada cluster (valores estandarizados)",
x = "Variable",
y = "Cluster",
fill = "Valor"
) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
datos_pca %>%
pivot_longer(
cols = danceability:tempo,
names_to = "variable",
values_to = "valor"
) %>%
ggplot(aes(x = cluster, y = valor, fill = cluster)) +
geom_boxplot() +
facet_wrap(~variable, scales = "free") +
theme_minimal() +
labs(
title = "Distribución de variables por cluster",
x = "Cluster",
y = "Valor"
)
datos_pca %>%
count(cluster, track_genre) %>%
group_by(cluster) %>%
mutate(prop = n / sum(n)) %>%
ggplot(aes(x = cluster, y = prop, fill = track_genre)) +
geom_bar(stat = "identity") +
theme_minimal() +
labs(
title = "Distribución de géneros dentro de cada cluster",
y = "Proporción",
x = "Cluster"
)
fviz_pca_biplot(
pca_model,
geom.ind = "point",
habillage = pca_scores$cluster,
addEllipses = TRUE,
label = "var",
repel = TRUE
)