Lollapalooza segundo Spotify - Web Scraping, API do Spotify e tidyverse

Por Athos 27/03/2017

knitr::opts_chunk$set(eval = FALSE)

No dia em que fui ao Lollapalooza eu descobri o Rspotify, um wraper da API do Spotify e daí me veio a ideia de juntar infos dos dois assuntos.

A brincadeira aqui vai envolver

  • Web Scraping - para baixar e estruturar as tabelas de programação do Lolapalooza SP 2017
  • API do Spotify - por meio do pacote Rspotify
  • todos os pacotes do tidyverse

Lollapalooza deste ano em São Paulo contou com 47 bandas distribuídas em quatro palcos. A graça é associar a programação do Lolla com as informações de popularidade das bandas fornecidas pelo Spotify. Abaixo eu vou descrever como peguei os dados, listar as três hipóteses que criei e gerar alguns gráficos pra discutí-las.

Base de dados

Pré-requisitos

Pacotes

# instala o Rspotify
if(!require("Rspotify"))
  devtools::install_github("tiagomendesdantas/Rspotify")
library(Rspotify)
library(magrittr)
library(forcats)
library(stringi)
library(lubridate)
library(httr)
library(rvest)
library(tidyverse)

O Rspotify é um pacote novo e que ainda não está no CRAN, mas já está funcional.

Conta no Spotify

Para utilizar a API do Spotify é necessário ter um cadastro no site deles, como se pode imaginar.

App no Spotify

Para você receber um código de acesso para usar a API deles é preciso criar um App dentro da sua conta do Spotify, esse é o pré-requisito mais burocrático de todos. Eu aprendi a fazer isso seguindo os passos do README do próprio pacote Rspotify no Github (veja aqui).

No fim, você terá um app_id, um client_id e um client_secret em mãos.

Extraindo programação do Lollapalooza SP 2017

O objetivo aqui é termos uma versão em data.frame das tabelas contidas no site lollapaloozabr.com/lineup-horarios/. Lá tem a agenda completa dos dois dias do evento.

Vamos ao código! Dica: a melhor maneira de aprender o que cada passo do código faz é ir rodando linha a linha e observando o resultado.

# programacao do site do lollapalooza 2017 ----------------------

lolla2017_programacao <- "https://www.lollapaloozabr.com/lineup-horarios/" %>%
  GET() %>% 
  read_html() %>%
  html_table() %>%
  set_names(c("sabado", "domingo")) %>%
  at_depth(2, ~ .x %>% 
             stri_replace_all_regex(" {2,}", "") %>% 
             stri_replace_all_regex("[\\n]{1}", ",") %>% 
             stri_replace_all_regex("[,]{2,}", "\\\n") %>% 
             read.csv(text = ., header = FALSE, 
                      col.names = c("artist", "hora"))) %>%
  map(~ .x[-1] %>% 
        data_frame(palco = names(.), programacao_palco = .) %>% 
        unnest(programacao_palco)) %>%
  data_frame(dia = names(.), programacao = .) %>%
  unnest(programacao) %>%
  separate(hora, c("hora_ini", "hora_fim"), sep = "-") %>%
  mutate(artist = artist %>% tolower,
         hora_ini = paste(if_else(dia %in% "sabado", "2017-03-25", "2017-03-26"), hora_ini) %>% ymd_hm(),
         hora_ini = if_else(hour(hora_ini) < 12, hora_ini + hours(12), hora_ini),
         hora_fim = paste(if_else(dia %in% "sabado", "2017-03-25", "2017-03-26"), hora_fim) %>% ymd_hm(),
         hora_fim = if_else(hour(hora_fim) < 12, hora_fim + hours(12), hora_fim),
         dia = fct_relevel(dia, c("sabado", "domingo")))

Resultado

dia palco artist hora_ini hora_fim
sabado Palco Skol cage the elephant 2017-03-25 16:25:00 2017-03-25 17:25:00
sabado Palco Skol metallica 2017-03-25 21:00:00 2017-03-25 23:00:00
sabado Palco Skol doctor pheabes 2017-03-25 12:05:00 2017-03-25 13:05:00
sabado Palco Skol rancid 2017-03-25 18:35:00 2017-03-25 19:35:00
sabado Palco Skol suricato 2017-03-25 14:15:00 2017-03-25 15:15:00
sabado Palco Onix the 1975 2017-03-25 17:30:00 2017-03-25 18:30:00
sabado Palco Onix the outs 2017-03-25 13:10:00 2017-03-25 14:10:00
sabado Palco Onix the xx 2017-03-25 19:40:00 2017-03-25 20:55:00

