Last.fm Wrapped con R

Recientemente Spotify, el servicio de streaming musical compartió el “Wrapped” de sus usuarios, un resumen anual de su uso de la plataforma. Esta ha sido una iniciativa muy exitosa para Spotify, que genera mucho involucramiento y difusión, al grado que se ha convertido en un evento muy esperado por los usuarios de esta plataforma.

Este año, 2024, los usuarios no han estado particularmente contentos con su Wrapped por distintas razones. Para ser franco, sí me pareció más simple que en años anteriores, pero también es algo que no me sorprende tanto, pues Spotify ha despedido a una proporción considerable de su personal durante el último año.

Pero, lo relevante para este artículo es que podemos replicar las estadísticas básicas que han aparecido en el Wrapped analizando datos obtenidos mediante consultas (requests) a la API (Application Programming Interface) de last.fm, un servicio que se especializa en compilar estadísticas de escucha musical y con ellas generar una base de datos pública.

Haremos este análisis con R, para poner en práctica las consultas a APIs usando el paquete httr2 así distintas tareas de extracción, transformación y presentación de datos con los paquetes del tidyverse. En realidad, son los mismo resúmenes que siempre están disponibles en last.fm, pero es una oportunidad de hacer un análisis de datos divertido.

Empezaremos instalando estos paquetes, con la mención que el análisis ha sido realizado con la versión 4.4.2 de R (“Pile of Leaves”).

Paquetes necesarios

Los paquetes necesarios son:

  • httr2: Permite hacer peticiones HTTP, particularmente útil para interactuar con APIs.
  • tidyverse: Una familia de paquetes con múltiples herramientas para manipular y visualizar datos.
  • jsonlite: No es indispensable para el análisis, pero en este artículo servirá para mostrar de una manera más legibles algunos datos.

Por supuesto, instalamos estamos paquetes a nuestra librería de R con la función install.packages.

install.packages(c("tidyverse", "httr"))

Cargamos los paquetes a nuestra sesión con la función library.

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http: conflicted.r-lib.org="">) to force all conflicts to become errors</http:>
library(httr2)
library(jsonlite)
## 
## Adjuntando el paquete: 'jsonlite'
## 
## The following object is masked from 'package:purrr':
## 
##     flatten

Preparación para el análisis

Para poder hacer peticiones a last.fm necesitamos una API key, que podemos obtener en la siguiente liga, el proceso es muy sencillo y no tienen ningún costo:

Una vez que tenemos nuestra API key, la asignamos a una variable. Es muy importante que no compartas tu key con nadie más, por ello en este artículo no la verás mostrada.

api_key <- "tu API key va aquí"

Para hacer peticiones a la API de last.fm partimos de una URL raíz, que se comparte para todas las peticiones. La asignamos a una variable lastfm_root.

lastfm_root <- "http://ws.audioscrobbler.com/2.0/"

Los URL para hacer peticiones a la API de last.fm tienen la siguiente estructura:

  • [URL raiz][método][entidad:usuario/artista/canción/disco/etc.][API key][parámetros opcionales][formato json (opcional)]

Por ejemplo:

  •  "http://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=zegim&api_key=xxx&period=12month&limit=50&format=json"

Mientras nuestras URL tengan una estructura similar, vamos por buen camino.

Para este análisis usaré mi propia información de last.fm del último año, así que asignaré mi nombre de usuario a una variable, así como el periodo de tiempo sobre el que haremos el análisis. El periodo de tiempo, además de anual “12month”, puede ser “overall”, “7day”, “1month”, “3month” o “6month”.

También pondremos un límite al número de artistas y canciones que recuperaremos para este “Wrapped” y lo asignamos a un par de variables. He fijado estos límites en 50, pues creo que esta cantidad da una buena cantidad de información relevante para analizar, pero por supuesto que puedes cambiarlo.

usuario <- "zegim"
periodo <- "12month"
limite_artistas  <- "50"
limite_canciones <- "50"

Ya con esta preparación hecha, comencemos el análisis obteniendo los artistas más reproducidos del último año.

Artistas más reproducidos

Para recuperar estos datos usaremos el método “user.gettopartists”, el cual asignamos a una variable.

