Uma jornada pelo tidyverse


1 - Introdução

  O tidyverseé um conjunto de pacotes do R desenvolvido por Hadley Wickham que buscam suprir todas as ferramentas necessárias para um fluxo de trabalho completo em ciências de dados.

  Os pacotes do tidyverse seguem a filosofia tidy. Suas funções devem obedecer os quatro princípios desta filosofia:

  • Reutilizar estruturas de dados existentes;
  • Unir funções simples a partir do uso do pipe %>%;
  • Abraçar a programação funcional;
  • As funções devem ser pensadas para uso por seres humanos.

É comum que os nomes de funções dos pacotes do tidyversesejam simples e diretos como, por exemplo: filter, select e arrange.

  Entre os pacotes que fazem parte do tidyverseestão:

  • readr
  • dplyr
  • tidyr
  • tibble
  • lubridate
  • stringr
  • forcats
  • magrittr
  • ggplot2
  • broom

Instalando o tidyverse

  O tidyversepode ser instalado pelo menu de instalação de pacotes disponível no R Studio ou pela seguinte linha de comando.

install.packages("tidyverse")

  Este comando instalará os seguintes pacotes e suas respectivas dependências: ggplot2, dplyr, tidyr, readr, purr, tibble, stringr e forcats.

Carregando o tidyverse

  De forma parecida a da instalação é possível carregar uma parte dos pacotes do tidyversede uma só vez. Ao carregar os pacotes pela função library(tidyverse) o R mostrará uma mensagem indicando quais pacotes foram carregados, suas versões e possíveis conflitos de nomes de funções com outros pacotes.

library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.5     v dplyr   1.0.7
## v tidyr   1.1.4     v stringr 1.4.0
## v readr   2.0.2     v forcats 0.5.1
## Warning: package 'ggplot2' was built under R version 4.1.2
## Warning: package 'tibble' was built under R version 4.1.1
## Warning: package 'tidyr' was built under R version 4.1.2
## Warning: package 'readr' was built under R version 4.1.1
## Warning: package 'dplyr' was built under R version 4.1.2
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

2 - O pipe: %>%

O pipe %>% é um operador implementado no pacote magrittr que busca melhorar a legibilidade e o tempo de implementação dos códigos. Ele é uma estrutura fundamental dentro do tidyverse para inteligar as demais funções dos pacotes.

No R Studio o atalho para o pipe é o seguinte:

ctrl + shift + m

É fácil notar as vantagens no uso do pipe a partir de um exemplo simples: Vamos gerar uma amostra, então calcular sua média e por fim arredondar para ter três casas decimais.

Primeiro, sem usar o pipe:

round(mean(runif(100)), 3)
## [1] 0.544

Agora com o pipe:

runif(100) %>%
  mean() %>%
  round(3)
## [1] 0.464

É bem mais simples de entender o que está acontecendo no código no segundo exemplo.

O pipe é tão popular que na versão 4.1 do R foi implementada uma versão nativa do pipe denotada por: |>. Ainda que suas funções sejam essencialmente as mesmas, existem algumas diferenças nas possibilidades do %>% e do |>, incluindo:

  • Por ser implementado nativamente, o |> é mais rápido do que o %>%;
  • Até o momento o |>não possui um marcador para a base de dados, o %>% suporta o uso do . dentro do fluxo de funções (mais sobre isso daqui a algumas seções). Uma solução momentânea é utilizar as novas funções anônimas implementadas no R 4.1;
  • Por ser uma implementação recente, nem todos as funções de pacotes de fora do tidyverse que funcionam com o %>%funcionam da mesma maneira com o |>.

Por enquanto vamos manter a utilização do %>% neste minicurso.

3 - A missão de hoje: uma amostra da base de músicas do Spotify

Antes de mostrar os pacotes do tidyverseiremos conhecer a base de dados que servirá como material de trabalho. O sistema de recomendação de faixas do Spotify utiliza diversas variáveis para caracterizar as músicas de modo a sugerir quais são mais similares as que um determinado usuário escuta.

Existem variáveis que indicam a dançabilidade da faixa (é sério) e sua positividade a outras características mais esperadas como a nota musical ou o tempo em milisegundos. Algumas das variáveis da base indicam a probabilidade da música possuir alguma característica, usualmente estas variáveis são interpretadas como o nível de confiança de que a música possui a característica.

A base é composta por 176.249 faixas e 17 variáveis, sendo elas:

  • genre: Gênero musical da faixa;
  • artist_name: Nome do artista;
  • track_name: Nome da faixa;
  • track_id: Número identificador da faixa;
  • popularity: Popularidade da faixa. Varia de 0 a 100 e valores maiores indicam maior popularidade;
  • acousticness: Indica se a faixa é acústica. Varia de 0 a 1 e valores mais elevados indicam maior confiança;
  • danceability: Indica o quão dançante é a faixa. Varia de 0 a 1 e valores mais elevados indicam maior dançabilidade;
  • duration_ms: Duração da faixa em milissegundos;
  • energy: Medida de percepção da intensidade da faixa. Varia de 0 a 1 e valores mais elevados indicam maior energia;
  • intrumentalness: Indica se uma faixa é instrumental. Varia de 0 a 1, valores acima de 0.5 indicam que a música é instrumental;
  • key: Nota música da faixa;
  • liveness: Indica a presença de audiência. Varia de 0 a 1 e valores mais elevados indicam maior probabilidade de audiência;
  • loudness: Volume médio da faixa em decibéis;
  • mode: Indica a tonalidade da faixa (maior ou menor);
  • speechiness: Indica a presença de voz na faixa. Varia de 0 a 1, valores acima de 0.66 indicam que há apenas voz na faixa, valores entre 0.33 e 0.66 indicam faixas com seções de música e voz (ex: rap) e valores abaixo de 0.33 sugerem música;
  • tempo: Ritmo da faixa em BPM (batidas por minuto);
  • valence: Indica a positividade da faixa (avaliada pela letra). Varia de 0 a 1 e valores mais elevados indicam maior positividade.

Mais informações sobre essas variáveis e a API do Spotify estão disponíveis neste link.

Agora que conhecemos a base de dados, mãos a obra!

4 - Importando dados com o readr

O pacote básico do R possui funções para importação dos formatos mais comuns de dados. Essas funções costumam possuir nomes como read.csv(), read.csv2(), read.delim(), entre outros e em grande parte do tempo conseguem fazer o necessário. Entretanto, seu uso se torna consideravelmente menos eficiente para bases de dados muito grandes.

Uma alternativa para estes cenários é o pacote readr, suas funções são mais eficientes que as do pacote básico, possuem mais argumentos para controlar a importação e também mostram uma barra de carregamento enquanto a importação é feita.

A base do Spotify foi dividida em três arquivos diferentes. Os arquivos que precisamos importar estão nas bases spotify_db_01.csv, spotify_db_02.csv e spotify_db_03.csv.

Para realizar a importação utilizaremos a função read_csv2. Ela possui os seguintes argumentos:

read_csv2(
  file, # O endereço do arquivo que será importado.
  col_names = TRUE, # Se TRUE, usará a primeira linha do arquivo. Se FALSE, usará nomes genéricos. Também aceita um vetor de nomes.  
  col_types = NULL, # Se NULL irá imputar o tipo de dado na coluna utilizando as primeiras 1000 linhas. Se a imputação falhar é necessário indicar o tipo de cada coluna por strings utilizando este argumento.
  locale = default_locale(), # Tipo de encoding utilizado no sistema. 
  na = c("", "NA"), # Vetor de strings que indica o que deve ser considerado um NA.
  quoted_na = TRUE, # Indica se valores faltantes dentro de citação devem ser considerados NA ou não.
  quote = "\"", # Caractere usado para indicar citação.
  comment = "", # String utilizada para indicar comentários no arquivo, o que vier depois do comentário será ignorado na importação.
  trim_ws = TRUE, # Indica se os espaços a frente e a atrás dos valores nas células devem ser removidos. 
  skip = 0, # Número de linhas a serem puladas no começo do arquivo no momento da importação.
  n_max = Inf, # Número máximo de linhas a serem importadas.
  guess_max = min(1000, n_max), # Número máximo de linhas a serem utilizadas na imputação do tipo de coluna.
  progress = show_progress(), # Indica se a barra de carregamento deve ser exibida.
  skip_empty_rows = TRUE # Indica se as linhas vazias devem ser ignoradas.
)

Para essas bases não precisaremos manipular a maior parte desses argumentos, portanto iremos direto a importação:

base1 = read_csv2('https://raw.githubusercontent.com/ricardo-js1/SEMEST-tidyverse/main/spotify_db_01.csv')
## i Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 175084 Columns: 6-- Column specification --------------------------------------------------------
## Delimiter: ";"
## chr (4): track_id, track_name, artist_name, genre
## dbl (2): popularity, duration_ms
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
base2 = read_csv2('https://raw.githubusercontent.com/ricardo-js1/SEMEST-tidyverse/main/spotify_db_02.csv')
## i Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 175084 Columns: 4-- Column specification --------------------------------------------------------
## Delimiter: ";"
## chr (4): artist_name, track_name, key, mode
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
base3 = read_csv2('https://raw.githubusercontent.com/ricardo-js1/SEMEST-tidyverse/main/spotify_db_03.csv')
## i Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 175084 Columns: 10-- Column specification --------------------------------------------------------
## Delimiter: ";"
## chr (1): track_id
## dbl (9): acousticness, danceability, energy, speechiness, instrumentalness, ...
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.

Ao importar as bases a função retorna algumas informações sobre o separador utilizado para os decimais e os tipos de colunas que foram imputados. Para verificar a base importada podemos utilizar as funções str(), glimpse()e print(), cada uma delas apresenta a base junto de algumas informações sobre a mesma.

base1 %>% 
  str()
## spec_tbl_df [175,084 x 6] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ track_id   : chr [1:175084] "0BRjO6ga9RKCKjfDqeFgWV" "0BjC1NfoEOOusryehmNudP" "0CoSDzoNIKCRs124s9uTVy" "0Gc6TVm52BwZD07Ki6tIvf" ...
##  $ track_name : chr [1:175084] "C'est beau de faire un Show" "Perdu d'avance (par Gad Elmaleh)" "Don't Let Me Be Lonely Tonight" "Dis-moi Monsieur Gordon Cooper" ...
##  $ artist_name: chr [1:175084] "Henri Salvador" "Martin & les f<e9>es" "Joseph Williams" "Henri Salvador" ...
##  $ genre      : chr [1:175084] "Movie" "Movie" "Movie" "Movie" ...
##  $ popularity : num [1:175084] 0 1 3 0 4 0 2 15 0 10 ...
##  $ duration_ms: num [1:175084] 99373 137373 170267 152427 82625 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   track_id = col_character(),
##   ..   track_name = col_character(),
##   ..   artist_name = col_character(),
##   ..   genre = col_character(),
##   ..   popularity = col_double(),
##   ..   duration_ms = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
base2 %>% 
  glimpse()
## Rows: 175,084
## Columns: 4
## $ artist_name <chr> "Henri Salvador", "Martin & les f\xe9es", "Joseph Williams~
## $ track_name  <chr> "C'est beau de faire un Show", "Perdu d'avance (par Gad El~
## $ key         <chr> "C#", "F#", "C", "C#", "F", "C#", "C#", "F#", "C", "G", "E~
## $ mode        <chr> "Major", "Minor", "Minor", "Major", "Major", "Major", "Maj~
base3 %>% 
  print()
## # A tibble: 175,084 x 10
##    track_id               acousticness danceability energy speechiness instrumentalness
##    <chr>                         <dbl>        <dbl>  <dbl>       <dbl>            <dbl>
##  1 0BRjO6ga9RKCKjfDqeFgWV      0.611          0.389 0.91        0.0525          0      
##  2 0BjC1NfoEOOusryehmNudP      0.246          0.59  0.737       0.0868          0      
##  3 0CoSDzoNIKCRs124s9uTVy      0.952          0.663 0.131       0.0362          0      
##  4 0Gc6TVm52BwZD07Ki6tIvf      0.703          0.24  0.326       0.0395          0      
##  5 0IuslXpMROHdEPvSl1fTQK      0.95           0.331 0.225       0.0456          0.123  
##  6 0Mf1jKa8eNAf1a4PwTbizj      0.749          0.578 0.0948      0.143           0      
##  7 0NUiKYRd6jt1LKMYGkUdnZ      0.344          0.703 0.27        0.953           0      
##  8 0PbIF9YVD505GutwotpB5C      0.939          0.416 0.269       0.0286          0      
##  9 0ST6uPfvaPpJLtQwhE6KfC      0.00104        0.734 0.481       0.046           0.00086
## 10 0VSqZ3KStsjcfERGdcWpFO      0.319          0.598 0.705       0.0281          0.00125
## # ... with 175,074 more rows, and 4 more variables: liveness <dbl>,
## #   loudness <dbl>, tempo <dbl>, valence <dbl>

5 - Organizando os dados com o dplyr e tidyr

No momento as variáveis sobre as faixas estão divididas em três bases diferentes e isso não é nada prático para se trabalhar. Seria razoável unificar as três bases em uma base final, mas como podemos fazer isso? Com o pacote dplyr.

União de bases com o dplyr

O dplyr é um pacote com funções de manipulação de dados. Ele possui funções muito úteis como mutate(), select(), filter(), summarise() e arrange(), mas também diversas funções de união de bases como left_join(), right_join(), inner_join() e outras que também terminam com o sufixo _join.Cada um desses joins possui características próprias e resultam em bases finais ligeiramente diferentes, então cuidado nessa parte!

Primeiro, um exemplo mais didático do blog do Garrick Aden:

Por esse exemplo fica fácil de entender que o left_join() mantém as linhas da base a esquerda (base x) e todas as colunas presentes nas bases x e y. Esse tipo de join descartará as colunas da base y que não possuem linhas equivalentes na base x e, caso uma linha exista apenas na base x ela receberá NA nas variáveis da base y. Nesse exemplo o join utilizou como chave as colunas com os números das linhas, vamos precisar identificar nas bases do Spotify quais variáveis poderemos utilizar como chave da união.

Ainda considerando a base x na esquerda e a base y a direita, temos os outros tipos de joins:

  • right_join(): Mantém todas as linhas da base y (a direita) e todas as colunas das bases x e y. Linhas na base y sem alguma variável na base x receberão NA;
  • inner_join(): Mantém todas as linhas da base x com valores equivalentes na base y, e todas as colunas de x e y;
  • full_join(): Mantém todas as linhas e colunas das bases x e y. NA serão atribuídos para as variáveis sem valor em alguma das colunas;
  • semi_join(): Mantém apenas as linhas da base x com equivalentes na base y e apenas as colunas da base x;
  • anti_join(): Mantém apenas as linhas da base x que não possuem equivalentes na base y e apenas as colunas da base x.

Em geral os argumentos dessas funções são iguais, usando a left_join() como exemplo, teremos:

base_temp = left_join(x, # base a esquerda
                      y, # base a direita
                      by = c(), # Nome da coluna que será utilizada para parear as bases
                      suffix = c(".x", ".y"), # Sufixo que será adicionado ao nome da variável caso existam variáveis nas duas bases com o mesmo nome
                      keep = FALSE) # Argumento que indica se a variável chave das duas bases devem ser mantidas. O padrão é falso. 

Precisamos ter apenas um pouco de cuidado com o argumento by. Aqui podemos alguns cenários diferentes de como lidaremos com ele, lembrando que o nome da variávela esquerda sempre será o da base a esquerda e o da direita o da variável na base a direita:

  • As chaves possuem o mesmo nome nas duas bases: Nesse caso precisamos passar o nome da chave uma vez só: by = 'nome_chave';
  • Preciso utilizar mais de uma chave para unir as bases corretamente: Então o argumento by receberá um vetor com os nomes: by = c('chave1', 'chave2');
  • Minha(s) chave(s) tem nomes diferentes entre as bases: Aqui é parecido com o caso anterior, mas precisamos indicar o nome da variável nas duas bases: by = c('chave1_b1' ='chave1_b2'). Para indicar mais variáveis, basta adicionar uma vírgula e próximo par de nomes.
  • Sou indeciso e não quero escolher qual variável será a chave…: Se não definirmos o argumento by então a função fará (ou tentará) o join com todas as variáveis que possuírem o mesmo nome entre as duas bases. O R retornará uma mensagem dizendo quais variáveis foram utilizadas, porém isso não é uma boa prática e pode dar errado por muitos motivos.

Voltando as nossas bases, vamos olhar para as variáveis e identificar quais são os candidatos ideias para nossas chaves. Cada linha dessas bases são informações sobre uma faixa, seria razoável utilizar chaves que permitiriam identificar diretamente cada faixa. Nessa lógica, a primeira variável a chamar atenção é a track_id, como cada faixa tem sua própria identificação única seria uma chave ideal.

Tudo resolvido? Não, a nossa base2não possui a variável track_id, apenas artist_name, track_name, keye mode. Se tentarmos unir usando como chave apenas artist_name teremos problemas porque cada artista pode ter mais de uma faixa, acontece parecido com track_name já que dois artistas diferentes podem ter lançado faixas com o mesmo nome. A solução nesse caso será usar tanto artist_name quanto track_name como chaves, como feito no cenário 2 na lista acima.

Vamos utilizar uma base temporária para guardar a união intermediária.

temp = left_join(base1, base3, by = 'track_id')

E então concluímos a união com a adição da base2:

base = left_join(temp, base2, by = c('artist_name', 'track_name'))

Verificando a base final:

base %>%
  glimpse()
## Rows: 175,084
## Columns: 17
## $ track_id         <chr> "0BRjO6ga9RKCKjfDqeFgWV", "0BjC1NfoEOOusryehmNudP", "~
## $ track_name       <chr> "C'est beau de faire un Show", "Perdu d'avance (par G~
## $ artist_name      <chr> "Henri Salvador", "Martin & les f\xe9es", "Joseph Wil~
## $ genre            <chr> "Movie", "Movie", "Movie", "Movie", "Movie", "Movie",~
## $ popularity       <dbl> 0, 1, 3, 0, 4, 0, 2, 15, 0, 10, 0, 2, 4, 3, 0, 0, 0, ~
## $ duration_ms      <dbl> 99373, 137373, 170267, 152427, 82625, 160627, 212293,~
## $ acousticness     <dbl> 0.61100, 0.24600, 0.95200, 0.70300, 0.95000, 0.74900,~
## $ danceability     <dbl> 0.389, 0.590, 0.663, 0.240, 0.331, 0.578, 0.703, 0.41~
## $ energy           <dbl> 0.9100, 0.7370, 0.1310, 0.3260, 0.2250, 0.0948, 0.270~
## $ speechiness      <dbl> 0.0525, 0.0868, 0.0362, 0.0395, 0.0456, 0.1430, 0.953~
## $ instrumentalness <dbl> 0.00e+00, 0.00e+00, 0.00e+00, 0.00e+00, 1.23e-01, 0.0~
## $ liveness         <dbl> 0.3460, 0.1510, 0.1030, 0.0985, 0.2020, 0.1070, 0.105~
## $ loudness         <dbl> -1.828, -5.559, -13.879, -12.178, -21.150, -14.970, -~
## $ tempo            <dbl> 166.969, 174.003, 99.488, 171.758, 140.576, 87.479, 8~
## $ valence          <dbl> 0.8140, 0.8160, 0.3680, 0.2270, 0.3900, 0.3580, 0.533~
## $ key              <chr> "C#", "F#", "C", "C#", "F", "C#", "C#", "F#", "C", "G~
## $ mode             <chr> "Major", "Minor", "Minor", "Major", "Major", "Major",~

Selecionar, filtrar, agrupar e outros verbos

As demais funções do dplyrcobrem um grande número de atividades rotineiras como filtrar as linhas da base de dados de acordo com alguma condição das variáveis, selecionar colunas e calcular estatísticas dentro de grupos. Muitas dessas funções possuem alternativas no pacote base do R, porém nem todas essas alternativas são tão simples de se utilizar.

Veremos algumas dessas funções a seguir.

select()

Permite selecionar colunas da base de dados. A função pode receber apenas o nome das colunas, ou colunas que comecem ou terminem com a palavra especificada pelas funções starts_with()e ends_with(). Os operadores ou :, complementar !, e & e intervalo : também são aceitos para espeficar a seleção.

Primeiro, um exemplo mais simples em que selecionaremos o nome, o artista e o tempo da faixa. Utilizaremos a função names() para ver o nome das colunas resultantes em cada exemplo.

base %>%
  select(artist_name, track_id, duration_ms) %>%
  names()
## [1] "artist_name" "track_id"    "duration_ms"

Podemos utilizar o operador complementar ! para excluir variáveis da base. Vamos utilizá-lo para excluir as variáveis de nota musical e tom (keye mode).

base %>%
  select(!c(key, mode)) %>%
  names()
##  [1] "track_id"         "track_name"       "artist_name"      "genre"           
##  [5] "popularity"       "duration_ms"      "acousticness"     "danceability"    
##  [9] "energy"           "speechiness"      "instrumentalness" "liveness"        
## [13] "loudness"         "tempo"            "valence"

E por último, também podemos utilizar a função ends_with() para selecionar todas as colunas que terminam com name.

base %>%
  select(ends_with('name')) %>%
  names()
## [1] "track_name"  "artist_name"

filter()

A possibilidade de filtrar a base de dados de acordo com uma determinada característica das variáveis é muito útil. A função filter() possibilita a utilização de filtros de forma bastante simples e sendo compatível com os operadores lógicos do R.

Esta função também possui um operador especial denotado por %in% que permite filtrar uma variável categórica de acordo com um vetor de categorias. Veremos como utilizá-lo em breve.

Primeiro, vamos começar com um exemplo simples: Filtraremos a base de dados para conter apenas as faixas do gênero Rock.

base %>%
  filter(genre == 'Rock')
## # A tibble: 2,213 x 17
##    track_id  track_name    artist_name genre popularity duration_ms acousticness
##    <chr>     <chr>         <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 4kzvAGJi~ We Are The C~ Queen       Rock          50      180667     0.23    
##  2 144y07Se~ Bennie And T~ Elton John  Rock          58      361547     0.244   
##  3 49nmsafp~ The Mighty F~ Fall Out B~ Rock          53      212467     0.0612  
##  4 5mcWdITs~ One More Day  Diamond Rio Rock          55      216040     0.639   
##  5 2daZovie~ Dead Inside   Muse        Rock          61      262947     0.000259
##  6 7ptv3yXd~ (Oh) Pretty ~ Van Halen   Rock          55      172671     0.192   
##  7 0zjBoMgP~ I Get the Pi~ Mitchell T~ Rock          54      212343     0.00938 
##  8 5j6WdDC1~ Rocket Queen  Guns N' Ro~ Rock          53      375307     0.0303  
##  9 028njLMK~ Hum Halleluj~ Fall Out B~ Rock          57      230507     0.00869 
## 10 5VBzIn9c~ How Many Mor~ Led Zeppel~ Rock          54      508000     0.114   
## # ... with 2,203 more rows, and 10 more variables: danceability <dbl>,
## #   energy <dbl>, speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>,
## #   loudness <dbl>, tempo <dbl>, valence <dbl>, key <chr>, mode <chr>

Agora, vamos utilizar o operador %in% para filtrar apenas as faixas dos gêneros Pop, R&B e Eletronic.

base %>%
  filter(genre %in% c('Pop', 'R&B', 'Eletronic'))
## # A tibble: 7,701 x 17
##    track_id   track_name   artist_name genre popularity duration_ms acousticness
##    <chr>      <chr>        <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 2YegxR5As~ Be Without ~ Mary J. Bl~ R&B           65      246333       0.083 
##  2 6KFaHC9G1~ Desperado    Rihanna     R&B           63      186467       0.323 
##  3 6muW8cSjJ~ Ice On My B~ Yung Bleu   R&B           62      199520       0.0675
##  4 7yHqOZfsX~ Heaven Fall~ Surfaces    R&B           61      240597       0.36  
##  5 4XzgjxGKq~ Love Myself  Olivia O'B~ R&B           68      213947       0.596 
##  6 7KdRu0h7P~ Needs        ELHAE       R&B           61      205640       0.661 
##  7 21Ft8ME79~ Make It Out~ Nao         R&B           64      239147       0.667 
##  8 1BViPjTT5~ Seigfried    Frank Ocean R&B           66      334570       0.975 
##  9 33YFwLJbA~ Roll In Pea~ Layton Gre~ R&B           60      170343       0.72  
## 10 47TqCCnEl~ You Make Me~ Usher       R&B           69      219120       0.0359
## # ... with 7,691 more rows, and 10 more variables: danceability <dbl>,
## #   energy <dbl>, speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>,
## #   loudness <dbl>, tempo <dbl>, valence <dbl>, key <chr>, mode <chr>

O operador complementar ! é compatível com o operador %in%, agora vamos filtrar todas as faixas exceto as com gênero Pop, R&B e Eletronic.

base %>%
  filter(!genre %in% c('Pop', 'R&B', 'Eletronic'))
## # A tibble: 167,383 x 17
##    track_id  track_name    artist_name genre popularity duration_ms acousticness
##    <chr>     <chr>         <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 0BRjO6ga~ "C'est beau ~ "Henri Sal~ Movie          0       99373      0.611  
##  2 0BjC1Nfo~ "Perdu d'ava~ "Martin & ~ Movie          1      137373      0.246  
##  3 0CoSDzoN~ "Don't Let M~ "Joseph Wi~ Movie          3      170267      0.952  
##  4 0Gc6TVm5~ "Dis-moi Mon~ "Henri Sal~ Movie          0      152427      0.703  
##  5 0IuslXpM~ "Ouverture"   "Fabien Na~ Movie          4       82625      0.95   
##  6 0Mf1jKa8~ "Le petit so~ "Henri Sal~ Movie          0      160627      0.749  
##  7 0NUiKYRd~ "Premi\xe8re~ "Martin & ~ Movie          2      212293      0.344  
##  8 0PbIF9YV~ "Let Me Let ~ "Laura May~ Movie         15      240067      0.939  
##  9 0ST6uPfv~ "Helka"       "Chorus"    Movie          0      226200      0.00104
## 10 0VSqZ3KS~ "Les bisous ~ "Le Club d~ Movie         10      152694      0.319  
## # ... with 167,373 more rows, and 10 more variables: danceability <dbl>,
## #   energy <dbl>, speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>,
## #   loudness <dbl>, tempo <dbl>, valence <dbl>, key <chr>, mode <chr>

Também podemos utilizar filtros compostos. Por exemplo, podemos filtrar as músicas do gênero Rock e com letras mais negativas (o que é indicado para valores baixos na variável valence).

base %>% 
  filter(genre == 'Rock' & valence <= 0.2)
## # A tibble: 240 x 17
##    track_id  track_name    artist_name genre popularity duration_ms acousticness
##    <chr>     <chr>         <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 5jOQ5v49~ "Plans"       Oh Wonder   Rock          56      235671     0.844   
##  2 1ZzehTXQ~ "Pitchfork K~ AJR         Rock          53      212760     0.0111  
##  3 5VVWgWH8~ "Stockholm S~ Muse        Rock          61      297000     0.00187 
##  4 6IM45SqA~ "Why Won't T~ Tame Impala Rock          57      286093     0.619   
##  5 2qdVHdkL~ "You Were Mi~ Dixie Chic~ Rock          55      217807     0.342   
##  6 4q26Viix~ "My Only Swe~ El Ten Ele~ Rock          53      314587     0.109   
##  7 00oZhqZI~ "Tomorrow Ne~ The Beatles Rock          58      179547     0.000084
##  8 0xIZPPBf~ "Black Sabba~ Black Sabb~ Rock          50      378227     0.0467  
##  9 1jfcwpvd~ "Somewhere B~ Cody Jinks  Rock          53      215840     0.00606 
## 10 6z1frj1J~ "Are You Wit~ Easton Cor~ Rock          55      220347     0.314   
## # ... with 230 more rows, and 10 more variables: danceability <dbl>,
## #   energy <dbl>, speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>,
## #   loudness <dbl>, tempo <dbl>, valence <dbl>, key <chr>, mode <chr>

group_by(), ungroup(), summarise() e mutate()

Algumas vezes é interessante calcular estatísticas descritivas dentro de categorias de uma variável categórica. A função group_by()permite utilizar uma ou mais variáveis categóricas de modo que as funções que venham na sequência sejam calculadas dentro destes grupos. A função ungroup() deve sempre ser utilizada ao fim dos cálculos envolvendo os grupos para evitar que ocorram resultados inesperados.

Sozinhas estas duas funções não fazem muito, seu uso principal envolve as funções summarise() e mutate(). A principal diferença entre essas duas funções é que a primeira criará uma nova base de dados com as informações resumidas, já a segunda adicionará uma nova variável a base de dados atual.

Para deixar isso mais claro, vamos avaliar o seguinte exemplo: Iremos utilizar as funções para calcular a valência (valence) média por gênero musical.

Primeiro, faremos isso com a função mutate():

base %>% 
  group_by(genre) %>% 
  mutate(med_val = mean(valence, na.rm = TRUE)) %>% 
  select(genre, med_val) %>% # selecionando as colunas aqui só para deixar o resultado mais claro
  ungroup()
## # A tibble: 175,084 x 2
##    genre med_val
##    <chr>   <dbl>
##  1 Movie   0.448
##  2 Movie   0.448
##  3 Movie   0.448
##  4 Movie   0.448
##  5 Movie   0.448
##  6 Movie   0.448
##  7 Movie   0.448
##  8 Movie   0.448
##  9 Movie   0.448
## 10 Movie   0.448
## # ... with 175,074 more rows

A variável med_val representa a valência média em cada grupo, porém sua construção dentro da função mutate() faz com que cada linha receba o valor da média de seu respectivo gênero. Isso é pouco produtivo e gera muita informação repetida dentro da base de dados (isso será problemático para bases gigantescas ou quando o computador possui pouca memória disponível).

Agora, vamos repetir esse cálculo utilizando a função summarise():

base %>% 
  group_by(genre) %>% 
  summarise(med_val = mean(valence, na.rm = T)) %>% 
  ungroup()
## # A tibble: 26 x 2
##    genre            med_val
##    <chr>              <dbl>
##  1 A Capella          0.329
##  2 Alternative        0.449
##  3 Anime              0.442
##  4 Blues              0.580
##  5 Children's Music   0.676
##  6 Classical          0.216
##  7 Comedy             0.413
##  8 Country            0.535
##  9 Dance              0.518
## 10 Electronic         0.385
## # ... with 16 more rows

Calculando deste jeito o resultado será uma base de dados em que o número de colunas será o número de variáveis utilizadas para agrupar na função group_by() mais uma coluna para cada estatística resumo calculada na função summarise(). O número de linhas será dado pelo número de combinações entre os grupos, ou pelo número de grupos na variável caso seja utilizada apenas uma.

Vamos ver como fica um exemplo um pouco mais complexo agora: Obteremos algumas médias de variáveis agrupadas pelo tom da faixa e gênero musical.

base %>% 
  group_by(genre, mode) %>% 
  summarise(med_valence = mean(valence, na.rm = T),
            med_enegy = mean(energy, na.rm = T),
            med_tempo = mean(tempo, na.rm = T)) %>% 
  ungroup()
## `summarise()` has grouped output by 'genre'. You can override using the
## `.groups` argument.
## # A tibble: 52 x 5
##    genre            mode  med_valence med_enegy med_tempo
##    <chr>            <chr>       <dbl>     <dbl>     <dbl>
##  1 A Capella        Major       0.358     0.265      110.
##  2 A Capella        Minor       0.250     0.212      116.
##  3 Alternative      Major       0.448     0.714      123.
##  4 Alternative      Minor       0.451     0.714      121.
##  5 Anime            Major       0.453     0.671      128.
##  6 Anime            Minor       0.423     0.656      125.
##  7 Blues            Major       0.582     0.599      121.
##  8 Blues            Minor       0.576     0.613      121.
##  9 Children's Music Major       0.683     0.394      121.
## 10 Children's Music Minor       0.627     0.415      120.
## # ... with 42 more rows

Um uso bem típico para a função summarise()é feito para contar a quantidade de observações dentro de cada grupo. Isto é feito ao utilizar a função n() dentro da summarise()após definir os grupos.

Vamos contar o número de faixas por gênero musical neste exemplo.

base %>% 
  group_by(genre) %>% 
  summarize(freq = n())
## # A tibble: 26 x 2
##    genre             freq
##    <chr>            <int>
##  1 A Capella          119
##  2 Alternative       9090
##  3 Anime             8935
##  4 Blues             8464
##  5 Children's Music  5400
##  6 Classical         8703
##  7 Comedy            9674
##  8 Country           7379
##  9 Dance             7965
## 10 Electronic        9149
## # ... with 16 more rows

Alternativamente, isso pode ser calculado a partir da função tally(). Esta função também possui a versão add_tally() que seria equivalente a calcular a frequência dos grupos utilizando a função mutate().

base %>% 
  group_by(genre) %>% 
  tally(name = 'freq')
## # A tibble: 26 x 2
##    genre             freq
##    <chr>            <int>
##  1 A Capella          119
##  2 Alternative       9090
##  3 Anime             8935
##  4 Blues             8464
##  5 Children's Music  5400
##  6 Classical         8703
##  7 Comedy            9674
##  8 Country           7379
##  9 Dance             7965
## 10 Electronic        9149
## # ... with 16 more rows

A função mutate() possui um uso mais interessante do que calcular estatísticas resumo que se repetirão a cada linha. Para isso tomaremos como exemplo a variável duration_ms que apresenta a duração de cada faixa em milissegundos. Um milissegundo é equivalente a 0,001 segundos, podemos simplificar isso e criar uma variável de duração em minutos para música utilizando a função mutate().

base = base %>% 
  mutate(duration_min = duration_ms / (1000 * 60))

base %>% select(duration_ms, duration_min)
## # A tibble: 175,084 x 2
##    duration_ms duration_min
##          <dbl>        <dbl>
##  1       99373         1.66
##  2      137373         2.29
##  3      170267         2.84
##  4      152427         2.54
##  5       82625         1.38
##  6      160627         2.68
##  7      212293         3.54
##  8      240067         4.00
##  9      226200         3.77
## 10      152694         2.54
## # ... with 175,074 more rows

arrange()

Para finalizar sobre o dplyriremos conhecer a função arrange(). No exemplo em que contamos o número de linhas por gênero musical a base resultante não possuía nenhum ordenamento em particular. Em algus casos é interessante ordenar os dados de forma crescente ou decrescente para tornar os resultados mais intuitivos e é aí que a função arrange()entra.

base %>% 
  group_by(genre) %>% 
  summarize(freq = n()) %>% 
  arrange(freq)
## # A tibble: 26 x 2
##    genre             freq
##    <chr>            <int>
##  1 A Capella          119
##  2 Rap               1455
##  3 Rock              2213
##  4 Pop               2408
##  5 Indie             3310
##  6 Soul              4395
##  7 R&B               5293
##  8 Children's Music  5400
##  9 Country           7379
## 10 Hip-Hop           7402
## # ... with 16 more rows

Por padrão a função ordenará em ordem crescente, para a ordem decrescente isso é feito da seguinte forma:

base %>% 
  group_by(genre) %>% 
  summarize(freq = n()) %>% 
  arrange(desc(freq))
## # A tibble: 26 x 2
##    genre        freq
##    <chr>       <int>
##  1 Comedy       9674
##  2 Electronic   9149
##  3 Alternative  9090
##  4 Anime        8935
##  5 Classical    8703
##  6 Reggae       8687
##  7 Reggaeton    8548
##  8 Soundtrack   8478
##  9 Blues        8464
## 10 Opera        8280
## # ... with 16 more rows

Pivoteando com o tidyr

Os pacotes do tidyverse foram feitos para utilizar dados no formato tidy. Este formato é definido por três regras:

  • Cada variável deve ter sua própria coluna;
  • Cada observação deve ter sua própria linha;
  • Cada valor deve ter sua própria célula.

O tidyr possui funções para arrumar bases de dados bagunçadas e deixá-las no formato aceito pelo tidyverse. Geralmente as funções esperam dados no formato wide, em que cada variável tem sua própria coluna, ou long, em que cada linha representa uma observação de uma categoria. Para simplificar, o formato wide é mais horizontal e o long é mais vertical.

Isso é especialmente importante ao se trabalhar com o ggplot2, alguns tipos de gráficos dependem que as variáveis estejam organizadas de um jeito específico.

A base do Spotify já está em formato wide e é relativamente bem comportada, mas o que faríamos se fosse uma base mais bagunçada? O tidyr possui as funções pivot_longer() e pivot_wider()para pivotear bases entre os dois tipos de formato.

Essas funções funcionam da seguinte forma:

A função pivot_longer() transforma uma base de dados em formato wide para o formato long, a função pivot_wider() faz a transformação oposta. Naturalmente se aplicamos as duas funções em sequência a uma base de dados teremos a base final com o mesmo formato da base inicial.

O uso das duas funções é bem parecido: é necessário definir as colunas que serão pivoteadas por meio dos argumentos ,cols, names_to e values_topara a pivot_longer()ou id_cols,names_from e values_from para a pivot_wider(). Para a pivot_longer() estes argumentos indicam o nome da coluna que será criada para guardar os nomes das variáveis e o valor, respectivamente. Para a pivot_wider() é parecido, estes argumentos indicam quais variáveis serão utilizadas para criar as novas colunas e de onde os valores virão.

Vamos utilizar uma base reduzida para ilustrar o funcionamento destas funções. Utilizaremos Os Beatles e as variáveis valênia e track_name.

Esta base atual será uma base em formato long:

base %>% 
  filter(artist_name == 'The Beatles') %>% 
  select(valence, track_name)
## # A tibble: 145 x 2
##    valence track_name                            
##      <dbl> <chr>                                 
##  1  0.879  Baby It's You - Remastered 2009       
##  2  0.516  Things We Said Today - Remastered 2009
##  3  0.632  The Night Before - Remastered 2009    
##  4  0.0534 Tomorrow Never Knows - Remastered 2009
##  5  0.664  Hello, Goodbye - Remastered 2015      
##  6  0.879  All I've Got To Do - Remastered 2009  
##  7  0.49   The Fool On The Hill - Remastered 2009
##  8  0.183  Come Together - Remastered 2015       
##  9  0.968  From Me To You - Mono / Remastered    
## 10  0.676  All You Need Is Love - Remastered 2015
## # ... with 135 more rows

Agora, utilizaremos a função pivot_wider() para deixá-la no formato wide:

base %>% 
  filter(artist_name == 'The Beatles') %>% 
  select(valence, track_name) %>%
  pivot_wider(names_from = track_name, values_from = valence)
## # A tibble: 1 x 145
##   `Baby It's You - R~ `Things We Said To~ `The Night Before~ `Tomorrow Never Kn~
##                 <dbl>               <dbl>              <dbl>               <dbl>
## 1               0.879               0.516              0.632              0.0534
## # ... with 141 more variables: Hello, Goodbye - Remastered 2015 <dbl>,
## #   All I've Got To Do - Remastered 2009 <dbl>,
## #   The Fool On The Hill - Remastered 2009 <dbl>,
## #   Come Together - Remastered 2015 <dbl>,
## #   From Me To You - Mono / Remastered <dbl>,
## #   All You Need Is Love - Remastered 2015 <dbl>,
## #   Yellow Submarine - Remastered 2015 <dbl>, ...

Notem que cada faixa virou sua própria coluna com o valor da variável valência na primeira linha. Por fim, vamos utilizar a função pivot_longer()para retornar ao formato da base original. Neste caso o argumento colsreceberá os índices das colunas.

base %>% 
  filter(artist_name == 'The Beatles') %>% 
  select(valence, track_name) %>%
  pivot_wider(values_from = valence, names_from = track_name) %>% 
  pivot_longer(names_to = 'artist_name', values_to  = 'valence', cols = 1:145)
## # A tibble: 145 x 2
##    artist_name                            valence
##    <chr>                                    <dbl>
##  1 Baby It's You - Remastered 2009         0.879 
##  2 Things We Said Today - Remastered 2009  0.516 
##  3 The Night Before - Remastered 2009      0.632 
##  4 Tomorrow Never Knows - Remastered 2009  0.0534
##  5 Hello, Goodbye - Remastered 2015        0.664 
##  6 All I've Got To Do - Remastered 2009    0.879 
##  7 The Fool On The Hill - Remastered 2009  0.49  
##  8 Come Together - Remastered 2015         0.183 
##  9 From Me To You - Mono / Remastered      0.968 
## 10 All You Need Is Love - Remastered 2015  0.676 
## # ... with 135 more rows

É um exemplo bem simples, mas mostra um pouco da utilidade dessas funções. Para bases realmente bagunçadas pode ser necessário usar os dois tipos de pivoteamento seguidos para se obter a base final no formato necessário. Essa página no CRAN apresenta alguns exemplos mais complexos de utilização dessas funções.

6 - Manipulando strings com o stringr

A habilidade de manipular texto pode ser muito útil na hora de organizar bases de dados. É comum encontrar bases que foram preenchidas manualmente por várias pessoas diferentes e as coisas logo começam a ficar complicadas quando você descobrir que João escrevia o local como Rio de Janeiro, Maria como RJ e por aí vai, sem contar as vezes que digitaram o nome errado.

O pacote stringr possui diversas funções de manipulação de texto, a partir dele é possível converter todas as letras para maiúsculas ou minúsculas, detectar a presença de padrões de letras dentro do texto e realizar substituições. Este pacote utiliza as chamadas regular expressions (regex) para identiicação dos padrões de palavras e este é um tema complexo o suficiente para ter um minicurso por si só. Por isso desta vez vamos nos manter com as funções mais acessíveis do stringr.

Primeiro, as funções str_to_upper(), str_to_lower(), str_to_title() e str_to_sentence()permitem converter o texto para maiúsculo, minúsculo, apenas com a primeira letra de cada palavra como maiúscula e com apenas a primeira letra da string em maiúscula.

Vamos testar essas funções com os nomes das músicas.

base %>% 
  select(track_name) %>% 
  mutate(
    titulo_upper = str_to_upper(track_name),
    titulo_lower = str_to_lower(track_name),
    titulo_title = str_to_title(track_name),
    titulo_sentence = str_to_sentence(track_name)
  )
## # A tibble: 175,084 x 5
##    track_name    titulo_upper    titulo_lower   titulo_title   titulo_sentence  
##    <chr>         <chr>           <chr>          <chr>          <chr>            
##  1 "C'est beau ~ "C'EST BEAU DE~ "c'est beau d~ "C'est Beau D~ "C'est beau de f~
##  2 "Perdu d'ava~ "PERDU D'AVANC~ "perdu d'avan~ "Perdu D'avan~ "Perdu d'avance ~
##  3 "Don't Let M~ "DON'T LET ME ~ "don't let me~ "Don't Let Me~ "Don't let me be~
##  4 "Dis-moi Mon~ "DIS-MOI MONSI~ "dis-moi mons~ "Dis-Moi Mons~ "Dis-moi monsieu~
##  5 "Ouverture"   "OUVERTURE"     "ouverture"    "Ouverture"    "Ouverture"      
##  6 "Le petit so~ "LE PETIT SOUP~ "le petit sou~ "Le Petit Sou~ "Le petit souper~
##  7 "Premi\xe8re~ "PREMI\xe8RES ~ "premi\xe8res~ "Premi\xe8Res~ "Premi\xe8res re~
##  8 "Let Me Let ~ "LET ME LET GO" "let me let g~ "Let Me Let G~ "Let me let go"  
##  9 "Helka"       "HELKA"         "helka"        "Helka"        "Helka"          
## 10 "Les bisous ~ "LES BISOUS DE~ "les bisous d~ "Les Bisous D~ "Les bisous des ~
## # ... with 175,074 more rows

Para detectar a presença de um padrão dentro de uma string podemos utilizar a função str_detect(). Esta função pode ser utilizada junto com a função filter() do pacote dplyr para filtrar apenas as linhas que contenham um determinado padrão nas variáveis de texto.

No próximo exemplo vamos filtrar a base para manter apenas as linhas com love no título das faixas.

base %>%
  filter(str_detect(track_name, 'love'))
## # A tibble: 125 x 18
##    track_id   track_name   artist_name genre popularity duration_ms acousticness
##    <chr>      <chr>        <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 2Y1TGD05Z~ "Papa loves~ Henri Salv~ Movie          0      201587     0.667   
##  2 1To829onY~ "4 Leaf Clo~ Ravyn Lenae Alte~         55      220101     0.0145  
##  3 6ZUdUi3qB~ "Beloved"    Jordan Fel~ Alte~         49      240453     0.00497 
##  4 0GIDR7iLJ~ "This Velve~ Red Hot Ch~ Alte~         54      225200     0.0706  
##  5 4qAPq3U8G~ "Goodlove"   Justine Sk~ Alte~         50      249227     0.0894  
##  6 2cgf6SSsS~ "Tremble Fo~ Collective~ Alte~         41      232560     0.000332
##  7 3JeT6Xcv6~ "lovers\x92~ Bibio       Dance         60      238640     0.68    
##  8 6HcQZfMrw~ "Dreamlover" Mariah Car~ Dance         56      232960     0.18    
##  9 6SaeXxdt0~ "Fastlove, ~ George Mic~ Dance         63      324667     0.052   
## 10 71JQ4eRRt~ "sea of lov~ Christina ~ Dance         49      215387     0.538   
## # ... with 115 more rows, and 11 more variables: danceability <dbl>,
## #   energy <dbl>, speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>,
## #   loudness <dbl>, tempo <dbl>, valence <dbl>, key <chr>, mode <chr>,
## #   duration_min <dbl>

Os operadores do regex ^ e $indicam que a busca deve ser por trechos que comecem ou terminem com o padrão, respectivamente.

base %>% 
  filter(str_detect(track_name, '^love'))
## # A tibble: 11 x 18
##    track_id   track_name   artist_name genre popularity duration_ms acousticness
##    <chr>      <chr>        <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 3JeT6Xcv6~ "lovers\x92~ Bibio       Dance         60      238640      0.68   
##  2 3XTOmBAnu~ "love (wip)" San Holo    Elec~         53      262687      0.193  
##  3 7lY3juj1M~ "love (ain<~ isaac grac~ Folk          56      224813      0.93   
##  4 6wcRv0UBv~ "love"       DEAN        R&B           59      258227      0.00125
##  5 0ltO5bci1~ "love someb~ joan        R&B           57      227631      0.0596 
##  6 1Znlc3UZA~ "love yours~ XXXTENTACI~ Hip-~         69       48423      0.674  
##  7 2WX8QMtZV~ "love me fo~ Lil Yachty  Hip-~         52      104075      0.0924 
##  8 3bjLCKsBN~ "love ride"  Christian ~ Indie         62      234192      0.206  
##  9 3jjUphDBl~ "love gang ~ Whethan     Indie         51      178515      0.122  
## 10 7L3r32qd8~ "love n hat~ B0nds       Jazz           2       51981      0.288  
## 11 0Yqsb1WKT~ "love the r~ Zachary Kn~ Soul          36      304030      0.259  
## # ... with 11 more variables: danceability <dbl>, energy <dbl>,
## #   speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>, loudness <dbl>,
## #   tempo <dbl>, valence <dbl>, key <chr>, mode <chr>, duration_min <dbl>
base %>%
  filter(str_detect(track_name, 'love$'))
## # A tibble: 19 x 18
##    track_id  track_name    artist_name genre popularity duration_ms acousticness
##    <chr>     <chr>         <chr>       <chr>      <dbl>       <dbl>        <dbl>
##  1 0GIDR7iL~ This Velvet ~ Red Hot Ch~ Alte~         54      225200     0.0706  
##  2 4qAPq3U8~ Goodlove      Justine Sk~ Alte~         50      249227     0.0894  
##  3 32K03Gjk~ motherlove    Bea Miller  Dance         54      185747     0.00114 
##  4 6qpaTfe6~ Power Glove   Knife Party Dance         53      261245     0.000105
##  5 1uTXtV2r~ Superlove     Tinashe     Dance         52      183792     0.0148  
##  6 63PjUXm7~ self-love     San Holo    Elec~         62      143775     0.0128  
##  7 4GA6Iljg~ Summer love   Kobasolo    Anime         38      267865     0.331   
##  8 1XwChHbX~ Arrow of love GARNiDELiA  Anime         23      219680     0.71    
##  9 3Bny9WzD~ Koisuruotome~ Kobasolo    Anime         18      251520     0.534   
## 10 7FzFtbkN~ closest love  fripSide    Anime         20      275680     0.0138  
## 11 5o3Ry6h5~ Caligulove    Them Crook~ Blues         36      295280     0.00473 
## 12 6wcRv0UB~ love          DEAN        R&B           59      258227     0.00125 
## 13 0kaSpCet~ show me love  isaac grac~ Folk          57      197133     0.00785 
## 14 6zHyWsqT~ Dontmakemefa~ Cuco        Pop           63      207692     0.373   
## 15 3H6MaJHf~ A Heartfelt ~ Wyatt Cenac Come~         23      253777     0.879   
## 16 3oP7AI2q~ You'll Go Bl~ Gilbert Go~ Come~         15      165987     0.828   
## 17 74OLXYsv~ bitterlove    Ardhito Pr~ Jazz          57      216265     0.393   
## 18 0piJU93a~ thelove       Jitwam      Jazz          34      163928     0.152   
## 19 4nWfJBA1~ Wilderlove    John Mark ~ World         43      281206     0.00384 
## # ... with 11 more variables: danceability <dbl>, energy <dbl>,
## #   speechiness <dbl>, instrumentalness <dbl>, liveness <dbl>, loudness <dbl>,
## #   tempo <dbl>, valence <dbl>, key <chr>, mode <chr>, duration_min <dbl>

A função str_replace() o padrão especificado por um novo padrão. Neste exemplo vamos substituir a palavra love no nome das faixas por LOOOVE.

base %>% 
  filter(str_detect(track_name, 'love')) %>% 
  mutate(
    track_name = str_replace(track_name, 'love', 'LOOOVE')
  ) %>% 
  select(track_name)
## # A tibble: 125 x 1
##    track_name                        
##    <chr>                             
##  1 Papa LOOOVEs mambo                
##  2 4 Leaf CLOOOVEr (feat. Steve Lacy)
##  3 BeLOOOVEd                         
##  4 This Velvet GLOOOVE               
##  5 GoodLOOOVE                        
##  6 Tremble For My BeLOOOVEd          
##  7 LOOOVErs<U+FFFD> carvings                
##  8 DreamLOOOVEr                      
##  9 FastLOOOVE, Pt. 1                 
## 10 sea of LOOOVErs                   
## # ... with 115 more rows

Obviamente o padrão a ser substituído deve ser definido com mais cuidado, mas fica como exemplo das possibilidades do pacote.

A função str_replace()substitui apenas o primeiro padrão encontrado na string, para os casos em que se deseja substituir todas as vezes que o padrão aparece deve se utilizar a funçaõ str_replace_all().

7 - Gráficos com o ggplot2 feat. forcats

O ggplot2 é a opção do tidyverse para construção de gráficos. Sua principal vantagem é a possibilidade de construir gráficos complexos com uma sintaxe simples, porém com a desvantagem de ser ligeiramente mais complexo que a função plot para construção de gráficos mais simples. O pacote funciona com uma estrutura de camadas de geometria, os geom_, em que cada nova camada é adicionada sobre as camadas anteriores (então a ordem de como definimos os elementos do gráfico importa!).

O site ggplot2tor possui uma lista interativa das geometrias do ggplot2, incluindo os argumentos disponíveis para cada uma delas e identificando quais são obrigatórios e quais são opcionais para construção dos gráficos. A especificação das variáveis utilizadas pela geometria são especificadas dentro do argumento aes(), de aesthetic (estética).

Todo gráfico feito com o ggplot2 começa pela função `ggplot(). As camadas seguintes serão adicionadas pelo operador +, então para construir um gráfico de barras simples utilizaremos a função geom_bar(). Caso não especifiquemos o eixo y a função contará a frequência da variável especificada em x, também podemos especificar y como a frequência, mas então precisaremos do argumento stat = 'identity'. Também utilizaremos a função coord_flip() para girar o gráfico e melhorar a visualização dos nomes dos gêneros musicais.

base %>%
  group_by(genre) %>%
  tally() %>% 
  ggplot() +
  geom_bar(aes(x = genre, y = n), stat = 'identity') + 
  coord_flip()

Agora, utilizaremos os argumentos colore fillpara definir a cor das bordas das barras e a cor interna, respectivamente.

base %>%
  group_by(genre) %>%
  tally() %>% 
  ggplot() +
  geom_bar(aes(x = genre, y = n), stat = 'identity', color = 'black', fill = 'darkred') + 
  coord_flip()

Ainda podemos ir além, agora utilizaremos a função labs()para definir o título dos eixos e do gráfico. Isto é feito utilizando os argumentos x, y e labs.

base %>%
  group_by(genre) %>%
  tally() %>% 
  ggplot() +
  geom_bar(aes(x = genre, y = n), stat = 'identity', color = 'black', fill = 'darkred') + 
  coord_flip() +
  labs(y = 'Frequência', x = 'Gênero musical', title = 'Frequência dos gêneros musicais')

Uma adição útil a este tipo de gráfico são etiquetas com a frequência da categoria. Isto pode ser adicionado pela função geom_text(), esta função recebe como argumentos dentro do aes() a variável do eixos x e ye o valor da etiqueta no argumento label. Também utilizaremos o argumento nudge_y para aumentar o afastamento entre as etiquetas e a barra. Notem que os argumentos que não envolvem variáveis devem ser passados fora do aes().

base %>%
  group_by(genre) %>%
  tally() %>% 
  ggplot() +
  geom_bar(aes(x = genre, y = n), stat = 'identity', color = 'black', fill = 'darkred') + 
  geom_text(aes(x = genre, y = n, label = n), nudge_y = 400) + 
  coord_flip() +
  labs(y = 'Frequência', x = 'Gênero musical', title = 'Frequência dos gêneros musicais')

Neste tipo de gráfico é util ordenar as barras em ordem decrescente pela frequência, mas como fazer isso? O pacote forcatspossui inúmeras funções para lidar com fatores, uma delas é a fct_reorder()e permite reordenar os grupos de uma variável categórica de acordo com os valores de uma outra variável.

base %>%
  group_by(genre) %>%
  tally() %>% 
  ggplot() +
  geom_bar(aes(x = fct_reorder(genre, n), y = n), stat = 'identity', color = 'black', fill = 'darkred') + 
  geom_text(aes(x = genre, y = n, label = n), nudge_y = 400) + 
  coord_flip() +
  labs(y = 'Frequência', x = 'Gênero musical', title = 'Frequência dos gêneros musicais')

O ggplot2 também possui diversos temas para os gráficos. Os temas básicos incorporados com o pacote podem ser vistos neste link, mas existem pacotes como o ggthemes e o ggthemr com opções bastante interessantes. Por ora, vamos adicionar o tema clássico ao nosso gráfico com a função theme_classic().

base %>%
  group_by(genre) %>%
  tally() %>% 
  ggplot() +
  geom_bar(aes(x = fct_reorder(genre, n), y = n), stat = 'identity', color = 'black', fill = 'darkred') + 
  geom_text(aes(x = genre, y = n, label = n), nudge_y = 400) + 
  coord_flip() +
  labs(y = 'Frequência', x = 'Gênero musical', title = 'Frequência dos gêneros musicais') +
  theme_classic()

Antes de continuar explorando as opções nos temas, vamos começar um segundo gráfico. Desta vez faremos um gráfico de dispersão entre a valência, a positividade medida nas faixas, e a energia. Obviamente temos um número muito grande de faixas, então começaremos apenas com a banda Foo Fighters (ou a banda que quiserem, caso queiram mudar).

O gráfico de dispersão é feito com a função geom_point(), esta função receberá a variável energia em x e valência em y.

base %>% 
  filter(artist_name == 'Foo Fighters') %>% 
  ggplot() +
  geom_point(aes(x = energy, y = valence)) +
  theme_light()

É um começo, agora vamos deixar as coisas mais interessantes e adicionar mais artistas. Dessa vez vamos incluir a Katy Perry, os Beatles e o Justin Timberlake. Dessa vez iremos adicionar o argumento colordentro da aes() para colorir cada observação de acordo com sua banda.

base %>% 
  filter(artist_name %in% c('Foo Fighters', 'The Beatles', 'Katy Perry', 'Justin Timberlake')) %>% 
  ggplot() +
  geom_point(aes(x = energy, y = valence, color = artist_name)) +
  theme_light()

Já sabemos que podemos utilizar a função labs() para nomear os eixos e o título do gráfico. O ggplot2 tem como padrão utilizar o nome da variável como nome da legenda, mas podemos mudar isso também. No nosso caso utilizaremos a função scale_color_discrete(), mas essa está longe de ser a única opção disponível no pacote. Este link apresenta uma ferramenta interativa para selecionar as escalas de acordo com o tipo de variável utilizado e se utilizamos uma escala para cor (color) ou preenchimento (fill).

base %>% 
  filter(artist_name %in% c('Foo Fighters', 'The Beatles', 'Katy Perry', 'Justin Timberlake')) %>% 
  ggplot() +
  geom_point(aes(x = energy, y = valence, color = artist_name)) +
  theme_light() +
  labs(x = 'Energia', y = 'Valência', title = 'Valência x Energia por Artista') +
  scale_color_discrete(name = 'Artista')

A função scale_color_discrete() utiliza a escala de cor padrão do ggplot2, mas também é possível definirmos uma escala de cor manualmente por meio do argumento values na função scale_color_manual(). Este argumento receberá um vetor de cores (nomes ou códigos hexadecimais) com tamanho equivalente ao número de categorias da variável utilizada no argumento color.

Vamos aproveitar e também aumentar o tamanho dos pontos por meio do argumento size na função geom_point(). Como há pontos se sobrepondo também iremos adicionar transparência nesses pontos por meio do argumento alpha.

base %>% 
  filter(artist_name %in% c('Foo Fighters', 'The Beatles', 'Katy Perry', 'Justin Timberlake')) %>% 
  ggplot() +
  geom_point(aes(x = energy, y = valence, color = artist_name), size = 4, alpha = 0.6) +
  theme_light() +
  labs(x = 'Energia', y = 'Valência', title = 'Valência x Energia por Artista') +
  scale_color_manual(name = 'Artista', values = c('#009392', '#9ccb86', '#eeb479', '#cf597e'))

Já vimos como adicionar etiquetas aos gráficos, como alterar o nome dos eixos e da legenda, mas e como faríamos para alterar o tamanho da fonte? Todos os temas do ggplot2 são definidos a partir da função theme() e ela pode ser utilizada para manipular diretamente qualquer parte de um gráfico feito com o pacote. Este link apresenta um guia sobre como estes elementos são nomeados e como podem ser manipulados.

Vamos aproveitar o último gráfico e alterar o tamanho da fonte do título, da legenda e dos eixos. Para isso utilizaremos os argumentos plot.title, legend.title, axis.title.x e axis.title.y para indicar quais elementos queremos manipular. Esses argumentos receberão a função element_text() que possui argumentos para alterar o tamanho, cor, ângulo, fonte, entre outros.

Nesta parte é importante lembrar que o ggplot2 funciona em camadas empilhadas, então se iremos manipular o tema manualmente essas manipulações devem ser adicionadas como a última camada. Caso adicionemos um tema pronto após manipular, nossas manipulações serão sobrepostas.

base %>% 
  filter(artist_name %in% c('Foo Fighters', 'The Beatles', 'Katy Perry', 'Justin Timberlake')) %>% 
  ggplot() +
  geom_point(aes(x = energy, y = valence, color = artist_name), size = 4, alpha = 0.6) +
  theme_light() +
  labs(x = 'Energia', y = 'Valência', title = 'Valência x Energia por Artista') +
  scale_color_manual(name = 'Artista', values = c('#009392', '#9ccb86', '#eeb479', '#cf597e')) +
  theme(
    plot.title = element_text(size = 25, face = 'bold'),
    axis.title.x = element_text(size = 18, face = 'bold'),
    axis.title.y = element_text(size = 18, face = 'bold'),
    legend.title = element_text(size = 15, face = 'bold')
  )

A função theme()também permite manipular a posição da legenda no gráfico. Isso é feito através do argumento legend.position, o padrão aqui é right, mas também aceita bottom, top e left. Vamos colocar nossa legenda na parte de baixo do gráfico.

base %>% 
  filter(artist_name %in% c('Foo Fighters', 'The Beatles', 'Katy Perry', 'Justin Timberlake')) %>% 
  ggplot() +
  geom_point(aes(x = energy, y = valence, color = artist_name), size = 4, alpha = 0.6) +
  theme_light() +
  labs(x = 'Energia', y = 'Valência', title = 'Valência x Energia por Artista') +
  scale_color_manual(name = 'Artista', values = c('#009392', '#9ccb86', '#eeb479', '#cf597e')) +
  theme(
    plot.title = element_text(size = 25, face = 'bold'),
    axis.title.x = element_text(size = 18, face = 'bold'),
    axis.title.y = element_text(size = 18, face = 'bold'),
    legend.title = element_text(size = 15, face = 'bold'),
    legend.position = 'bottom'
  )

Viram só? Com um pouco de cuidado na manipulação dos temas é possível se afastar bastante desses gráficos básicos do ggplot2. Um outro pacote muito útil para temas do ggplot2é o bbplot que trás temas baseados nos gráficos da BBC. Se o pessoal da BBC achou bom fazer isso, quem sou eu para discordar?

8 - E o que podemos fazer com tudo isso junto?

Como vimos, o tidyversepossui pacotes para cada função dentro do fluxo de análise de dados. Nós importamos bases de dados, as organizamos, calculamos estatísticas descritivas e fizemos gráficos. Isso já permite responder muitas questões que possam surgir sobre uma base de dados.

Nesta seção investigaremos um pouco mais essa base, porém de um jeito mais direto ao ponto.

Quantas vezes cada banda apareceu nessa base de dados e quanto representam no total?

base %>% 
  group_by(artist_name) %>% 
  tally(sort = T) %>% 
  mutate(prop = n/sum(n),
         prop = paste0(round(prop * 100, 3),'%')) %>% 
  rename(Frequência = n,
         Proporção = prop,
         Artista = artist_name) %>% 
  ungroup()
## # A tibble: 14,443 x 3
##    Artista                 Frequência Proporção
##    <chr>                        <int> <chr>    
##  1 Giuseppe Verdi                1312 0.749%   
##  2 Giacomo Puccini               1094 0.625%   
##  3 Kimbo Children's Music         971 0.555%   
##  4 Wolfgang Amadeus Mozart        800 0.457%   
##  5 Richard Wagner                 778 0.444%   
##  6 Nobuo Uematsu                  773 0.442%   
##  7 Juice Music                    684 0.391%   
##  8 Georges Bizet                  677 0.387%   
##  9 Randy Newman                   667 0.381%   
## 10 Johann Sebastian Bach          632 0.361%   
## # ... with 14,433 more rows

Qual é o gênero musical com maior dançabilidade?

base %>% 
  group_by(genre) %>% 
  summarise(med_danc = mean(danceability, na.rm = T)) %>% 
  arrange(desc(med_danc)) %>% 
  ungroup()
## # A tibble: 26 x 2
##    genre            med_danc
##    <chr>               <dbl>
##  1 Reggaeton           0.730
##  2 Hip-Hop             0.728
##  3 Rap                 0.704
##  4 Reggae              0.699
##  5 Children's Music    0.697
##  6 Dance               0.642
##  7 R&B                 0.638
##  8 Electronic          0.619
##  9 Soul                0.616
## 10 Pop                 0.605
## # ... with 16 more rows

Mas não poderia resolver isso com um gráfico?

ggplot(base, aes(x = reorder(genre, danceability), y = danceability)) +
  geom_boxplot() +
  labs(title = 'Dançabilidade por gênero musical', x = 'Gênero musical', y = 'Dançabilidade') +
  theme_light() +
  coord_flip() +
  scale_fill_discrete(name = 'Gênero') +
  theme(legend.position = 'bottom')

Qual é a música com maior (escolha uma variável numérica aqui) por gênero musical?

base %>% 
  group_by(genre) %>% 
  filter(energy == max(energy)) %>% 
  select(genre, artist_name, track_name, energy) %>% 
  ungroup()
## # A tibble: 47 x 4
##    genre       artist_name             track_name                         energy
##    <chr>       <chr>                   <chr>                               <dbl>
##  1 A Capella   Tonic Sol-Fa            Cecilia                             0.818
##  2 Country     Travis Tritt            T-R-O-U-B-L-E - 2006 Remastered V~  0.991
##  3 Alternative Five Finger Death Punch Fake                                0.998
##  4 Alternative Meshuggah               Bleed                               0.998
##  5 Country     Eric Church             Drink In My Hand - Live             0.991
##  6 Country     Zac Brown Band          The Devil Went Down To Georgia - ~  0.991
##  7 Dance       Caramell                Caramelldansen                      0.999
##  8 Dance       2 Unlimited             No Limit                            0.999
##  9 Electronic  Bear Grillz             Wicked                              0.999
## 10 Electronic  Lil Texas               Total Knock Out                     0.999
## # ... with 37 more rows

Podemos comparar o volume das faixas de alguns gêneros?

A escala de medida de volume é em decibéis (dB), ela é uma escala log em que o 0 representa o som mais baixo que um humano consegue escutar sem auxílio. Valores positivos indicam que o som é algumas vezes acima desse limiar, valores negativos indicam que é algumas vezes abaixo do limiar.

base %>% 
  filter(genre %in% c('Hip-Hop', 'Soul', 'World', 'Dance', 'Opera')) %>%  
  ggplot() +
  geom_density(aes(x = loudness, fill = genre), alpha = 0.5) +
  labs(x = 'dB', y = 'Densidade', title = 'Distribuição do volume por gênero musical') +
  scale_fill_discrete(name = 'Gênero') +
  theme_light() +
  theme(legend.position = 'bottom') 

Encontrando as faixas mais populares na base de dados

Faremos isso de dois jeitos. No primeiro vamos apenas ordenar a base em ordem decrescente de popularitdade.

base %>% 
  select(track_name, artist_name, genre, popularity) %>% 
  arrange(desc(popularity))
## # A tibble: 175,084 x 4
##    track_name                                    artist_name     genre   popularity
##    <chr>                                         <chr>           <chr>        <dbl>
##  1 7 rings                                       "Ariana Grande" Dance          100
##  2 break up with your girlfriend, i'm bored      "Ariana Grande" Dance           99
##  3 Wow.                                          "Post Malone"   Rap             99
##  4 Con Calma                                     "Daddy Yankee"  Hip-Hop         98
##  5 Without Me                                    "Halsey"        Dance           97
##  6 Sweet but Psycho                              "Ava Max"       Dance           97
##  7 Sunflower - Spider-Man: Into the Spider-Verse "Post Malone"   Rap             97
##  8 Dancing With A Stranger (with Normani)        "Sam Smith"     Pop             97
##  9 Happier                                       "Marshmello"    Pop             97
## 10 Calma - Remix                                 "Pedro Cap\xf3" Pop             97
## # ... with 175,074 more rows

No segundo, vamos filtrar para manter apenas as faixas com a maior popularidade por gênero musical.

base %>% 
  group_by(genre) %>% 
  filter(popularity == max(popularity)) %>% 
  ungroup() %>% 
  select(track_name, artist_name, genre, popularity) %>% 
  arrange(desc(popularity))
## # A tibble: 34 x 4
##    track_name                             artist_name       genre      popularity
##    <chr>                                  <chr>             <chr>           <dbl>
##  1 7 rings                                "Ariana Grande"   Dance             100
##  2 Wow.                                   "Post Malone"     Rap                99
##  3 Con Calma                              "Daddy Yankee"    Hip-Hop            98
##  4 Dancing With A Stranger (with Normani) "Sam Smith"       Pop                97
##  5 Happier                                "Marshmello"      Pop                97
##  6 Calma - Remix                          "Pedro Cap\xf3"   Pop                97
##  7 Secreto                                "Anuel Aa"        Reggaeton          96
##  8 Bad Liar                               "Imagine Dragons" Rock               90
##  9 All of Me                              "John Legend"     Soul               85
## 10 Bola Rebola                            "Tropkillaz"      Electronic         84
## # ... with 24 more rows

9 - Extras