É interessante reparar que para gerar essa simples tabelinha utilizamos os pacotes httr, rvest, purrr, dplyr, tidyr, lubridate, stringi e forcats. Só faltou o ggplot2 para zerar o tidyverse.

OBS: 89 fm não é uma banda, era só um espaço reservado para fins de publicidade da rádio.

Extraindo a popularidade das bandas do Lollapalooza no Spotify

Agora vamos usar o pacote Rspotify para extrair as popularidades das bandas que estão listadas no data.frame lolla2017_programacao. Para tanto, usei uma playlist oficial no Spotify feita pela própria equipe do Lollapalooza. Essa playlist é identificada pelo id 1mHoPn6JpbtWtoBuvSXrVm lá no banco de dados do Spotify.

meu_token <- spotifyOAuth(app_id, client_id, client_secret) # coloque aqui suas infos fornecidas pelo Spotify.
lolla2017_playlist <- getPlaylistSongs("lollabr", "1mHoPn6JpbtWtoBuvSXrVm", token = meu_token) %>%
  mutate(artistInfo = map(artistId, getArtistinfo),
         artist = artist %>% tolower) %>%
  rename(track_popularity = popularity,
         track_id = id) %>%
  unnest(artistInfo) %>%
  select(artist, id, name, popularity, followers)

Algumas bandas ficaram de fora da playlist e por isso fiz uma pesquisa por nome do artista na própria API do Spotify para recuperar o respectivo id. A função que faz isso é a searchArtist().

# recuperando infos dos artistas esquecidos pela playlist ----------------------

possibly_searchArtist <- possibly(searchArtist, NA_character_)

artistas_fora_da_playlist <- lolla2017_programacao %>% 
  filter(!artist %in% lolla2017_playlist$artist) %$% 
  artist %>% 
  data_frame(artist = .) %>%
  mutate(search_artist = map(artist, ~ .x %>% possibly_searchArtist),
         artist_info = map2(search_artist, artist, ~ {
           if(.x %>% is.na) {data.frame(search_artist = NA)} else {
           .x %>%
             mutate(name = name %>% tolower) %>%
             filter(name %in% .y) %>%
             head(1)
         }})) %>%
  select(-search_artist) %>%
  unnest(artist_info) %>%
  select(artist, id, name, popularity, followers) 

Resultado

artist id name popularity followers
ricci 1EUMh6DZo2CfpolG75YQBL ricci 49 6116
jimmy eat world 3Ayl7mCk0nScecqOzvNp6s jimmy eat world 66 361785
89 fm NA NA NA NA
martin garrix 60d24wfXkVzDSfLS6hyCjZ martin garrix 85 2244761
illusionize 3RloA7E4XMItSP4FjMBv3L illusionize 46 30054

Juntando tudo

Agora vamos juntar a programação do Lolla com as infos do Spotify. A chave é artist.

lolla2017 <- left_join(lolla2017_programacao,
                       lolla2017_playlist %>% bind_rows(artistas_fora_da_playlist),
                       by = "artist") %>%
  select(-id, -name) %>%
  dplyr::filter(followers %>% is.na %>% not) 

Base final

dia palco artist hora_ini hora_fim popularity followers
sabado Palco Skol cage the elephant 2017-03-25 16:25:00 2017-03-25 17:25:00 72 745453
sabado Palco Skol metallica 2017-03-25 21:00:00 2017-03-25 23:00:00 80 3047126
sabado Palco Skol doctor pheabes 2017-03-25 12:05:00 2017-03-25 13:05:00 20 313
sabado Palco Skol rancid 2017-03-25 18:35:00 2017-03-25 19:35:00 60 220182
sabado Palco Skol suricato 2017-03-25 14:15:00 2017-03-25 15:15:00 41 52326
sabado Palco Onix the 1975 2017-03-25 17:30:00 2017-03-25 18:30:00 77 1600845
sabado Palco Onix the outs 2017-03-25 13:10:00 2017-03-25 14:10:00 28 3788
sabado Palco Onix the xx 2017-03-25 19:40:00 2017-03-25 20:55:00 76 2383923