metodo_artistas <- "user.gettopartists"

Con esta variable y las que hemos definido en la preparación del análisis, generamos el URL de petición o request.

request_artistas <- paste0(
  lastfm_root,
  "?method=", metodo_artistas,
  "&user=", usuario,
  "&api_key=", api_key,
  "&period=", periodo,
  "&limit=", limite_artistas,
  "&format=json"
)

El resultado debe verse parecido a este:

"http://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=zegim&api_key=xxx&period=12month&limit=50&format=json"

Con esta URL lista, usaremos tres funciones del paquete **httr2* para hacer la petición a la API last.fm y obtener una respuesta con contenido en formato json.

  • request: Para construir la petición a partir de una URL, como cadena de texto.
  • req_perform: Para ejecutar la petición y obtener una respuesta de la API (más información sobre las respuestas aquí).
  • resp_body_json: Extrae el contenido de la respuesta en formato json. R tratará ese contenido como una lista, pero usaremos funciones del paquete jsonlite para una mejor presentación visual.

Ejecutamos estas tres funciones en secuencia, usando pipes (%>%) y asignamos el resultado a la variable contenido_artistas.

contenido_artistas <- 
  request(request_artistas) %>% 
  req_perform() %>% 
  resp_body_json()

Si todo ha salido bien, obtenemos una lista con una estructura un tanto compleja, en la que se encuentra bastante información de los cincuenta artistas que hemos pedido.

En caso de que algo haya marchado mal, nos aparecerá un error, generalmente un error 400, que indica que no se ha encontrado el recurso solicitado. De ser así, debemos verificar que el URL de la petición tenga la estructura y parámetros correctos, que nuestra API key sea correcta, y que el usuario del que solicitamos datos sea uno válido.

Como nuestra petición ha tenido éxito, veamos su primer elemento relevante:

jsonlite::toJSON(contenido_artistas$topartists$artist[[1]]) %>% 
  jsonlite::prettify()
## {
##     "streamable": [
##         "0"
##     ],
##     "image": [
##         {
##             "size": [
##                 "small"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "medium"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "large"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "extralarge"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "mega"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         }
##     ],
##     "mbid": [
##         "381086ea-f511-4aba-bdf9-71c753dc5077"
##     ],
##     "url": [
##         "https://www.last.fm/music/Kendrick+Lamar"
##     ],
##     "playcount": [
##         "369"
##     ],
##     "@attr": {
##         "rank": [
##             "1"
##         ]
##     },
##     "name": [
##         "Kendrick Lamar"
##     ]
## }
## 

Vamos a extraer dos elementos de cada artista, su nombre (name) y el número de reproducciones que ha tenido (playcount).

Haremos esto usando la función map_df del paquete purrr, parte del tidyverse, que está diseñado para trabajar con programación funcional lo cual es muy útil para trabajar con listas. Esta función es parte de la familia map, que aplica una función a todos lo elementos de una lista.

Extraemos los dos elementos arriba mencionados y lo asignamos a un data frame con la función tibble del paquete del mismo nombre, también parte del tidyverse.

df_artistas <- 
  map_df(contenido_artistas$topartists$artist, function(x_artist) {
  tibble(
    "artista" = x_artist[["name"]],
    "reproducciones" = as.numeric(x_artist[["playcount"]]))
})

El resultado será un data frame como el siguiente:

df_artistas
## # A tibble: 50 × 2
##    artista            reproducciones
##    <chr>                       <dbl>
##  1 Kendrick Lamar                369
##  2 Coheed and Cambria            327
##  3 Bicep                         229
##  4 Sasha                         201
##  5 Placebo                       194
##  6 Nick Drake                    181
##  7 Aphex Twin                    179
##  8 Aqua                          175
##  9 Faithless                     174
## 10 Kylie Minogue                 172
## # ℹ 40 more rows</dbl></chr>

Sí, definitivamente esto fue lo que más escuché durante el último año… aunque no esperaba que hubiera tanta diferencia entre los dos primeros artistas y los demás.

Con esto ya tenemos nuestra listado de artistas con más reproducciones en el último año, así que podemos continuar obteniendo las canciones más reproducidas.

