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


1. Diccionario de variables (en español)

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)
Data summary
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
)