Introdução ao dplyr

O que é o dplyr e por que usá-lo?

dplyr é um pacote do R para manipulação de dados, sendo um dos pacotes que forma o núcleo do tidyverse. Se você nunca ouviu falar no tidyverse, ele é basicamente um pacote de pacotes que tem uma filosofia em comum, sendo cada pacote especializado em um tipo de tarefa, com a intenção de integrar todos eles facilmente no nosso fluxo de análise. O dplyr tem como base poucos verbos que resolvem boa parte dos problemas envolvendo manipulação de dados.

Um outro ponto é que o dplyr permite que façamos um código facilmente legível e compreensível, justamente pelo fato de usar verbos e também de permitir o encadeamento, que faz com que a sequência do código seja mais próxima da maneira com que pensamos. Além disso, o pacote é bem rápido, ainda que não seja tão rápido quanto o data.table (que não tem uma sintaxe tão amigável).

As funções do dplyr podem ser utilizadas para manipular tabelas do SQL, sem importar a tabela para o R (utilizando lazy query), mas isso é assunto para um post futuro. Existe também uma comunidade grande de programadores do R que utilizam o pacote.

Instalação

Temos duas opções: Podemos instalar apenas o dplyr ou todo o tidyverse (recomendado):

install.packages("tidyverse")

require(tidyerse)

Dados

Neste post vamos apresentar as principais funções do pacote com exemplos, e para isso utilizaremos o dataset starwars, que pode ser utilizado ao ativar o dplyr. O dataset tem informações sobre personagens da saga, que foram coletados de uma API de Star Wars.

Vamos dar uma olhada no dataset:

starwars
## # A tibble: 87 x 13
##    name     height  mass hair_color skin_color eye_color birth_year gender
##    <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
##  1 Luke Sk~    172   77. blond      fair       blue            19.0 male  
##  2 C-3PO       167   75. <NA>       gold       yellow         112.  <NA>  
##  3 R2-D2        96   32. <NA>       white, bl~ red             33.0 <NA>  
##  4 Darth V~    202  136. none       white      yellow          41.9 male  
##  5 Leia Or~    150   49. brown      light      brown           19.0 female
##  6 Owen La~    178  120. brown, gr~ light      blue            52.0 male  
##  7 Beru Wh~    165   75. brown      light      blue            47.0 female
##  8 R5-D4        97   32. <NA>       white, red red             NA   <NA>  
##  9 Biggs D~    183   84. black      light      brown           24.0 male  
## 10 Obi-Wan~    182   77. auburn, w~ fair       blue-gray       57.0 male  
## # ... with 77 more rows, and 5 more variables: homeworld <chr>,
## #   species <chr>, films <list>, vehicles <list>, starships <list>

Já podemos ver que não estamos lidando com um data.frame tradicional. Este é um tibble, que é, basicamente, um data.frame com uma série de modificações. A propósito, tibble é outro pacote do tidyverse. Note que são mostradas as quantidades de linhas e colunas, apenas as 10 primeiras linhas são printadas, a classe das colunas é indicada, e, caso haja coluna(s) cujo conteúdo exceda o tamanho da janela, elas não são printadas, mas apenas listadas ao final.

Nesse tibble temos então, para 87 personagens de Star Wars, informações de nome, altura, massa, cor de cabelo, pele, dos olhos, idade na batalha de Yavin (quando os rebeldes destruíram a primeira estrela da morte), gênero, mundo natal, espécie, lista de filmes onde o personagem aparece, lista de veículos que o personagem pilotou e lista de naves que o personagem pilotou. Perceba que usei a palavra ‘lista’ nessas três ultimas informações, isso porque essas colunas são realmente listas. Podemos, então, ter um tibble onde colunas são listas.

Também podemos utilizar a função glimpse, do pacote tibble, para visualizar a tabela de outra maneira. A função é carregada ao ativar o dplyr. Observe que o glimpse também mostra o número de linhas e colunas, além de mostrar a classe das colunas e alguns valores dessas colunas.

glimpse(starwars)
## Observations: 87
## Variables: 13
## $ name       <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", ...
## $ height     <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188...
## $ mass       <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 8...
## $ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey", "b...
## $ skin_color <chr> "fair", "gold", "white, blue", "white", "light", "l...
## $ eye_color  <chr> "blue", "yellow", "red", "yellow", "brown", "blue",...
## $ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0...
## $ gender     <chr> "male", NA, NA, "male", "female", "male", "female",...
## $ homeworld  <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alder...
## $ species    <chr> "Human", "Droid", "Droid", "Human", "Human", "Human...
## $ films      <list> [<"Revenge of the Sith", "Return of the Jedi", "Th...
## $ vehicles   <list> [<"Snowspeeder", "Imperial Speeder Bike">, <>, <>,...
## $ starships  <list> [<"X-wing", "Imperial shuttle">, <>, <>, "TIE Adva...