Canciones más reproducidas

El procedimiento para obtener las canciones más reproducidas es básicamente el mismo que para obtener los artistas más reproducidos. Sólo necesitamos cambiar el método de nuestra petición de “user.gettopartists” a “user.gettoptracks”.

metodo_canciones <- "user.gettoptracks"

Generamos el URL de petición.

request_canciones <- paste0(
  lastfm_root,
  "?method=", metodo_canciones,
  "&user=", usuario,
  "&api_key=", api_key,
  "&period=", periodo,
  "&limit=", limite_canciones,
  "&format=json"
)

Hacemos la petición usando las funciones requestreq_perform y resp_body_json en secuencia.

contenido_canciones <- 
  request(request_canciones) %>% 
  req_perform() %>% 
  resp_body_json()

Y,si todo ha salido bien, obtendremos una lista con la información de cincuenta canciones.

Veamos la primera canción:

jsonlite::toJSON(contenido_canciones$toptracks$track[[1]]) %>% 
  jsonlite::prettify()
## {
##     "streamable": {
##         "fulltrack": [
##             "0"
##         ],
##         "#text": [
##             "0"
##         ]
##     },
##     "mbid": [
##         "05de7dd7-8200-4487-93c8-e0b1b61dfd0f"
##     ],
##     "name": [
##         "When It Rains, It Pours"
##     ],
##     "image": [
##         {
##             "size": [
##                 "small"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/34s/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "medium"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "large"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         },
##         {
##             "size": [
##                 "extralarge"
##             ],
##             "#text": [
##                 "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png"
##             ]
##         }
##     ],
##     "artist": {
##         "url": [
##             "https://www.last.fm/music/Bad+Boy+Chiller+Crew"
##         ],
##         "name": [
##             "Bad Boy Chiller Crew"
##         ],
##         "mbid": [
##             "4db5a006-33ce-45b9-a624-cc57948c27ec"
##         ]
##     },
##     "url": [
##         "https://www.last.fm/music/Bad+Boy+Chiller+Crew/_/When+It+Rains,+It+Pours"
##     ],
##     "duration": [
##         "197"
##     ],
##     "@attr": {
##         "rank": [
##             "1"
##         ]
##     },
##     "playcount": [
##         "36"
##     ]
## }
## 

En este caso, para cada canción vamos a extraer el nombre (name), artista(artist), número de reproducciones (playcount) y su duración en segundos (duration). Con estos datos generaremos un data frame usando de nuevo las funciones tibble y map_df.

En el proceso, aplicamos la función as.numeric a los datos de reproducciones y duración, para asegurar que tendremos datos numéricos válidos.

df_canciones <- 
  map_df(contenido_canciones$toptracks$track, function(x){
    tibble(
      "cancion" = x[["name"]],
      "artista" = x[["artist"]][["name"]],
      "reproducciones" = as.numeric(x[["playcount"]]),
      "duracion" = as.numeric(x[["duration"]])
    )
  })

Veamos nuestro resultado:

df_canciones
## # A tibble: 50 × 4
##    cancion                                    artista    reproducciones duracion
##    <chr>                                      <chr>               <dbl>    <dbl>
##  1 When It Rains, It Pours                    Bad Boy C…             36      197
##  2 Time Machine                               Eelke Kle…             31      232
##  3 König oder Feigling - Ed’s Theme           Blumio                 30        0
##  4 Glue                                       Bicep                  29      269
##  5 A Disappearing Act                         Coheed an…             29      210
##  6 Like That                                  Future                 29      267
##  7 A Favor House Atlantic                     Coheed an…             28      344
##  8 You're So High (10 Years On) (Sasha Remix) Eli & Fur              28        0
##  9 All the Stars (with SZA)                   Kendrick …             28        0
## 10 Is It Enough                               R Plus                 28        0
## # ℹ 40 more rows</dbl></dbl></chr></chr>

Con eso ya tenemos nuestra lista de canciones más escuchadas del último año.