Resultados

Hipótese I

Hipótese I: a organização usou a estratégia de distribuir a popularidade das bandas uniformemente no dia.

Um dos vários desafios logísticos que o evento tem é a alocação das bandas na grade horária nos quatro diferentes palcos.

Eu fui no evento no sábado e ouvi falar que a banda Cage The Elephant tinha sido uma das primeiras bandas a se apresentar. Sabia da popularidade da banda (segundo o Spotify, está mais popular do que The Strokes) e na hora estranhei a decisão do evento de colocá-los para tocar tão cedo.

lolla2017_grafico <- lolla2017 %>%
  mutate(hora = map2(hora_ini, hora_fim, ~ seq(.x, .y, 30*60) %>% floor_date("30 minutes"))) %>%
  unnest(hora) %>%
  group_by(dia, hora, palco) %>%
  summarise(artist = first(artist),
            n = n(),
            mean_popularity = mean(popularity))

lolla2017_grafico %>%
  ggplot(aes(x = ymd_hm(format(hora, "2017-03-26 %H%M")), y = mean_popularity, colour = palco)) +
  geom_line() +
  geom_point() +
  geom_point(data = lolla2017_grafico %>% filter(artist %in% "cage the elephant"), colour = "red", size = 2) +
  geom_text(data = lolla2017_grafico %>% filter(artist %in% "cage the elephant") %>% head(1), aes(label = artist), colour = "red", hjust = 0, vjust = -1) +
  facet_wrap(~dia) +
  labs(x = "Hora do dia", y = "Popularidade média") +
  theme(text = element_text(size = 16))

O gráfico acima vai de acordo com o senso comum de que os populares ficam para o final, não ajudando a confirmar a hipótese de que o Cage The Elefant estava mal posicionado.

Hipótese II

Hipótese II: em termos de popularidade das bandas, o dia de domingo estava melhor do que o dia de sábado.

Em conversas com amigos e conhecidos reparei que a maioria ou iria no domingo ou preferiria ir no domingo caso tivesse oportunidade. Isso me fez levantar a dúvida se realmente havia maior concentração de bandas boas no domingo.

ggplot(lolla2017 %>%
         mutate(artist = artist %>% fct_reorder(popularity, .desc = TRUE))) +
  geom_bar(aes(x = artist, y = popularity, fill = dia), stat = "identity", position = "dodge") +
  theme(text = element_text(size = 16),
        axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.4))

ggplot(lolla2017) +
 geom_density(aes(fill = dia, x = popularity, colour = dia), fill = NA) +
  theme(text = element_text(size = 16),
        axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.4))

Conclusão: nada indica que houve desbalanceamento. Acho que meu círculo de amigos tem algum viés estranho.

Hipótese III

Hipótese III: a popularidade das bandas nos diferentes palcos estava equilibrada.

Quando me questionei da hipótese I também pensei na dificuldade de posicionar as bandas nos diferentes palcos. Já que teriam milhares de pessoas disputando espaço, seria do interesse da organização deixá-los o mais espalhado possível por vários motivos: melhor fluxo das filas, maior conforto, menos risco de acidentes, entre outros, e um bom jeito de fazer isso seria deixando os palcos igualmente atrativos para não haver uma grande aglomeração em um único ponto.

ggplot(lolla2017 %>%
         mutate(palco = palco %>% as.factor %>% fct_reorder(popularity, mean))) +
  geom_boxplot(aes(fill = palco, y = popularity, x = 1)) +
  theme(text = element_text(size = 16),
        axis.text.x = element_blank()) +
  labs(x = "")

O palco Skol teve menor variação de popularidade, costumou contar sempre com artistas de média a alta popularidade, mas os palcos AXE e Onix foram visitados por artista de peso. O palco Perry’s foi o mais visitado por artistas de menor expressão.

Considerações finais

O tema tratado aqui não foi útil, concordo, mas passamos por quase todas as etapas existentes em um processo típico de análise de dados. Fizemos web scraping, usamos APIs, arrumamos os dados, estruturamos as informações, criamos variáveis e geramos gráficos. Só ficou de fora a parte de modelagem. E não à toa todos os pacotes do tidyverse foram úteis nesse trabalho.

A lição pra casa é encontrar uns dados interessantes na internet e aplicar as etapas que aprendemos aqui!

comments powered by Disqus