Live coding:
Análise de dados musicais com R

utilizando pacotes para processamento de texto

04/12/2024

Oi, eu sou a Bianca!

Analista de dados na Agência Pública e membro da R-Ladies São Paulo, mas no passado já esteve envolvida com pesquisa acadêmica na área de neurobiologia de transtornos psiquiátricos. Gosta de quadrinhos, desenhar e passa muito tempo nos SESCs.

Objetivos

  • Explorar pacotes para processamento de texto;
  • Aplicar as funções em um corpus (letras de música);
  • Introduzir técnicas de análise de dados musicais;
  • Usar pacotes do R para visualizações sobre músicas.

Note

Links:

Por que analisar texto?

  • Sentimento, temas, frequência de palavras.
  • Música - texto - dado não-estruturado - dado estruturado –> objeto de análise!

Algumas definições

Algumas definições

  • Um corpus é uma coleção de textos usada para análise
  • Um corpus pode ser usado para identificar padrões, como temas recorrentes ou tendências linguísticas.


Exemplo: Todas as letras de músicas de um artista, um gênero musical ou de um período específico.

  • Um token é uma unidade mínima de texto, geralmente uma palavra.


Exemplo: Na frase “Fiel como um cão”, os tokens são: “fiel”, “como”, “um”, “cão”.

  • Bigramas são pares consecutivos de palavras em um texto.


Exemplo: No verso “Porque chumbo trocado não dói” os bigramas são “Porque chumbo”, “chumbo trocado”, “trocado não”, “não dói”

  • Palavras comuns que geralmente não agregam significado em uma análise, como “o”, “de”, “e”.


Exemplo: “Minha vó é show de bola / Aprendeu lá em Angola / Encontrou meu corpo aberto e fechou” - sem as stopwords: “minha vó é bola aprendeu lá angola encontrou meu corpo aberto fechou”

Fontes de dados:

  • APIs (Spotify, Genius, Vagalume, etc.),

  • Planilhas com metadados musicais,

  • Rankings (Billboard, Spotify…)

  • Seus dados!

O que investigar em um dataset musical: artista, album, ano, letras, gênero, popularidade.

Pacotes úteis

  • dplyr (manipulação de dados);

  • text (análise de texto);

  • ggplot2 (visualização);

  • tidytext (NLP para texto);

  • spotifyr (coleta de dados da API Spotify).

Exemplos e referências

Charts and Beats - Projeto de Nate Sayuri (@natesayuri no insta/behance)

tayloR

tayloR

Sugestão de fluxo de trabalho:

  • Limpeza e tokenização;
  • Análise de frequência;
  • Nuvem de palavras e associações;
  • Análise de sentimentos em letras de músicas.

E o que vamos analisar hoje?

maRRom

Bônus

Conteúdos adicionados após a live :)

Bibliotecas utilizadas

# Verificar antes se já estão instaladas
## Para raspagem do site vagalume
library(httr2)       # Realiza requisições HTTP de forma robusta.
library(rvest)       # Extrai informações de páginas HTML para web scraping.
library(xml2)        # Manipula e analisa documentos HTML/XML.
library(purrr)       # Programação funcional para manipulação de listas e vetores.
library(glue)        # Cria strings interpoladas para formatação dinâmica.
library(future)      # Configura e gerencia processamento paralelo.
library(tidytext)    # Processa e analisa textos em formato tidy.
## Para manipular e analisar os dados
library(dplyr)       # Manipulação de dados com verbos intuitivos (filter, mutate, etc.).
library(tidyr)       # Reorganiza e arruma dados em formato tidy.
library(stringr)     # Manipula strings com funções consistentes e expressões regulares.
library(pbapply)     # Aplica funções em paralelo com barra de progresso.
library(stopwords)   # Fornece listas de stopwords para remoção em análises de texto.
library(tm)          # Ferramentas de processamento e manipulação de textos.
library(wordcloud2)  # Cria nuvens de palavras para visualização gráfica.
library(ggplot2)     # Cria gráficos estáticos e personalizáveis de alta qualidade.
library(lexiconPT)   # Fornece léxicos para análise de sentimentos em português.

Raspagem

# 1. Extração da lista de músicas
artistas <- tibble(
  url_artista = "https://www.vagalume.com.br/alcione/", # confira a url np Vagalume
  nome_artista = "alcione")

# Função para coletar as músicas de cada artista
discografia <- function(url) {
  musLista <- url |>
    read_html() |>
    html_elements("#alfabetMusicList a")
  musLinks <- musLista |> html_attr("href")
  musNomes <- musLista |> html_text()
  
  musica <- tibble(
    url_musica = musLinks,
    nome_musica = musNomes
  ) |>
    filter(!str_detect(url_musica, "#play$")) |>
    mutate(url_musica = glue("https://www.vagalume.com.br{url_musica}"))
  
  Sys.sleep(1)
  return(musica)
}

discografia <- possibly(discografia)

musLista <- pblapply(
  artistas$url_artista, discografia, cl = future::availableCores() - 1)

musicas <- artistas |>
  mutate(data = musLista) |>
  unnest(cols = data)

# 2. Extração dos dados das músicas
album <- function(url) {
  pagina <- url |> read_html()
  
  letra <- pagina |>
    html_element("#lyrics") |>
    html_text2()
  
  estilo <- pagina |>
    html_elements(".h14 a") |>
    html_text()
  
  letra <- tibble(
    letra_musica = letra,
    estilo_musica = list(estilo)
  )
  
  Sys.sleep(0.5)
  return(letra)
}

album <- possibly(album)

letLista <- pblapply(
  musicas$url_musica, album, cl = future::availableCores() - 1
)

letras <- musicas |>
  mutate(data = letLista) |>
  unnest(cols = data)

# Salvando os dados extraídos
saveRDS(letras, "dados/letras-alcione.RDS")

Análise

# 3. Análise de frequência de palavras
my_stopwords <- c(stopwords("pt"))

## Tokenização
contagem_palavras <- letras |>
  unnest_tokens(word, letra_musica) |>
  filter(!word %in% my_stopwords) |> # Filtrando palavras comuns (stopwords)
  count(word) |>
  arrange(desc(n)) |>
  slice(1:30)

contagem_palavras |>
  ggplot(aes(x = reorder(word, n), y = n)) +
  geom_bar(stat = "identity", fill = "pink", colour = "black", alpha = 0.7) +
  coord_flip() +
  labs(x = "Palavras", y = "Frequência", title = "Frequência das Palavras") +
  theme_bw()


# 4. Análise de bigramas
letras %>%
  select(letra_musica) %>%
  unnest_tokens(bigrama, letra_musica, token = "ngrams", n = 2) %>%
  separate(bigrama, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% my_stopwords, !word2 %in% my_stopwords) %>%
  count(word1, word2, sort = TRUE) %>%
  mutate(word = paste(word1, word2)) %>%
  slice(1:30) %>%
  ggplot(aes(reorder(word, n), n)) +
  geom_linerange(aes(ymin = min(n), ymax = n), size = 1, colour = 'darksalmon') +
  geom_point(colour = 'dodgerblue4', size = 3, alpha = 0.9) +
  coord_flip() +
  labs(x = "Top 30 2-grams mais comuns", y = "Contagem") +
  theme_bw()

# Filtra as linhas que possuem o bigrama "chumbo trocado" na coluna `letra_musica`
exemplo_bigrama <- letras %>%
  filter(str_detect(letra_musica, "quatro horas"))

# Printando as linhas correspondentes
exemplo_bigrama


# 5. Análise de sentimentos
sentimentos_pt <- oplexicon_v2.1 %>%
  mutate(word = term) %>%
  select(word, polarity)

letras_sentimentos <- letras %>%
  unnest_tokens(word, letra_musica) %>%
  inner_join(sentimentos_pt, by = "word") %>%
  filter(!word %in% my_stopwords)

letras_sentimentos %>%
  group_by(polarity) %>%
  count(word) %>%
  top_n(15) %>%
  ggplot(aes(reorder(word, n), n)) +
  geom_linerange(aes(ymin = min(n), ymax = n), size = 1, colour = 'darksalmon') +
  geom_point(colour = 'dodgerblue4', size = 3, alpha = 0.9) +
  facet_wrap(~polarity, scales = "free") +
  coord_flip() +
  labs(x = "Top 15 palavras mais comuns", y = "Contagens", title = "Sentimentos") +
  theme_bw()

# 6. Identificando músicas mais positivas e negativas
summ <- letras_sentimentos %>%
  group_by(nome_musica) %>%
  summarise(mean_pol = mean(polarity))
summ %>%
  arrange(desc(mean_pol)) %>%
  slice_head(n = 15) %>%                             # Seleciona as 15 mais positivas
  mutate(situation = "Positivas") %>%
  bind_rows(
    summ %>%
      arrange(mean_pol) %>%
      slice_head(n = 15) %>%                         # Seleciona as 15 mais negativas
      mutate(situation = "Negativas")
  ) %>%
  ggplot(aes(reorder(nome_musica, mean_pol), mean_pol)) +
  geom_linerange(aes(ymin = min(mean_pol), ymax = mean_pol), size = 1, colour = 'darksalmon') +
  geom_point(colour = 'dodgerblue4', size = 3, alpha = 0.9) +
  facet_wrap(~situation, scales = "free") +
  coord_flip() +
  labs(x = "Músicas", y = "Polaridades") +
  theme_bw()

# 7. Nuvem de palavras
wordcloud <- letras |>
  unnest_tokens(word, letra_musica) |>
  filter(!word %in% my_stopwords) |> # Filtrando palavras comuns (stopwords)
  count(word)

wordcloud |> 
  filter(n > 10) |> 
  wordcloud2(size = 1, minSize = 0.8, gridSize = 1,
           color = "#9A373F",
           backgroundColor = "white",
           minRotation = -pi/4, maxRotation = pi/4, shuffle = FALSE,
           rotateRatio = 0, shape = 'circle', ellipticity = 1,
           widgetsize = NULL, figPath = NULL, hoverFunction = NULL)