Vamos a provechar que tenemos la duración de cada canción para hacer una estimación de qué artista tuvo la mayor cantidad de tiempo de reproducción durante el último año. Es posible tener artistas cuyas canciones se escuchan muchas veces, pero son muy cortas, por lo que en realidad no son tan escuchados como otros artistas con canciones más largas, así que vamos a explorarlo.

Artistas con más tiempo reproducido

Como nos dimos cuenta al darle un vistazo al data frame df_canciones, tenemos canciones con duración 0, debido a que last.fm no tiene esa información entre sus metadatos. Para no perder por completo la información de esas canciones, vamos a hacer un reemplazo de este valor cero por la mediana de duración entre todas las canciones de nuestro data frame.

Hacemos este reemplazo con la función mutate del paquete dplyr del tidyverse, dentro de la cual usamos la función ifelse; primero para reemplazar los valores 0 por NA, y después ese NA, identificados por la función is.na, por la mediana de duraciones, calculada con la función median.

El paso intermedio de 0 a NA evita que la mediana se encuentre muy sesgada precisamente por la presencia de valores iguales a cero.

df_canciones <- 
  df_canciones %>% 
  mutate(
    duracion = ifelse(duracion == 0, NA, duracion),
    duracion = ifelse(is.na(duracion), median(duracion, na.rm = TRUE), duracion)
  )

Nuestro resultado es el siguiente:

df_canciones
## # A tibble: 50 × 4
##    cancion                                    artista    reproducciones duracion
##    <chr>                                      <chr>               <dbl>    <dbl>
##  1 When It Rains, It Pours                    Bad Boy C…             36      197
##  2 Time Machine                               Eelke Kle…             31      232
##  3 König oder Feigling - Ed’s Theme           Blumio                 30      216
##  4 Glue                                       Bicep                  29      269
##  5 A Disappearing Act                         Coheed an…             29      210
##  6 Like That                                  Future                 29      267
##  7 A Favor House Atlantic                     Coheed an…             28      344
##  8 You're So High (10 Years On) (Sasha Remix) Eli & Fur              28      216
##  9 All the Stars (with SZA)                   Kendrick …             28      216
## 10 Is It Enough                               R Plus                 28      216
## # ℹ 40 more rows</dbl></dbl></chr></chr>

Hemos obtenido una duración de 216 segundos, poco más de tres minutos y medio, lo cual suena razonable para una canción. Con esto, podemos obtener los artistas con más tiempo de reproducción

Vamos a obtener el tiempo de reproducción al multiplicar el valor de reproducciones por el valor de duración dentro de una llamada a mutate.

Después vamos a agrupar los datos por artista usando la función group_by de dplyr del tidyverse. Con los datos agrupados, hacemos la suma de tiempo del artista, usando la función sum.

Desagrupamos los datos con ungroup, los ordenamos de mayor a menor usando las funciones arrange y desc, todas del paquete dplyr.

Por último, dentro de otro mutate, creamos las columnas minutos y horas, dividiendo el tiempo, que representa segundos, entre 60 y 3600, respectivamente.

Asignamos el resultado a la variable df_tiempo.

df_tiempo <-
  df_canciones %>%
  mutate(tiempo = reproducciones * duracion) %>%
  group_by(artista) %>%
  summarise(tiempo = sum(tiempo)) %>%
  ungroup() %>%
  arrange(desc(tiempo)) %>%
  mutate(
    minutos = tiempo / 60,
    horas = tiempo / 3600
  )

Veamos nuestro resultado:

df_tiempo
## # A tibble: 36 × 4
##    artista            tiempo minutos horas
##    <chr>               <dbl>   <dbl> <dbl>
##  1 Bicep               22009    367.  6.11
##  2 Kendrick Lamar      20525    342.  5.70
##  3 Coheed and Cambria  15722    262.  4.37
##  4 Burial              14211    237.  3.95
##  5 R Plus              10694    178.  2.97
##  6 Aqua                10465    174.  2.91
##  7 Mastodon             9736    162.  2.70
##  8 Paramore             9021    150.  2.51
##  9 Chappell Roan        8674    145.  2.41
## 10 TEKKEN Project       8208    137.  2.28
## # ℹ 26 more rows</dbl></dbl></dbl></chr>