Apesar da concepção de data.frame do tidyverse ser o tibble, é perfeitamente possível utilizar suas funções com um data.frame. Porém para transformar um data.frame em um tibble, basta usar a função as_tibble (ou as_data_frame).

Funções básicas (verbos)

O dplyr oferece 5 verbos (funções) básicos, que resolvem alguns problemas comuns:

  • select: Seleciona colunas.
  • filter: Filtra registros (linhas).
  • mutate: Cria novas variáveis (colunas).
  • summarise (ou summarize): Reduz uma série de valores de uma coluna em um valor apenas.
  • arrange: Ordena o data.frame de acordo com os valores de uma(s) coluna(s).

Além disso, existe a função group_by, que pode ser usada em conjunto com os verbos, de maneira a fazer operações por grupos de valores de coluna(s).

Todas essas funções recebem pelo menos dois argumentos: o data.frame e o(s) argumento(s) pertinente(s) à função (nomes de colunas, funções, condições). A seguir vamos utilizar as funções para ficar mais clara sua sintaxe.

Uso

  • O select seleciona colunas, podendo ser indicadas pelos nomes (com ou sem aspas, embora seja preferível não usar aspas) ou pelo índice da coluna. Por exemplo, se quisermos selecionar as colunas name, height e mass, podemos selecioná-las das seguintes maneiras:

    select(starwars, name, height, mass)
    # ou
    select(starwars, "name", "height", "mass")
    # Ou, como as colunas são seguidas
    select(starwars, name:mass)
    # Utilizando os índices
    select(starwars, 1:3)

    Gerando o seguinte output:

    ## # A tibble: 87 x 3
    ##    name               height  mass
    ##    <chr>               <int> <dbl>
    ##  1 Luke Skywalker        172   77.
    ##  2 C-3PO                 167   75.
    ##  3 R2-D2                  96   32.
    ##  4 Darth Vader           202  136.
    ##  5 Leia Organa           150   49.
    ##  6 Owen Lars             178  120.
    ##  7 Beru Whitesun lars    165   75.
    ##  8 R5-D4                  97   32.
    ##  9 Biggs Darklighter     183   84.
    ## 10 Obi-Wan Kenobi        182   77.
    ## # ... with 77 more rows

    Também podemos remover colunas. Por exemplo, queremos selecionar todas as colunas do nosso tibble, exceto a coluna height:

    select(starwars, -height)
    ## # A tibble: 87 x 12
    ##    name   mass hair_color skin_color eye_color birth_year gender homeworld
    ##    <chr> <dbl> <chr>      <chr>      <chr>          <dbl> <chr>  <chr>    
    ##  1 Luke~   77. blond      fair       blue            19.0 male   Tatooine 
    ##  2 C-3PO   75. <NA>       gold       yellow         112.  <NA>   Tatooine 
    ##  3 R2-D2   32. <NA>       white, bl~ red             33.0 <NA>   Naboo    
    ##  4 Dart~  136. none       white      yellow          41.9 male   Tatooine 
    ##  5 Leia~   49. brown      light      brown           19.0 female Alderaan 
    ##  6 Owen~  120. brown, gr~ light      blue            52.0 male   Tatooine 
    ##  7 Beru~   75. brown      light      blue            47.0 female Tatooine 
    ##  8 R5-D4   32. <NA>       white, red red             NA   <NA>   Tatooine 
    ##  9 Bigg~   84. black      light      brown           24.0 male   Tatooine 
    ## 10 Obi-~   77. auburn, w~ fair       blue-gray       57.0 male   Stewjon  
    ## # ... with 77 more rows, and 4 more variables: species <chr>,
    ## #   films <list>, vehicles <list>, starships <list>
  • A função filter recebe uma ou mais condições lógicas e retorna as linhas do tibble que atendam o solicitado. Vamos filtrar personagens que não têm cabelo, logo hair_color é NA:

    filter(starwars, is.na(hair_color))
    ## Warning: package 'bindrcpp' was built under R version 3.4.4
    ## # A tibble: 5 x 13
    ##   name     height  mass hair_color skin_color  eye_color birth_year gender
    ##   <chr>     <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> 
    ## 1 C-3PO       167   75. <NA>       gold        yellow          112. <NA>  
    ## 2 R2-D2        96   32. <NA>       white, blue red              33. <NA>  
    ## 3 R5-D4        97   32. <NA>       white, red  red              NA  <NA>  
    ## 4 Greedo      173   74. <NA>       green       black            44. male  
    ## 5 Jabba D~    175 1358. <NA>       green-tan,~ orange          600. herma~
    ## # ... with 5 more variables: homeworld <chr>, species <chr>, films <list>,
    ## #   vehicles <list>, starships <list>

    Podemos também utilizar mais de uma condição (usando & ou |). Se quisermos registros que atendam a todas as condições, podemos utilizar & (E) ou separar as condições por vírgulas. E quando for necessário registros que atendam a pelo menos uma condição, devemos utilizar o operador | (OU). Vamos agora filtrar personagens sem cabelo e Droids:

    filter(starwars, is.na(hair_color), species == "Droid")
    ## # A tibble: 3 x 13
    ##   name  height  mass hair_color skin_color  eye_color birth_year gender
    ##   <chr>  <int> <dbl> <chr>      <chr>       <chr>          <dbl> <chr> 
    ## 1 C-3PO    167   75. <NA>       gold        yellow          112. <NA>  
    ## 2 R2-D2     96   32. <NA>       white, blue red              33. <NA>  
    ## 3 R5-D4     97   32. <NA>       white, red  red              NA  <NA>  
    ## # ... with 5 more variables: homeworld <chr>, species <chr>, films <list>,
    ## #   vehicles <list>, starships <list>

