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~    172    77 blond      fair       blue            19   male  
##  2 C-3PO    167    75 <NA>       gold       yellow         112   <NA>  
##  3 R2-D2     96    32 <NA>       white, bl~ red             33   <NA>  
##  4 Dart~    202   136 none       white      yellow          41.9 male  
##  5 Leia~    150    49 brown      light      brown           19   female
##  6 Owen~    178   120 brown, gr~ light      blue            52   male  
##  7 Beru~    165    75 brown      light      blue            47   female
##  8 R5-D4     97    32 <NA>       white, red red             NA   <NA>  
##  9 Bigg~    183    84 black      light      brown           24   male  
## 10 Obi-~    182    77 auburn, w~ fair       blue-gray       57   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   male   Tatooine 
    ##  2 C-3PO    75 <NA>       gold       yellow         112   <NA>   Tatooine 
    ##  3 R2-D2    32 <NA>       white, bl~ red             33   <NA>   Naboo    
    ##  4 Dart~   136 none       white      yellow          41.9 male   Tatooine 
    ##  5 Leia~    49 brown      light      brown           19   female Alderaan 
    ##  6 Owen~   120 brown, gr~ light      blue            52   male   Tatooine 
    ##  7 Beru~    75 brown      light      blue            47   female Tatooine 
    ##  8 R5-D4    32 <NA>       white, red red             NA   <NA>   Tatooine 
    ##  9 Bigg~    84 black      light      brown           24   male   Tatooine 
    ## 10 Obi-~    77 auburn, w~ fair       blue-gray       57   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))
    ## # 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, bl~ red               33 <NA>  
    ## 3 R5-D4     97    32 <NA>       white, red red               NA <NA>  
    ## 4 Gree~    173    74 <NA>       green      black             44 male  
    ## 5 Jabb~    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, bl~ 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  
    ##  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 ~    150  NA   auburn     fair       blue            48   female
    ##  2 Wilh~    180  NA   auburn, g~ fair       blue            64   male  
    ##  3 Obi-~    182  77   auburn, w~ fair       blue-gray       57   male  
    ##  4 Bail~    191  NA   black      tan        brown           67   male  
    ##  5 Barr~    166  50   black      yellow     blue            40   female
    ##  6 Bigg~    183  84   black      light      brown           24   male  
    ##  7 Boba~    183  78.2 black      fair       brown           31.5 male  
    ##  8 Eeth~    171  NA   black      brown      brown           NA   male  
    ##  9 Finn      NA  NA   black      dark       dark            NA   male  
    ## 10 Greg~    185  85   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  
    ##  2 Besalisk         198       102  
    ##  3 Cerean           198        82  
    ##  4 Chagrian         196       NaN  
    ##  5 Clawdite         168        55  
    ##  6 Droid            200        69.8
    ##  7 Dug              112        40  
    ##  8 Ewok              88        20  
    ##  9 Geonosian        183        80  
    ## 10 Gungan           224        74  
    ## # ... 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.