Creando tablas sobre tus artistas favorites con {gt} y la API de Spotify

Introducci贸n

Utilizaci贸n del paquete {gt} 馃摝 para visualizar datos de artistas obtenidos mediante la API de Spotify馃幎.

Este post hace referencia al taller del 2022-11-23:

馃敻 Repositorio 馃敻 Grabaci贸n 馃敻

1. Librer铆as

Se definen los paquetes 馃摝 a utilizar:

library(tidyverse) # Manipulaci贸n de datos
library(gt)        # Tablas gt: grammar of tables
library(gtExtras)  # Extras de tablas gt
library(spotifyr)  # Trabajar con la API de spotify
library(rjson)     # Lectura de archivo json
library(ggrepel)   # Para textos en ggplot

鈿狅笍 De no haber podido obtener las credenciales para consultar a la API de Spotify, pasar al punto 3, en donde se leen los datos de un dataframe almacenado en el repositorio. 鈿狅笍

2. API Spotify - Credenciales

Se cuenta con un archivo (credentials..json), que contiene las siguientes variables obtenidas de la p谩gina de desarrolladores de Spotify:

  • SPOTIFY_CLIENT_ID

  • SPOTIFY_CLIENT_SECRET

El formato del archivo credentials.json est谩 incluido en el .gitignore del repositorio. Esto permite mantener las credenciales an贸nimas (sin publicar en repositorios), dado que son personales y no deben compartirse. A continuaci贸n se muestra el formato de este archivo:

{
  "SPOTIFY_CLIENT_ID" : "SPOTIFY_CLIENT_ID",
  "SPOTIFY_CLIENT_SECRET" : "SPOTIFY_CLIENT_SECRET"
}

Se realiza la lectura de las credenciales mediante la funci贸n fromJSON(). Luego, se setean como variables de entorno:

credentials <- fromJSON(file = "credentials.json")

Sys.setenv(SPOTIFY_CLIENT_ID = credentials$SPOTIFY_CLIENT_ID)
Sys.setenv(SPOTIFY_CLIENT_SECRET = credentials$SPOTIFY_CLIENT_SECRET)

Mediante la funci贸n get_spotify_access_token() del paquete {spotifyr} 馃摝, se realiza la autenticaci贸n a la API:

access_token <- get_spotify_access_token()

Para verificar que la autenticaci贸n haya sido exitosa, se realiza una consulta de los artistas m谩s escuchados:

tabla_user <- get_my_top_artists_or_tracks(
                type = 'artists',
                time_range = 'short_term', # 'medium_term', 'long_term'
                limit = 5
              ) %>%
  select(name, genres) %>%
  rowwise() %>%
  mutate(genres = paste(genres, collapse = ', ')) %>%
  ungroup %>%
  gt()

Caso: an谩lisis de un artista particular

En este caso, el artista a analizar es Bad Bunny:

ARTISTA = 'bad bunny'

La funci贸n get_artist_audio_features() del paquete {spotifyr} 馃摝 permite generar una b煤squeda de variables vinculadas al audio de cada track.

vars_audio = c('danceability',
               'energy',
               'loudness',
               'acousticness',
               'instrumentalness')
tracks_features <- get_artist_audio_features(artist = ARTISTA) %>%
  
  select(
    artist_id,
    artist_name,
    album_id,
    album_name,
    album_release_date,
    album_images,
    track_name,
    duration_ms,
    
    # Variables vinculadas al audio:
    all_of(vars_audio)
  ) %>%
  
  arrange(desc(album_release_date))

3. The grammar of tables (gt)

De no haber podido autenticarte con la API de Spotify, dejamos un archivo .rds con los datos necesarios para seguir con el resto del taller:

tracks_features <- read_rds('https://github.com/karbartolome/gt-spotify/blob/main/02_caso_spotify/tracks_features.rds?raw=true')

Se cuenta con un df de variables vinculadas a cada canci贸n de cada 谩lbum del artista seleccionado:

tracks_features %>%
  glimpse()

3.1 Intro a gt

tabla <- tracks_features %>%
  head() %>%
  select(artist_name, album_name, track_name, all_of(vars_audio)) %>%
  
  # En este paso se transforma el df en un objeto gt:
  gt()
tabla

Notar que las variables num茅ricas aparecen con muchos decimales. Una forma sencilla de arreglar esto en {gt} es la siguiente:

tabla %>%
  fmt_number(columns = where(is.numeric), decimals = 2)

3.2 Tabla a nivel 谩lbumes

Se agregan los datos a nivel de cada 谩lbum. Notar que, en el caso de las variables de audio, se decidi贸 convertirlas en una lista (N observaciones num茅ricas que representan cada una de las canciones incluidas en el 谩lbum). En los pr贸ximos pasos se explicar谩 el por qu茅 de la decisi贸n:

df_albums <- tracks_features %>%
  
  group_by(album_images,
           artist_name,
           album_id,
           album_name,
           album_release_date) %>%
  
  summarise(# Duraci贸n del 谩lbum: suma de duraci贸n de cada canci贸n
    duration_mins = sum(duration_ms / (1000 * 60)),
    
    # Lista de cada variable de audio
    across(all_of(vars_audio), ~ list(.x)),) %>%
  
  ungroup()
tabla_albums <- df_albums %>%
  select(-album_images) %>%
  gt() 
tabla_albums

3.3 Im谩genes en tablas gt

Se quiere a帽adir la imagen del 谩lbum. Para ello, se construye la siguiente funci贸n. Para cada 谩lbum se cuenta con distintos tama帽os de im谩genes, donde cada imagen se encuentra representada por un url. Se decide seleccionar la imagen de tama帽o=64, extrayendo el url:

get_imagen = function(album_images) {
  album_images %>%
    data.frame() %>%
    filter(height == 64) %>%
    pull(url) %>%
    as.character()
}

Se aplica la funci贸n al dataframe. Para ello, se utiliza la funci贸n map() del paquete {purrr} 馃摝:

df_albums <- df_albums %>%
  mutate(
    album_images = map(album_images, 
                       ~ get_imagen(album_images = .x))
  ) %>%
  distinct()

Para visualizar los urls como imagenes, se utiliza la funci贸n text_transform() de {gt}. Adem谩s, la funci贸n tab_header() permite a帽adir t铆tulo y subt铆tulo.

imagen_path = 'https://raw.githubusercontent.com/karbartolome/gt-spotify/main/02_caso_spotify/imagenes/bad_bunny.png'
image_size = 50

imagen_artista <- paste0(
    "<img src='", imagen_path,"' style='height:",image_size,"px;'>"
  )

tabla_albums <- df_albums %>%  select(-album_id) %>%
  
  gt() %>%
  
  tab_header(
    title = md(glue::glue('{imagen_artista} **{str_to_title(ARTISTA)}** en Spotify')),
    subtitle = '脕lbumes m谩s recientes') %>%
  
  text_transform(
    locations = cells_body(columns = c(album_images)),
    fn = function(album_images) {
      lapply(album_images, web_image, height = 50)
    }
  ) 

De momento, se ocultan las variables de audio para visualizar la tabla:

tabla_albums %>%
  cols_hide(all_of(vars_audio))

Otra funci贸n 煤til para visualizar este tipo de informaci贸n es gt_merge_stack() que permite concatenar dos variables en una 煤nica:

tabla_albums <- tabla_albums %>%
  
  gt_merge_stack(col1 = album_name,
                 col2 = artist_name) 
tabla_albums %>% 
  cols_hide(all_of(vars_audio)) 

Para visualizar las variables de audio, se utilizar谩n algunas funcionalidades del paquete {gtExtras} 馃摝:

tabla_albums <- tabla_albums %>%
  
  gt_color_box(
    columns = duration_mins,
    palette = c('white', color_spotify),
    domain = c(0, round(max(df_albums$duration_mins)) + 1)
  ) %>%
  
  gt_plt_dist(
    column = danceability,
    type = "density",
    line_color = "black",
    fill_color = color_spotify
  ) %>%
  
  gt_plt_dist(
    column = energy,
    type = "density",
    line_color = "black",
    fill_color = color_spotify
  ) %>%
  
  gt_plt_dist(
    column = loudness,
    type = "density",
    line_color = "black",
    fill_color = color_spotify
  ) %>%
  
  gt_plt_dist(
    column = acousticness,
    type = "boxplot",
    line_color = "black",
    fill_color = color_spotify
  )
tabla_albums %>%
  cols_hide('instrumentalness')

3.4 Ggplot en gt

Notar que si se agregara el boxplot para el caso de la variable instrumentalness, se observa, para todos los 谩lbumes, distribuciones con valores at铆picos:

tabla_albums %>%
  gt_plt_dist(
    column = instrumentalness,
    type = "boxplot",
    line_color = "black",
    fill_color = color_spotify
  ) 

Por esta raz贸n, se utilizar谩 el caso de esta variable para explicar c贸mo se podr铆a identificar a estos valores at铆picos mediante el uso de {ggplot2} 馃摝

gen_outliers_plots <- function(.df,
                               .variable,
                               .font_size = 22,
                               .lwd = 3) {
  temp <- .df %>%
    select(all_of(c('track_name', .variable))) %>%
    pivot_longer(cols = -track_name)
  
  track <- temp %>%
    slice(which.max(value)) %>%
    pull(track_name)
  
  temp %>%
    mutate(is_outlier = ifelse(track_name == track, track_name, NA)) %>%
    
    ggplot(aes(y = value, x = name)) +
    geom_boxplot(
      fill = color_spotify,
      width = 0.2,
      lwd = .lwd,
      outlier.size = 5
    ) +
    geom_text_repel(
      aes(label = is_outlier),
      na.rm = TRUE,
      nudge_x = 0.4,
      size = .font_size
    ) +
    coord_flip() +
    theme_void()
}

Realizando la prueba de la funci贸n, se observa el gr谩fico que genera:

plot <- gen_outliers_plots(
  .df = tracks_features %>% filter(album_name == 'X 100PRE'),
  .variable = 'instrumentalness',
  .font_size = 10,
  .lwd = 0.5
)
plot

Ahora se incluyen estos plots en la tabla. Primero, se mappea la funci贸n al df:

df_albums <- df_albums %>%
  mutate(instrumentalness = map(
    album_id,
    ~ gen_outliers_plots(
        .df = tracks_features %>% filter(album_id == .x),
        .variable = 'instrumentalness')
  ))

Se visualiza la tabla completa:

tabla_albums <- tabla_albums %>%
  
  text_transform(
    locations = cells_body(columns = instrumentalness),
    fn = function(x) {
      map(
        df_albums$instrumentalness,
        gt::ggplot_image,
        height = px(60),
        aspect_ratio = 2
      )
    }
  ) 
tabla_albums

Formato

tabla_albums <- tabla_albums %>%
  
  tab_spanner(label = 'Un tipo de variables',
              columns = danceability:loudness) %>%
  tab_spanner(label = 'Otro tipo de variables',
              columns = c('acousticness', 'instrumentalness')) %>%
  
  tab_footnote(
    locations = cells_column_labels('duration_mins'),
    footnote = 'Duraci贸n en minutos = suma de la duraci贸n de
                               cada una de las canciones que componen el 谩lbum.'
  ) %>%
  
  tab_footnote(
    locations = cells_column_labels('instrumentalness'),
    footnote = 'En el caso de instrumentalness, al existir
                               valores muy at铆picos se muestra la canci贸n a la
                               que corresponde el m谩ximo valor en cada 谩lbum.'
  ) %>%
  
  tab_source_note(source_note = 'Fuente: API de Spotify') %>%
  
  cols_label(
    album_images = '',
    album_name = '',
    album_release_date = 'Lanzamiento',
    duration_mins = 'Duraci贸n',
    danceability = 'Danceability 馃暫',
    energy = 'Energy 鉁',
    loudness = 'Loudness 馃攰',
    acousticness = 'Acousticness 馃幑',
    instrumentalness = 'Instrumentalness 馃幖'
  )
tabla_albums

Adem谩s, se a帽ade un 鉁theme 鉁 espec铆fico para asignarle colores y formatos adicionales:

tabla_albums <- tabla_albums %>%
   tab_options(
      column_labels.background.color = "#39423c",
      footnotes.background.color = "#39423c",
      source_notes.background.color = "#39423c",
      heading.background.color = "#39423c",
      heading.align = "left",
    ) %>%
    tab_style(style = cell_text(color = color_spotify, size = px(32)),
              locations = cells_title("title"))
tabla_albums

3.5 Guardar la tabla gt como archivo .png

Para guardar la tabla, se utiliza la funci贸n gtsave() de {gt} 馃摝:

gtsave(tabla_albums,
       'tablas/tabla_final.png',
       vwidth = 2000,
       vheight = 3000)
Andrea G贸mez Vargas
Andrea G贸mez Vargas

Soci贸loga & analista de datos

Related