Comprobamos que, efectivamente, tenemos una lista de artistas diferente a la que se obtiene tomando como criterio el número de reproducciones. Bicep tiene más tiempo de escucha que Kendrick Lamar y Coheed and Cambria, mientras que artistas como Burial y R Plus se hacen presentes cuando antes no lo estaban. Por supuesto, es una medida un tanto imprecisa, pero a mi me suena razonable, conociendo mis hábitos de escucha.

Finalmente, vamos a obtener las principales tags de los artistas con más reproducciones del último año.

Tags principales

Los tags son etiquetas que los usuarios de last.fm usan para describir a un artista. Generalmente son usadas para identificar los géneros del artista, pero también puede describir su país de origen y años en los que ha estado activo, entre otras cosas.

Comenzaremos por extraer a nuestros artistas más escuchados desde el data frame df_artistas que hemos generado anteriormente.

artistas <- df_artistas$artista

Si quisiéramos obtener los tags de un único artista, el procedimiento sería muy parecido a lo que hemos hecho en los dos casos anteriores.

Primero, asignamos a una variable el método a usar, en este caso “artist.getTopTags”.

Como ahora vamos a recuperar información de un artista y no de un usuario, asignamos a una variable el nombre del artista de interés; sólo debemos tener cuidado de reemplazar los espacios en su nombre por símbolos +, para que sea una petición válida a la API de last.fm.

metodo_tags <- "artist.getTopTags"
artista <- "Kendrick+Lamar"

Generamos nuestro URL de petición.

request_tags <- paste0(
  lastfm_root,
  "?method=", metodo_tags,
  "&artist=", artista,
  "&api_key=", api_key,
  "&format=json"
)

Y hacemos la petición.

contenido_tags <- 
  request(request_tags) %>% 
  req_perform() %>% 
  resp_body_json()

El primer elemento relevante de nuestro resultado es el siguiente.

jsonlite::toJSON(contenido_tags$toptags$tag[[1]]) %>% 
  jsonlite::prettify()
## {
##     "count": [
##         100
##     ],
##     "name": [
##         "Hip-Hop"
##     ],
##     "url": [
##         "https://www.last.fm/tag/Hip-Hop"
##     ]
## }
## 

Necesitamos hacer esto para cincuenta artistas, por lo que recurriremos a un bucle (loop). Como estamos usando R, vamos a aprovechar de nuevo el paquete purrr del tidyverse para aplicar cómodamente una función a todos los elementos de una lista.

Para ello, primero definiremos una función que realice todo el procedimiento anterior.

Esta función tendrá un argumento limite, pues cada artista puede tener una cantidad muy grande de tags, ya que estos son agregados por usuarios de last.fm y pueden ser cualquier cosa. Fijamos su valor por defecto en quince, para tener cierto balance entre cantidad y calidad de los tags.

También usaremos la función str_replace_all del paquete stringr del tidyverse para reemplazar los espacios en los nombres de los artistas por símbolos +.

Si todo marcha bien, la función nos devolverá un data frame con dos columnas, una con el nombre del artista y otro con el nombre de los tags.

obtener_tags <- function(artista, api_key, limite=15) {
  metodo_tags <- "artist.getTopTags"
  request_tags <- paste0(
    lastfm_root,
    "?method=", metodo_tags,
    #
    "&artist=", str_replace_all(artista, " ", "+"),
    "&api_key=", api_key,
    "&format=json"
  )

  contenido_tags <- 
    request(request_tags) %>% 
    req_perform() %>% 
    resp_body_json()
  
  # Verifica si se ha obtenido una respuesta válida, si no es así, devuelve NULL
  if (is.null(contenido_tags[["error"]])) {
    map_df(contenido_tags$toptags$tag[1:limite], function(tag) {
      tibble("artista" = artista, "tag" = tag[["name"]])
    })
  } else {
    NULL
  }
}

Probemos nuestra función con Kendrick Lamar.