Vamos agora filtrar por personagens com mais de 130 kg e selecionar name, height, mass e homeworld:

select(filter(starwars, mass > 130), name:mass, homeworld)
## # A tibble: 5 x 4
##   name                  height  mass homeworld
##   <chr>                  <int> <dbl> <chr>    
## 1 Darth Vader              202  136. Tatooine 
## 2 Jabba Desilijic Tiure    175 1358. Nal Hutta
## 3 IG-88                    200  140. <NA>     
## 4 Grievous                 216  159. Kalee    
## 5 Tarfful                  234  136. Kashyyyk

Apesar de termos só duas funções envolvidas, veja que começa a não ser tão simples de ler o que o R irá fazer com esse comando. Quando o número de funções aumenta, isso fica bem pior de ler. Para resolver esse problema, o tidyverse propõe uma maneira de encadear o código:

  • pipe (%>%): O pipe é o operador que faz o encadeamento das funções do tidyverse. Este operador é do pacote magrittr, mas ao carregar o dplyr já poderemos usá-lo. Seu atalho, no RStudio, é (Cmd)Ctrl + Shift + M. O pipe irá pegar o resultado da expressão a sua esquerda e colocar como primeiro argumento (por padrão) da expressão da direita. Como as funções do dplyr recebem um tibble no primeiro argumento, então isso vai facilitar a escrita. Vejamos o exemplo anterior com o pipe:

    starwars %>% 
      filter(mass > 130) %>% 
      select(name:mass, homeworld)
    ## # A tibble: 5 x 4
    ##   name                  height  mass homeworld
    ##   <chr>                  <int> <dbl> <chr>    
    ## 1 Darth Vader              202  136. Tatooine 
    ## 2 Jabba Desilijic Tiure    175 1358. Nal Hutta
    ## 3 IG-88                    200  140. <NA>     
    ## 4 Grievous                 216  159. Kalee    
    ## 5 Tarfful                  234  136. Kashyyyk

    Perceba que agora o código fica muito mais simples de entender: Pegamos o tibble starwars, filtramos personagens com mais de 130 kg e do tibble resultante do filtro selecionamos as colunas desejadas.

  • Com o mutate podemos criar novas colunas e essas novas colunas podem ser criadas em função das já existentes. Vamos calcular o IMC dos personagens, mas para isso precisamos da altura em metros. Vamos então criar uma coluna com a altura em metros e usar esse coluna recém criada para o cálculo do IMC:

    starwars %>% 
      mutate(altura_metros = height/100,
             IMC = mass/(altura_metros^2)) %>% 
      select(name, IMC) 
    ## # A tibble: 87 x 2
    ##    name                 IMC
    ##    <chr>              <dbl>
    ##  1 Luke Skywalker      26.0
    ##  2 C-3PO               26.9
    ##  3 R2-D2               34.7
    ##  4 Darth Vader         33.3
    ##  5 Leia Organa         21.8
    ##  6 Owen Lars           37.9
    ##  7 Beru Whitesun lars  27.5
    ##  8 R5-D4               34.0
    ##  9 Biggs Darklighter   25.1
    ## 10 Obi-Wan Kenobi      23.2
    ## # ... with 77 more rows
  • O summarise (ou summarize) permite que usemos funções de sumarização, ou seja, funções que recebem \(n\) elementos e retornam apenas \(1\) valor. Vamos calcular a maior altura e a massa média:

    starwars %>% 
      summarise(max_altura = max(height, na.rm = T),
                massa_media = mean(mass, na.rm = T))
    ## # A tibble: 1 x 2
    ##   max_altura massa_media
    ##        <int>       <dbl>
    ## 1        264        97.3
  • Usamos o arrange para ordenar o tibble, de maneira crescente ou decrescente. Vamos refazer o cálculo do IMC e usar essa coluna para ordenar, em ordem crescente, o tibble resultante:

    starwars %>%
      mutate(altura_metros = height/100,
             IMC = mass/(altura_metros^2)) %>% 
      select(name, height, mass, IMC) %>% 
      arrange(IMC)
    ## # A tibble: 87 x 4
    ##    name          height  mass   IMC
    ##    <chr>          <int> <dbl> <dbl>
    ##  1 Wat Tambor       193   48.  12.9
    ##  2 Adi Gallia       184   50.  14.8
    ##  3 Sly Moore        178   48.  15.1
    ##  4 Roos Tarpals     224   82.  16.3
    ##  5 Padmé Amidala    165   45.  16.5
    ##  6 Lama Su          229   88.  16.8
    ##  7 Jar Jar Binks    196   66.  17.2
    ##  8 Ayla Secura      178   55.  17.4
    ##  9 Shaak Ti         178   57.  18.0
    ## 10 Barriss Offee    166   50.  18.1
    ## # ... with 77 more rows

    Para que a ordenação seja feita de maneira decrescente basta utilizar o símbolo -, ou então a função desc

    starwars %>%
      mutate(altura_metros = height/100,
             IMC = mass/(altura_metros^2)) %>% 
      select(name, height, mass, IMC) %>% 
      arrange(-IMC) # Ou arrange(desc(IMC))
    ## # A tibble: 87 x 4
    ##    name                  height  mass   IMC
    ##    <chr>                  <int> <dbl> <dbl>
    ##  1 Jabba Desilijic Tiure    175 1358. 443. 
    ##  2 Dud Bolt                  94   45.  50.9
    ##  3 Yoda                      66   17.  39.0
    ##  4 Owen Lars                178  120.  37.9
    ##  5 IG-88                    200  140.  35.0
    ##  6 R2-D2                     96   32.  34.7
    ##  7 Grievous                 216  159.  34.1
    ##  8 R5-D4                     97   32.  34.0
    ##  9 Jek Tono Porkins         180  110.  34.0
    ## 10 Darth Vader              202  136.  33.3
    ## # ... with 77 more rows

    Também podemos reordenar o tibble de acordo com mais de uma coluna.

    starwars %>% 
      arrange(hair_color, name, -mass, -birth_year)
    ## # A tibble: 87 x 13
    ##    name     height  mass hair_color skin_color eye_color birth_year gender
    ##    <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> 
    ##  1 Mon Mot~    150  NA   auburn     fair       blue            48.0 female
    ##  2 Wilhuff~    180  NA   auburn, g~ fair       blue            64.0 male  
    ##  3 Obi-Wan~    182  77.0 auburn, w~ fair       blue-gray       57.0 male  
    ##  4 Bail Pr~    191  NA   black      tan        brown           67.0 male  
    ##  5 Barriss~    166  50.0 black      yellow     blue            40.0 female
    ##  6 Biggs D~    183  84.0 black      light      brown           24.0 male  
    ##  7 Boba Fe~    183  78.2 black      fair       brown           31.5 male  
    ##  8 Eeth Ko~    171  NA   black      brown      brown           NA   male  
    ##  9 Finn         NA  NA   black      dark       dark            NA   male  
    ## 10 Gregar ~    185  85.0 black      dark       brown           NA   male  
    ## # ... with 77 more rows, and 5 more variables: homeworld <chr>,
    ## #   species <chr>, films <list>, vehicles <list>, starships <list>
  • Por fim, podemos realizar todas as operações por grupos. Para isso existe a função group_by. Faremos então o cálculo da maior altura e a massa média por espécie:

    starwars %>% 
      group_by(species) %>% 
      summarise(max_altura = max(height, na.rm = T),
                massa_media = mean(mass, na.rm = T))
    ## # A tibble: 38 x 3
    ##    species   max_altura massa_media
    ##    <chr>          <int>       <dbl>
    ##  1 Aleena            79        15.0
    ##  2 Besalisk         198       102. 
    ##  3 Cerean           198        82.0
    ##  4 Chagrian         196       NaN  
    ##  5 Clawdite         168        55.0
    ##  6 Droid            200        69.8
    ##  7 Dug              112        40.0
    ##  8 Ewok              88        20.0
    ##  9 Geonosian        183        80.0
    ## 10 Gungan           224        74.0
    ## # ... with 28 more rows

    Todos os outros verbos têm (ou podem ter) seu comportamento alterado ao usar group_by. Nessa página os comportamentos são descritos.