obtener_tags("Kendrick Lamar", api_key)
## # A tibble: 15 × 2
##    artista        tag                
##    <chr>          <chr>              
##  1 Kendrick Lamar Hip-Hop            
##  2 Kendrick Lamar rap                
##  3 Kendrick Lamar seen live          
##  4 Kendrick Lamar west coast         
##  5 Kendrick Lamar hip hop            
##  6 Kendrick Lamar compton            
##  7 Kendrick Lamar California         
##  8 Kendrick Lamar jazz rap           
##  9 Kendrick Lamar Conscious Hip Hop  
## 10 Kendrick Lamar american           
## 11 Kendrick Lamar west coast hip hop 
## 12 Kendrick Lamar West Coast Rap     
## 13 Kendrick Lamar west coast hip-hop 
## 14 Kendrick Lamar underground hip-hop
## 15 Kendrick Lamar kendrick lamar</chr></chr>

Todo marcha bien, aunque ya anticipamos que tendremos que limpiar un poco los datos que obtendremos, para reducir duplicados y otras inconsistencias.

Como anticipamos, usaremos map_df para obtener los tags de todos nuestros artistas. He puesto la función Sys.sleep dentro de map_df para introducir una pequeña pausa de cortesía entre peticiones a la API de last.fm. Lo anterior no es estrictamente necesario, este análisis difícilmente superará el límite de peticiones de last.fm, pero me gusta ser considerado.

df_tags <- map_df(artistas, function(artista) {
  Sys.sleep(0.01)
  obtener_tags(artista, api_key)
})

Este es nuestro resultado:

df_tags
## # A tibble: 750 × 2
##    artista        tag              
##    <chr>          <chr>            
##  1 Kendrick Lamar Hip-Hop          
##  2 Kendrick Lamar rap              
##  3 Kendrick Lamar seen live        
##  4 Kendrick Lamar west coast       
##  5 Kendrick Lamar hip hop          
##  6 Kendrick Lamar compton          
##  7 Kendrick Lamar California       
##  8 Kendrick Lamar jazz rap         
##  9 Kendrick Lamar Conscious Hip Hop
## 10 Kendrick Lamar american         
## # ℹ 740 more rows</chr></chr>

Limpiamos un poco los nombres de los tags con las funciones str_to_lower, para poner todo en minúsculas, y str_replace_all, para cambiar los guiones por espacios, ambas son funciones del paquete stringr de tidyverse. Además, algo que se por darle un vistazo por mi cuenta a los resultados, es el uso de los tags electronic y electronica, que son equivalentes, por lo que también los consolidaremos.

De una vez, haremos el conteo de tags con la función count del paquete dplyr y asignamos el resultados a la variable df_conteo_tags. Quitamos del conteo las etiquetas con valor NA y seen live, ya que está última es usada por los usuarios de last.fm para llevar un registro de los artistas que han visto en vivo y no es relevante para nuestro análisis.

df_conteo_tags <- 
  df_tags %>%  
  mutate(
    tag = str_to_lower(tag),
    tag = str_replace_all(tag, "-", " "),
    tag = str_replace_all(tag, "electronica", "electronic")
  ) %>% 
  count(tag, sort = TRUE) %>% 
  filter(!tag %in% c(NA, "seen live")) 

Vemos nuestro resultado.

df_conteo_tags
## # A tibble: 271 × 2
##    tag             n
##    <chr>       <int>
##  1 electronic     49
##  2 dance          18
##  3 alternative    17
##  4 rock           16
##  5 pop            14
##  6 british        13
##  7 house          12
##  8 american       11
##  9 techno         11
## 10 trance         10
## # ℹ 261 more rows</int></chr>

Definitivamente me gusta la música electrónica.

Para terminar, vamos a generar gráficas para visualizar nuestros resultados usando el paquete ggplot2 del tidyverse.

Gráficas

No nos detendremos mucho en los detalles para generar los gráficos, pero en todos los casos limitaremos el número de datos para visualizar a diez renglones de los data frames usando la función top_n de dplyr y ordenamos estos datos de menor a mayor usando la función reorder.

El gráfico para los artistas más reproducidos es el siguiente.

df_artistas %>%
  top_n(10, wt = reproducciones) %>%
  mutate(artista = reorder(artista, reproducciones, decreasing = FALSE)) %>%
  ggplot() +
  aes(artista, reproducciones) +
  coord_flip() +
  geom_col(fill = "#1a759f") +
  geom_text(
    aes(label = reproducciones),
    hjust = 1.5,
    color = "#ffffff",
  ) +
  geom_text(
    aes(label = artista, y = 5),
    hjust = "left",
    color = "#ffffff"
  ) +
  scale_y_continuous(expand = c(0, 0)) +
  labs(title = "Artistas más reproducidos", x = "", y = "") +
  theme_minimal() +
  theme(
    panel.grid = element_blank(),
    axis.text = element_blank()
  )

Para el gráfico que muestra las canciones más escuchadas, uniremos el nombre de la canción con el nombre del artista usando la función paste0, de modo que tengamos ambos datos visibles.

df_canciones %>%
  top_n(10, wt = reproducciones) %>%
  mutate(cancion = paste0(cancion, " [", artista, "]")) %>%
  mutate(cancion = reorder(cancion, reproducciones, decreasing = FALSE)) %>%
  ggplot() +
  aes(cancion, reproducciones) +
  coord_flip() +
  geom_col(fill = "#55a630") +
  geom_text(
    aes(label = reproducciones),
    hjust = 1.5,
    color = "#ffffff",
  ) +
  geom_text(
    aes(label = cancion, y = 0.5),
    hjust = "left",
    color = "#ffffff"
  ) +
  scale_y_continuous(expand = c(0, 0)) +
  labs(title = "Canciones más reproducidas", x = "", y = "") +
  theme_minimal() +
  theme(
    panel.grid = element_blank(),
    axis.text = element_blank()
  )

Para el gráfico que muestra el tiempo escuchado por artista, redondearemos el número de minutos usando la función round para mejorar la presentación de los datos.

df_tiempo %>%
  mutate(
    artista = reorder(artista, tiempo, decreasing = FALSE),
    minutos = round(minutos)
    ) %>%
  top_n(10, wt = tiempo) %>%
  ggplot() +
  aes(artista, round(minutos)) +
  coord_flip() +
  geom_col(fill = "#b5838d") +
  geom_text(
    aes(label = minutos),
    hjust = "right",
    nudge_y = -3,
    color = "#ffffff",
  ) +
  geom_text(
    aes(label = artista, y = 3),
    hjust = "left",
    color = "#ffffff"
  ) +
  scale_y_continuous(expand = c(0, 0)) +
  labs(title = "Artistas con más tiempo de reproducción\n(minutos)", x = "", y = "") +
  theme_minimal() +
  theme(
    panel.grid = element_blank(),
    axis.text = element_blank()
  )

Finalmente, el gráfico con el conteo de los tags no requiere mayor procesamiento adicional.

df_conteo_tags %>%
  top_n(10, wt = n) %>%
  mutate(tag = reorder(tag, n, decreasing = FALSE)) %>%
  ggplot() +
  aes(tag, n) +
  coord_flip() +
  geom_col(fill = "#967aa1") +
  geom_text(
    aes(label = n),
    hjust = 1.5,
    color = "#ffffff",
  ) +
  geom_text(
    aes(label = tag, y = .2),
    hjust = "left",
    color = "#ffffff",
  ) +
  scale_y_continuous(expand = c(0, 0)) +
  labs(title = "Tags principales", x = "", y = "") +
  theme_minimal() +
  theme(
    panel.grid = element_blank(),
    axis.text = element_blank()
  )

Conclusión

Eso fue divertido, con todo y que quedó pendiente hacer el conteo de los discos más escuchados durante el año.

Sin embargo en el proceso practicamos hacer consultas a APIs usando R, así como tareas comunes de procesamiento de datos. No nos metimos muy a fondo a revisar cómo trabajar con json en R o cómo generar gráficos bastante personalizados con ggplot2, pero eso puede ser motivo de otro artículo.

Lo que seguramente sí será motivo de otro artículo es llevar todo el análisis anterior a una app de shiny para así poder generar estos “Wrapped” para cualquier usuario de last.fm.

Pero eso, será después.

Mientras puedes encontrar todo el código de este análisis en Github, incluido el Markdown de este artículo:


Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *