Chapter 4 Módulo IV

Script da aula do Módulo IV abaixo. Download aula4.R

4.1 Manipulação de dados com dplyr

Nesta seção, trataremos do pacote dplyr, que é um dos pacotes mais importantes da coleção tidyverse. Ele traz uma “gramática” específica de manipulação de dados, provendo um conjunto de funções que ajudam a resolver os desafios mais comuns na manipulação de dados. O objetivo é que você se familiarize com as funções do pacote dplyr; com as tarefas que elas executam; e veja exemplos de como aplicá-las a data.frames.

Para tanto vamos utilizar três datasets que vamos citar e descrever abaixo:

  • Chess game dataset

Este é um conjunto de pouco mais de 20.000 jogos coletados de uma seleção de usuários no site Lichess.org.

tb_chess_game <- read_csv(file="https://raw.githubusercontent.com/brunolucian/cursoBasicoR/master/datasets/chess_games.csv")
## 
## -- Column specification ---------------------------------------------------------------------------------------
## cols(
##   id = col_character(),
##   rated = col_logical(),
##   created_at = col_double(),
##   last_move_at = col_double(),
##   turns = col_double(),
##   victory_status = col_character(),
##   winner = col_character(),
##   increment_code = col_character(),
##   white_id = col_character(),
##   white_rating = col_double(),
##   black_id = col_character(),
##   black_rating = col_double(),
##   moves = col_character(),
##   opening_eco = col_character(),
##   opening_name = col_character(),
##   opening_ply = col_double()
## )
## # A tibble: 20,058 x 16
##    id      rated   created_at last_move_at turns victory_status winner increment_code white_id     white_rating
##    <chr>   <lgl>        <dbl>        <dbl> <dbl> <chr>          <chr>  <chr>          <chr>               <dbl>
##  1 TZJHLl~ FALSE      1.50e12      1.50e12    13 outoftime      white  15+2           bourgris             1500
##  2 l1NXvw~ TRUE       1.50e12      1.50e12    16 resign         black  5+10           a-00                 1322
##  3 mIICvQ~ TRUE       1.50e12      1.50e12    61 mate           white  5+10           ischia               1496
##  4 kWKvrq~ TRUE       1.50e12      1.50e12    61 mate           white  20+0           daniamurash~         1439
##  5 9tXo1A~ TRUE       1.50e12      1.50e12    95 mate           white  30+3           nik221107            1523
##  6 MsoDV9~ FALSE      1.50e12      1.50e12     5 draw           draw   10+0           trelynn17            1250
##  7 qwU9ra~ TRUE       1.50e12      1.50e12    33 resign         white  10+0           capa_jr              1520
##  8 RVN0N3~ FALSE      1.50e12      1.50e12     9 resign         black  15+30          daniel_like~         1413
##  9 dwF3DJ~ TRUE       1.50e12      1.50e12    66 resign         black  15+0           ehabfanri            1439
## 10 afoMwn~ TRUE       1.50e12      1.50e12   119 mate           white  10+0           daniel_like~         1381
## # ... with 20,048 more rows, and 6 more variables: black_id <chr>, black_rating <dbl>, moves <chr>,
## #   opening_eco <chr>, opening_name <chr>, opening_ply <dbl>
  • Netflix dataset

Programas de TV e filmes listados no Netflix Este conjunto de dados consiste em programas de TV e filmes disponíveis no Netflix a partir de 2019. O conjunto de dados é coletado do Flixable, um mecanismo de busca Netflix de terceiros.

Em 2018, eles lançaram um relatório interessante que mostra que o número de programas de TV na Netflix quase triplicou desde 2010. O número de filmes do serviço de streaming diminuiu em mais de 2.000 títulos desde 2010, enquanto seu número de programas de TV quase triplicou. Será interessante explorar o que todos os outros insights podem ser obtidos no mesmo conjunto de dados.

tb_netflix <- read_csv("https://raw.githubusercontent.com/brunolucian/cursoBasicoR/master/datasets/netflix_titles.csv")
## 
## -- Column specification ---------------------------------------------------------------------------------------
## cols(
##   show_id = col_character(),
##   type = col_character(),
##   title = col_character(),
##   director = col_character(),
##   cast = col_character(),
##   country = col_character(),
##   date_added = col_character(),
##   release_year = col_double(),
##   rating = col_character(),
##   duration = col_character(),
##   listed_in = col_character(),
##   description = col_character()
## )
## # A tibble: 7,787 x 12
##    show_id type   title director  cast   country date_added release_year rating duration listed_in description 
##    <chr>   <chr>  <chr> <chr>     <chr>  <chr>   <chr>             <dbl> <chr>  <chr>    <chr>     <chr>       
##  1 s1      TV Sh~ 3%    <NA>      João ~ Brazil  August 14~         2020 TV-MA  4 Seaso~ Internat~ In a future~
##  2 s2      Movie  7:19  Jorge Mi~ Demiá~ Mexico  December ~         2016 TV-MA  93 min   Dramas, ~ After a dev~
##  3 s3      Movie  23:59 Gilbert ~ Tedd ~ Singap~ December ~         2011 R      78 min   Horror M~ When an arm~
##  4 s4      Movie  9     Shane Ac~ Elija~ United~ November ~         2009 PG-13  80 min   Action &~ In a postap~
##  5 s5      Movie  21    Robert L~ Jim S~ United~ January 1~         2008 PG-13  123 min  Dramas    A brilliant~
##  6 s6      TV Sh~ 46    Serdar A~ Erdal~ Turkey  July 1, 2~         2016 TV-MA  1 Season Internat~ A genetics ~
##  7 s7      Movie  122   Yasir Al~ Amina~ Egypt   June 1, 2~         2019 TV-MA  95 min   Horror M~ After an aw~
##  8 s8      Movie  187   Kevin Re~ Samue~ United~ November ~         1997 R      119 min  Dramas    After one o~
##  9 s9      Movie  706   Shravan ~ Divya~ India   April 1, ~         2019 TV-14  118 min  Horror M~ When a doct~
## 10 s10     Movie  1920  Vikram B~ Rajne~ India   December ~         2008 TV-MA  143 min  Horror M~ An architec~
## # ... with 7,777 more rows
  • Games sales dataset

Este conjunto de dados contém uma lista de videogames com vendas superiores a 100.000 cópias.

tb_game_sales <- read_csv("https://raw.githubusercontent.com/brunolucian/cursoBasicoR/master/datasets/vgsales.csv")
## 
## -- Column specification ---------------------------------------------------------------------------------------
## cols(
##   Rank = col_double(),
##   Name = col_character(),
##   Platform = col_character(),
##   Year = col_character(),
##   Genre = col_character(),
##   Publisher = col_character(),
##   NA_Sales = col_double(),
##   EU_Sales = col_double(),
##   JP_Sales = col_double(),
##   Other_Sales = col_double(),
##   Global_Sales = col_double()
## )
## # A tibble: 16,598 x 11
##     Rank Name             Platform Year  Genre    Publisher NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales
##    <dbl> <chr>            <chr>    <chr> <chr>    <chr>        <dbl>    <dbl>    <dbl>       <dbl>        <dbl>
##  1     1 Wii Sports       Wii      2006  Sports   Nintendo      41.5    29.0      3.77        8.46         82.7
##  2     2 Super Mario Bro~ NES      1985  Platform Nintendo      29.1     3.58     6.81        0.77         40.2
##  3     3 Mario Kart Wii   Wii      2008  Racing   Nintendo      15.8    12.9      3.79        3.31         35.8
##  4     4 Wii Sports Reso~ Wii      2009  Sports   Nintendo      15.8    11.0      3.28        2.96         33  
##  5     5 Pokemon Red/Pok~ GB       1996  Role-Pl~ Nintendo      11.3     8.89    10.2         1            31.4
##  6     6 Tetris           GB       1989  Puzzle   Nintendo      23.2     2.26     4.22        0.58         30.3
##  7     7 New Super Mario~ DS       2006  Platform Nintendo      11.4     9.23     6.5         2.9          30.0
##  8     8 Wii Play         Wii      2006  Misc     Nintendo      14.0     9.2      2.93        2.85         29.0
##  9     9 New Super Mario~ Wii      2009  Platform Nintendo      14.6     7.06     4.7         2.26         28.6
## 10    10 Duck Hunt        NES      1984  Shooter  Nintendo      26.9     0.63     0.28        0.47         28.3
## # ... with 16,588 more rows

Conforme os próprios autores do pacote apontam, quando trabalhamos com dados, nós precisamos:

  • Descobrir o que desejamos fazer;

  • Descrever essas tarefas na forma de um programa de computador;

  • Executar o programa.

O pacote dplyr torna estes passos mais rápidos e fáceis de executar, pois:

  • ao invés de disponibilizar uma imensidão de funções, igual temos no R base e outros pacotes, ele restringe nossas opções e com isso nos ajuda a raciocinar de forma mais direta sobre o que desejamos e podemos fazer com os dados;

  • provém “verbos” (ou funções) mais simples, ou seja, funções que correspondem às tarefas mais comuns de manipulação de dados, ajudando-nos assim a traduzir pensamentos em código;

  • utiliza backends (códigos de final de processo, ou seja, mais próximos ao usuário) eficientes, de modo que gastamos menos tempo esperando pelo computador.

O pacote dplyr proporciona uma função para cada “verbo” considerado importante em manipulação de dados:

  • filter() para selecionar “casos” baseados em seus valores;

  • arrange() para reordenar os “casos”;

  • select() e rename() para selecionar variáveis baseadas em seus nomes;

  • mutate() e transmute() para adicionar novas variáveis que são funções de variáveis já existentes nos dados;

  • summarise() ou summarize() para condensar multiplos valores em um único;

  • group_by() embora não seja considerado um dos “verbos”, serve para agruparmos os dados em torno de uma ou mais variáveis. As funções consideradas “verbos” podem ser utilizadas antes ou após o agrupamentodos dados.

Veremos agora alguns exemplos de aplicação destas funções.

library(dplyr)

Note que pelo print que temos novamente um tibble, que é uma forma moderna de data.frame implementada pelo pessoal do tidyverse . Este formato é particularmente útil para grandes datasets porque só é impresso na tela as primeiras linhas e diversos resumos/informações sobre nossas variáveis. Para converter data.frames em tibbles, usamos as_tibble().

4.1.1 Filtrando linhas com filter()

filter() permite fazer um subset das linhas de um tibble/dataframe. Como todos os verbos simples de dplyr, o primeiro argumento será um tibble (ou data.frame). O segundo argumento e os subsequentes se referem a variáveis dentro do data.frame, em que se selecionam as linhas onde a expressão é verdadeira (TRUE).

Vamos selecionar todos as linhas em que o jogador que estava com as peças pretas foi o vencedor da partida:

filter(tb_chess_game, winner == "black")
## # A tibble: 9,107 x 16
##    id      rated   created_at last_move_at turns victory_status winner increment_code white_id     white_rating
##    <chr>   <lgl>        <dbl>        <dbl> <dbl> <chr>          <chr>  <chr>          <chr>               <dbl>
##  1 l1NXvw~ TRUE       1.50e12      1.50e12    16 resign         black  5+10           a-00                 1322
##  2 RVN0N3~ FALSE      1.50e12      1.50e12     9 resign         black  15+30          daniel_like~         1413
##  3 dwF3DJ~ TRUE       1.50e12      1.50e12    66 resign         black  15+0           ehabfanri            1439
##  4 Vf5fKW~ FALSE      1.50e12      1.50e12    38 resign         black  20+60          daniel_like~         1381
##  5 HRti5m~ FALSE      1.50e12      1.50e12    60 resign         black  5+40           daniel_like~         1381
##  6 2fEjSe~ FALSE      1.50e12      1.50e12    31 resign         black  8+0            daniel_like~         1381
##  7 guanvM~ FALSE      1.50e12      1.50e12    43 resign         black  15+15          sureka_aksh~         1141
##  8 PmpkWk~ FALSE      1.50e12      1.50e12    52 resign         black  15+15          shivangithe~         1094
##  9 EwaK0I~ FALSE      1.50e12      1.50e12    66 mate           black  15+16          sureka_aksh~         1141
## 10 yrSDoz~ FALSE      1.50e12      1.50e12   101 resign         black  15+15          shivangithe~         1094
## # ... with 9,097 more rows, and 6 more variables: black_id <chr>, black_rating <dbl>, moves <chr>,
## #   opening_eco <chr>, opening_name <chr>, opening_ply <dbl>
filter(tb_chess_game, winner == "black", black_rating < 1500)
## # A tibble: 2,947 x 16
##    id      rated   created_at last_move_at turns victory_status winner increment_code white_id     white_rating
##    <chr>   <lgl>        <dbl>        <dbl> <dbl> <chr>          <chr>  <chr>          <chr>               <dbl>
##  1 l1NXvw~ TRUE       1.50e12      1.50e12    16 resign         black  5+10           a-00                 1322
##  2 dwF3DJ~ TRUE       1.50e12      1.50e12    66 resign         black  15+0           ehabfanri            1439
##  3 guanvM~ FALSE      1.50e12      1.50e12    43 resign         black  15+15          sureka_aksh~         1141
##  4 PmpkWk~ FALSE      1.50e12      1.50e12    52 resign         black  15+15          shivangithe~         1094
##  5 EwaK0I~ FALSE      1.50e12      1.50e12    66 mate           black  15+16          sureka_aksh~         1141
##  6 yrSDoz~ FALSE      1.50e12      1.50e12   101 resign         black  15+15          shivangithe~         1094
##  7 mCij4h~ TRUE       1.50e12      1.50e12    13 resign         black  10+0           shivangithe~         1113
##  8 srz9Qf~ TRUE       1.50e12      1.50e12    54 mate           black  10+10          mannat1              1328
##  9 NS6ccs~ TRUE       1.50e12      1.50e12    53 resign         black  10+10          shivangithe~         1056
## 10 M3vpf2~ TRUE       1.50e12      1.50e12    66 mate           black  10+10          shivangithe~         1077
## # ... with 2,937 more rows, and 6 more variables: black_id <chr>, black_rating <dbl>, moves <chr>,
## #   opening_eco <chr>, opening_name <chr>, opening_ply <dbl>
filter(tb_chess_game, winner == "black" | opening_eco =="A10")
## # A tibble: 9,178 x 16
##    id      rated   created_at last_move_at turns victory_status winner increment_code white_id     white_rating
##    <chr>   <lgl>        <dbl>        <dbl> <dbl> <chr>          <chr>  <chr>          <chr>               <dbl>
##  1 l1NXvw~ TRUE       1.50e12      1.50e12    16 resign         black  5+10           a-00                 1322
##  2 RVN0N3~ FALSE      1.50e12      1.50e12     9 resign         black  15+30          daniel_like~         1413
##  3 dwF3DJ~ TRUE       1.50e12      1.50e12    66 resign         black  15+0           ehabfanri            1439
##  4 Vf5fKW~ FALSE      1.50e12      1.50e12    38 resign         black  20+60          daniel_like~         1381
##  5 HRti5m~ FALSE      1.50e12      1.50e12    60 resign         black  5+40           daniel_like~         1381
##  6 2fEjSe~ FALSE      1.50e12      1.50e12    31 resign         black  8+0            daniel_like~         1381
##  7 guanvM~ FALSE      1.50e12      1.50e12    43 resign         black  15+15          sureka_aksh~         1141
##  8 PmpkWk~ FALSE      1.50e12      1.50e12    52 resign         black  15+15          shivangithe~         1094
##  9 EwaK0I~ FALSE      1.50e12      1.50e12    66 mate           black  15+16          sureka_aksh~         1141
## 10 yrSDoz~ FALSE      1.50e12      1.50e12   101 resign         black  15+15          shivangithe~         1094
## # ... with 9,168 more rows, and 6 more variables: black_id <chr>, black_rating <dbl>, moves <chr>,
## #   opening_eco <chr>, opening_name <chr>, opening_ply <dbl>

4.1.2 Ordenando linhas com arrange()

arrange() funciona de modo semelhante a filter, mas ao invés de filtrar e selecionar linhas, ele apenas as reordena de acordo com alguma condição que passamos. Essa função recebe um data.frame e um conjunto de column names pelo qual vai ordenar. Se você fornecer mais de um nome de coluna, cada coluna adicional passada será usada como critério de desempate.

arrange(tb_chess_game, white_rating)
## # A tibble: 20,058 x 16
##    id      rated   created_at last_move_at turns victory_status winner increment_code white_id     white_rating
##    <chr>   <lgl>        <dbl>        <dbl> <dbl> <chr>          <chr>  <chr>          <chr>               <dbl>
##  1 YwaFfX~ TRUE       1.50e12      1.50e12    61 mate           white  10+0           ragnarlothb~          784
##  2 XJoTQF~ TRUE       1.50e12      1.50e12     2 resign         black  10+0           crazyscient~          784
##  3 APnsyo~ FALSE      1.40e12      1.40e12    27 resign         black  20+12          lucatiel-of~          788
##  4 lWZQsM~ TRUE       1.49e12      1.49e12    62 resign         black  15+15          natalua               793
##  5 t6GU62~ TRUE       1.49e12      1.49e12   144 resign         black  15+0           epicchess66           795
##  6 yRIvgI~ TRUE       1.50e12      1.50e12    23 resign         white  0+25           mr_zilgaze            798
##  7 yRIvgI~ TRUE       1.50e12      1.50e12    23 resign         white  0+25           mr_zilgaze            798
##  8 q5xbB1~ TRUE       1.41e12      1.41e12     6 resign         black  10+8           canabidiol            801
##  9 wr2hpx~ FALSE      1.39e12      1.39e12    92 mate           black  20+8           alliekatxox~          807
## 10 4Oie3k~ TRUE       1.50e12      1.50e12    47 mate           white  10+0           mata1234              808
## # ... with 20,048 more rows, and 6 more variables: black_id <chr>, black_rating <dbl>, moves <chr>,
## #   opening_eco <chr>, opening_name <chr>, opening_ply <dbl>

Se quiser ordenar de forma decrescente, utilize a função desc(nome_da_coluna) dentro de arrange(). Isso seria particularmente interessante se você quisesse ordenar os dados na coluna final do maior volume para o maior.

arrange(tb_chess_game, desc(white_rating), desc(black_rating) )
## # A tibble: 20,058 x 16
##    id       rated    created_at last_move_at turns victory_status winner increment_code white_id   white_rating
##    <chr>    <lgl>         <dbl>        <dbl> <dbl> <chr>          <chr>  <chr>          <chr>             <dbl>
##  1 Y1oXTO75 FALSE 1494548121994      1.49e12    20 resign         white  30+30          justicebot         2700
##  2 qIn8fg1t TRUE  1477763880499      1.48e12   149 resign         white  5+5            blitzbull~         2622
##  3 upN6B2c0 FALSE 1503524835210      1.50e12    69 resign         white  10+15          lance5500          2621
##  4 TVQunaRl FALSE 1503432282759      1.50e12    40 resign         black  10+15          lance5500          2621
##  5 fPm9lH2d FALSE 1502485078188      1.50e12    58 outoftime      black  10+10          lance5500          2621
##  6 woqCHd9g FALSE 1501877320309      1.50e12    42 resign         black  10+15          lance5500          2621
##  7 lh41ycUX FALSE 1497214098420      1.50e12    49 resign         white  5+60           lance5500          2621
##  8 zA4LDp7x FALSE 1496885486497      1.50e12    77 resign         white  15+0           lance5500          2621
##  9 HWw4eAWR FALSE 1503514029547      1.50e12    14 resign         white  10+25          lance5500          2621
## 10 2hNUHRUA FALSE 1501593317978      1.50e12    47 outoftime      white  10+25          lance5500          2621
## # ... with 20,048 more rows, and 6 more variables: black_id <chr>, black_rating <dbl>, moves <chr>,
## #   opening_eco <chr>, opening_name <chr>, opening_ply <dbl>

4.1.3 Selecionando colunas com select()

Geralmente trabalhamos com grandes datasets com muitas colunas, mas somente algumas poucas colunas serão de nosso interesse. select() nos permite rapidamente focar num subconjunto dos dados. O melhor é que podemos utilizar operações - que normalmente só funcionam com as posições das colunas - direto nos nomes das variáveis.

# Seleção por nome
select(tb_netflix, title, country, rating)
## # A tibble: 7,787 x 3
##    title country       rating
##    <chr> <chr>         <chr> 
##  1 3%    Brazil        TV-MA 
##  2 7:19  Mexico        TV-MA 
##  3 23:59 Singapore     R     
##  4 9     United States PG-13 
##  5 21    United States PG-13 
##  6 46    Turkey        TV-MA 
##  7 122   Egypt         TV-MA 
##  8 187   United States R     
##  9 706   India         TV-14 
## 10 1920  India         TV-MA 
## # ... with 7,777 more rows
# Selecionando todas as colunas num intervalo de colunas (inclusive)
select(tb_netflix, title:release_year)
## # A tibble: 7,787 x 6
##    title director      cast                                                country   date_added    release_year
##    <chr> <chr>         <chr>                                               <chr>     <chr>                <dbl>
##  1 3%    <NA>          João Miguel, Bianca Comparato, Michel Gomes, Rodol~ Brazil    August 14, 2~         2020
##  2 7:19  Jorge Michel~ Demián Bichir, Héctor Bonilla, Oscar Serrano, Azal~ Mexico    December 23,~         2016
##  3 23:59 Gilbert Chan  Tedd Chan, Stella Chung, Henley Hii, Lawrence Koh,~ Singapore December 20,~         2011
##  4 9     Shane Acker   Elijah Wood, John C. Reilly, Jennifer Connelly, Ch~ United S~ November 16,~         2009
##  5 21    Robert Luket~ Jim Sturgess, Kevin Spacey, Kate Bosworth, Aaron Y~ United S~ January 1, 2~         2008
##  6 46    Serdar Akar   Erdal Besikçioglu, Yasemin Allen, Melis Birkan, Sa~ Turkey    July 1, 2017          2016
##  7 122   Yasir Al Yas~ Amina Khalil, Ahmed Dawood, Tarek Lotfy, Ahmed El ~ Egypt     June 1, 2020          2019
##  8 187   Kevin Reynol~ Samuel L. Jackson, John Heard, Kelly Rowan, Clifto~ United S~ November 1, ~         1997
##  9 706   Shravan Kumar Divya Dutta, Atul Kulkarni, Mohan Agashe, Anupam S~ India     April 1, 2019         2019
## 10 1920  Vikram Bhatt  Rajneesh Duggal, Adah Sharma, Indraneil Sengupta, ~ India     December 15,~         2008
## # ... with 7,777 more rows
# Selecionando todas as colunas exceto aquelas em um intervalo (inclusive)
select(tb_netflix, -(date_added:description))
## # A tibble: 7,787 x 6
##    show_id type    title director      cast                                                          country   
##    <chr>   <chr>   <chr> <chr>         <chr>                                                         <chr>     
##  1 s1      TV Show 3%    <NA>          João Miguel, Bianca Comparato, Michel Gomes, Rodolfo Valente~ Brazil    
##  2 s2      Movie   7:19  Jorge Michel~ Demián Bichir, Héctor Bonilla, Oscar Serrano, Azalia Ortiz, ~ Mexico    
##  3 s3      Movie   23:59 Gilbert Chan  Tedd Chan, Stella Chung, Henley Hii, Lawrence Koh, Tommy Kua~ Singapore 
##  4 s4      Movie   9     Shane Acker   Elijah Wood, John C. Reilly, Jennifer Connelly, Christopher ~ United St~
##  5 s5      Movie   21    Robert Luket~ Jim Sturgess, Kevin Spacey, Kate Bosworth, Aaron Yoo, Liza L~ United St~
##  6 s6      TV Show 46    Serdar Akar   Erdal Besikçioglu, Yasemin Allen, Melis Birkan, Saygin Soysa~ Turkey    
##  7 s7      Movie   122   Yasir Al Yas~ Amina Khalil, Ahmed Dawood, Tarek Lotfy, Ahmed El Fishawy, M~ Egypt     
##  8 s8      Movie   187   Kevin Reynol~ Samuel L. Jackson, John Heard, Kelly Rowan, Clifton Collins ~ United St~
##  9 s9      Movie   706   Shravan Kumar Divya Dutta, Atul Kulkarni, Mohan Agashe, Anupam Shyam, Raay~ India     
## 10 s10     Movie   1920  Vikram Bhatt  Rajneesh Duggal, Adah Sharma, Indraneil Sengupta, Anjori Ala~ India     
## # ... with 7,777 more rows

DICA: Existem helper functions que podemos usar dentro de select(). São funções que lembram o funcionamento de uma regular expression (Vamos ver no curso avançado com mais detalhes) para identificarmos nomes de colunas que atendem a determinado critério. São muito úteis com grandes datasets: starts_with(), ends_with(), matches() e contains().

Vamos por exemplo selecionar todas as colunas que começam com a letra d:

select(tb_netflix, starts_with("d"))
## # A tibble: 7,787 x 4
##    director       date_added      duration description                                                         
##    <chr>          <chr>           <chr>    <chr>                                                               
##  1 <NA>           August 14, 2020 4 Seaso~ In a future where the elite inhabit an island paradise far from the~
##  2 Jorge Michel ~ December 23, 2~ 93 min   After a devastating earthquake hits Mexico City, trapped survivors ~
##  3 Gilbert Chan   December 20, 2~ 78 min   When an army recruit is found dead, his fellow soldiers are forced ~
##  4 Shane Acker    November 16, 2~ 80 min   In a postapocalyptic world, rag-doll robots hide in fear from dange~
##  5 Robert Luketic January 1, 2020 123 min  A brilliant group of students become card-counting experts with the~
##  6 Serdar Akar    July 1, 2017    1 Season A genetics professor experiments with a treatment for his comatose ~
##  7 Yasir Al Yasi~ June 1, 2020    95 min   After an awful accident, a couple admitted to a grisly hospital are~
##  8 Kevin Reynolds November 1, 20~ 119 min  After one of his high school students attacks him, dedicated teache~
##  9 Shravan Kumar  April 1, 2019   118 min  When a doctor goes missing, his psychiatrist wife treats the bizarr~
## 10 Vikram Bhatt   December 15, 2~ 143 min  An architect and his wife move into a castle that is slated to beco~
## # ... with 7,777 more rows

select() pode ser usada inclusive para renomear variáveis:

select(tb_netflix, listed_category = listed_in)
## # A tibble: 7,787 x 1
##    listed_category                                         
##    <chr>                                                   
##  1 International TV Shows, TV Dramas, TV Sci-Fi & Fantasy  
##  2 Dramas, International Movies                            
##  3 Horror Movies, International Movies                     
##  4 Action & Adventure, Independent Movies, Sci-Fi & Fantasy
##  5 Dramas                                                  
##  6 International TV Shows, TV Dramas, TV Mysteries         
##  7 Horror Movies, International Movies                     
##  8 Dramas                                                  
##  9 Horror Movies, International Movies                     
## 10 Horror Movies, International Movies, Thrillers          
## # ... with 7,777 more rows

A nova variável será chamada listed_category e receberá toda a informação da original listed_in.

No entanto, select() “abandona” todas as demais variáveis quando você faz uma renomeação. É melhor então é usar rename():

rename(tb_netflix,  listed_category = listed_in)
## # A tibble: 7,787 x 12
##    show_id type   title director  cast        country date_added release_year rating duration listed_category  
##    <chr>   <chr>  <chr> <chr>     <chr>       <chr>   <chr>             <dbl> <chr>  <chr>    <chr>            
##  1 s1      TV Sh~ 3%    <NA>      João Migue~ Brazil  August 14~         2020 TV-MA  4 Seaso~ International TV~
##  2 s2      Movie  7:19  Jorge Mi~ Demián Bic~ Mexico  December ~         2016 TV-MA  93 min   Dramas, Internat~
##  3 s3      Movie  23:59 Gilbert ~ Tedd Chan,~ Singap~ December ~         2011 R      78 min   Horror Movies, I~
##  4 s4      Movie  9     Shane Ac~ Elijah Woo~ United~ November ~         2009 PG-13  80 min   Action & Adventu~
##  5 s5      Movie  21    Robert L~ Jim Sturge~ United~ January 1~         2008 PG-13  123 min  Dramas           
##  6 s6      TV Sh~ 46    Serdar A~ Erdal Besi~ Turkey  July 1, 2~         2016 TV-MA  1 Season International TV~
##  7 s7      Movie  122   Yasir Al~ Amina Khal~ Egypt   June 1, 2~         2019 TV-MA  95 min   Horror Movies, I~
##  8 s8      Movie  187   Kevin Re~ Samuel L. ~ United~ November ~         1997 R      119 min  Dramas           
##  9 s9      Movie  706   Shravan ~ Divya Dutt~ India   April 1, ~         2019 TV-14  118 min  Horror Movies, I~
## 10 s10     Movie  1920  Vikram B~ Rajneesh D~ India   December ~         2008 TV-MA  143 min  Horror Movies, I~
## # ... with 7,777 more rows, and 1 more variable: description <chr>

4.1.4 Adicionando novas colunas com mutate()

Além de selecionar conjuntos de colunas existentes, é geralmente útil adicionar novas colunas que são funções de colunas já presentes no tibble/dataframe. Veja um exemplo com mutate(), onde queremos calcular o preço por unidade de volume:

mutate(tb_game_sales,
  part_na_sales = NA_Sales/Global_Sales,
)
## # A tibble: 16,598 x 12
##     Rank Name  Platform Year  Genre Publisher NA_Sales EU_Sales JP_Sales Other_Sales Global_Sales part_na_sales
##    <dbl> <chr> <chr>    <chr> <chr> <chr>        <dbl>    <dbl>    <dbl>       <dbl>        <dbl>         <dbl>
##  1     1 Wii ~ Wii      2006  Spor~ Nintendo      41.5    29.0      3.77        8.46         82.7         0.501
##  2     2 Supe~ NES      1985  Plat~ Nintendo      29.1     3.58     6.81        0.77         40.2         0.723
##  3     3 Mari~ Wii      2008  Raci~ Nintendo      15.8    12.9      3.79        3.31         35.8         0.442
##  4     4 Wii ~ Wii      2009  Spor~ Nintendo      15.8    11.0      3.28        2.96         33           0.477
##  5     5 Poke~ GB       1996  Role~ Nintendo      11.3     8.89    10.2         1            31.4         0.359
##  6     6 Tetr~ GB       1989  Puzz~ Nintendo      23.2     2.26     4.22        0.58         30.3         0.767
##  7     7 New ~ DS       2006  Plat~ Nintendo      11.4     9.23     6.5         2.9          30.0         0.379
##  8     8 Wii ~ Wii      2006  Misc  Nintendo      14.0     9.2      2.93        2.85         29.0         0.483
##  9     9 New ~ Wii      2009  Plat~ Nintendo      14.6     7.06     4.7         2.26         28.6         0.510
## 10    10 Duck~ NES      1984  Shoo~ Nintendo      26.9     0.63     0.28        0.47         28.3         0.951
## # ... with 16,588 more rows

mutate() nos permite ainda nos referir a colunas que acabamos de criar no mesmo comando. Vamos salvar esta alteração em um novo tibble, chamado tb_game_sales_trans

tb_game_sales_trans <- mutate(tb_game_sales,
  part_na_sales = NA_Sales/Global_Sales,
  part_eu_sales = EU_Sales/Global_Sales,
  part_jp_sales = JP_Sales/Global_Sales,
  part_os_sales = Other_Sales/Global_Sales
)

Se só nos interessarem as novas variáveis, usaríamos transmute():

transmute(tb_game_sales,
  part_na_sales = NA_Sales/Global_Sales,
  part_eu_sales = EU_Sales/Global_Sales,
  part_jp_sales = JP_Sales/Global_Sales,
  part_os_sales = Other_Sales/Global_Sales
)
## # A tibble: 16,598 x 4
##    part_na_sales part_eu_sales part_jp_sales part_os_sales
##            <dbl>         <dbl>         <dbl>         <dbl>
##  1         0.501        0.351        0.0456         0.102 
##  2         0.723        0.0890       0.169          0.0191
##  3         0.442        0.360        0.106          0.0924
##  4         0.477        0.334        0.0994         0.0897
##  5         0.359        0.283        0.326          0.0319
##  6         0.767        0.0747       0.139          0.0192
##  7         0.379        0.308        0.217          0.0966
##  8         0.483        0.317        0.101          0.0982
##  9         0.510        0.247        0.164          0.0790
## 10         0.951        0.0223       0.00989        0.0166
## # ... with 16,588 more rows

4.1.5 Modificando entradas com mutate() ou transmute() + case_when()

case_when() é uma função do pacote dplyr que nos permite modificar as variáveis a partir de uma sequência de condições que devem ser respeitadas.

SE CONDIÇÃO1 VERDADEIRA ~ FAÇA TAL COISA;
SENÃO ~ FAÇA OUTRA COISA

Ela substitui as estruturas condicionais nativas do R (função ifelse()) e é inspirada na declaração equivalente em SQL CASE WHEN. Os argumentos da função case_when() obedecem à seguinte estrutura: operação condicional ~ novo valor. No lado esquerdo do ~, você tem a comparação a ser feita. No lado direito, temos o novo valor a ser atribuído caso o resultado da comparação seja TRUE. Você pode tratar, inclusive, mais de uma condição, desde que parta do caso mais específico para o mais geral.

case_when(
  condição1 ~ "novo_valor1",
  condição2 ~ "novo_valor2",
  condição3 ~ "novo_valor3",
  TRUE ~ "valor para os demais casos não atendidos pelas condições acima"
)

Geralmente, no contexto de análise de dados com dplyr, utilizamos case_when() dentro de uma função mutate() ou transmute (que traz a apenas a nova coluna criada), uma vez que pretendemos alterar as entradas de uma coluna, alterando, portanto, a própria coluna.

No tibble tb_games_sales, vamos criar uma nova coluna de caracteres chamada nivel, em que classificaremos um valor em: alto se NA_sales > mean(NA_sales); baixo se NA_sales < mean(NA_sales) ou razoavel nos demais casos:

transmute(tb_game_sales,
       nivel_na =
         case_when(
           NA_sales > mean(NA_Sales) ~ "alto",
           NA_sales < mean(NA_Sales) ~ "baixo",
           TRUE ~ "razoável"
         ))

4.1.6 Sumarizando valores com summarise()

O último “verbo” de dplyr é summarise() (ou summarize). Ele colapsa um tibble/dataframe em uma única linha.

summarise(tb_game_sales,
  max_venda_na = max(NA_Sales, na.rm = TRUE)
)
## # A tibble: 1 x 1
##   max_venda_na
##          <dbl>
## 1         41.5

DICA: O parâmetro na.rm = TRUE dentro da função max() serve para que esta desconsidere os valores falatantes (NA) ao calcular a máximo Do contrário, na existência de missing values NA, a função sempre retornará NA. Isso também vale para outras funções matemáticas de funcionamento vetorizado, como sum(), mean e min, por exemplo.

Dependendo do seu objetivo, pode ser mais útil utilizar o “verbo” group_by() que veremos mais a frente. Com ele poderemos calcular o valor médio por plataforma por exemplo.

4.2 Estrutura do dplyr

Note que a sintaxe e funcionamento de todos os verbos de dplyr apresentados até aqui são bem similares:

  • o primeiro argumento é um tibble/dataframe;

  • os argumentos subsequentes descrevem o que fazer com os dados. Podemos nos referir às colunas do tibble/dataframe diretamente sem a necessidade de usar $ ou indexação por [].

  • o resultado é um novo tibble/dataframe.

Juntas, essas propriedades facilitam encadear múltiplos passos simples para alcançar um resultado complexo. O restante do que dplyr faz, vem de aplicar as 5 funções que vimos até aqui a diferentes tipos de dados. Ao invpes de trabalharmos com dados desagregados, vamos passar a trabalhar agora com dados agrupados por uma ou mais variáveis.

4.2.1 Operações agrupadas

Os verbos de dplyr tornam-se ainda mais poderosos quando os aplicamos a grupos de observações dentro de um conjunto de dados. Fazemos isso com a função group_by(). Ela “quebra” o dataset em grupos específicos de linhas. No início, não vemos qualquer alteração. É como se elas ficassem em segundo plano. No entanto, ao aplicarmos algum dos verbos principais no dataset “alterado” por group_by, eles automaticamente serão aplicados por grupo ou “by group”.

O uso de agrupamento afeta o resultado dos verbos principais da seguinte forma:

  • select() agrupado é o mesmo que não agrupado, exceto pelo fato que as variáveis de agrupamento são sempre preservadas.

  • arrange() agrupado é mesmo que não agrupado, a não ser que usemos .by_group = TRUE, caso em que ordena primeiro pelas variáveis de agrupamento;

  • mutate() e filter() são bastante úteis em conjunto com window functions (como rank() ou min(x) == x) (Ver vignette de “window-functions” do dplyr);

  • summarise() calcula o sumário para cada grupo.

No exemplo a seguir, nós separamos o dataset por Platform, contando o número de registros para cada um das plataformas (count = n()), computando a valor médio por plataforma (valor_medio_plataforma = mean(Global_Sales, na.rm = TRUE)).

by_platform <- group_by(tb_game_sales, Platform)
valor_medio_plataforma <- summarise(
  by_platform,
  count = n(),
  mvp = mean(Global_Sales, na.rm = TRUE)
)

Note que summarise() é normalmente utilizada com aggregate functions, as quais recebem um vetor de valores e retornam um único número. Há muito exemplos úteis do base R que podem ser utilizados, como min(), max(), mean(), sum(), sd(), median(), etc. dplyr fornece mais algumas outras bem úteis:

  • n(): número de observações no grupo atual;
  • n_distinct(x): número de valores únicos em x;
  • first(x), last(x) e nth(x, n) funcionam de forma similar a x[1], x[length(x)] e x[n], mas nos dão maior controle sobre resultado caso algum valor seja missing.

4.2.2 Cuidados com os nomes de variáveis

Uma das melhores características do pacote dplyr é que podemos nos referir as variáveis de um tibble ou dataframe como se fossem variáveis regulares (aquelas que estão no Global Environment). No entanto, a sintaxe de referência para nomes de colunas escondem algumas diferenças entre os verbos. Por exemplo, um nome ou valor de coluna passado para select() não tem o mesmo significado do que teria em mutate().

Veja formas equivalentes do ponto de vista de dplyr:

select(tb_chess_game, victory_status)
## # A tibble: 20,058 x 1
##    victory_status
##    <chr>         
##  1 outoftime     
##  2 resign        
##  3 mate          
##  4 mate          
##  5 mate          
##  6 draw          
##  7 resign        
##  8 resign        
##  9 resign        
## 10 mate          
## # ... with 20,048 more rows
select(tb_chess_game, 6)
## # A tibble: 20,058 x 1
##    victory_status
##    <chr>         
##  1 outoftime     
##  2 resign        
##  3 mate          
##  4 mate          
##  5 mate          
##  6 draw          
##  7 resign        
##  8 resign        
##  9 resign        
## 10 mate          
## # ... with 20,048 more rows

Se houver uma variável no Global Environment com o mesmo nome de uma coluna de nosso tibble/dataframe, o dplyr dará prioridade à variável que está no tibble.

victory_status <- "Vencedor"
select(tb_chess_game, victory_status)
## # A tibble: 20,058 x 1
##    victory_status
##    <chr>         
##  1 outoftime     
##  2 resign        
##  3 mate          
##  4 mate          
##  5 mate          
##  6 draw          
##  7 resign        
##  8 resign        
##  9 resign        
## 10 mate          
## # ... with 20,048 more rows

4.3 Usando o Pipe %>%

dplyr é funcional no sentido de que os chamados às funções não tem efeitos colaterais. Ou seja, você sempre precisa salvar seus resultados. Isso faz com que não tenhámos um código tão elegante, especialmente quando vamos fazer várias operações.

Para dar uma solução elegante ao problema, dplyr utiliza o operador pipe %>% do pacote magritrr. x %>% f(y) equivale a f(x, y). Então, podemos utilizar esse operador para reescrever múltiplas operações que podemos ler da esquerda para direita e de cima para baixo.

tb_game_sales %>% 
  group_by(Platform) %>% 
  summarise(count = n(),
  mvp = mean(Global_Sales, na.rm = TRUE))
## # A tibble: 31 x 3
##    Platform count    mvp
##    <chr>    <int>  <dbl>
##  1 2600       133 0.730 
##  2 3DO          3 0.0333
##  3 3DS        509 0.486 
##  4 DC          52 0.307 
##  5 DS        2163 0.380 
##  6 GB          98 2.61  
##  7 GBA        822 0.387 
##  8 GC         556 0.359 
##  9 GEN         27 1.05  
## 10 GG           1 0.04  
## # ... with 21 more rows

DICA: Note que o nome do tibble ou dataframe só precisa ser informado uma única vez logo ao início do processo.


4.4 Referências da seção

  • Wickham H.; François, R.; Henry, L.; Müller K. (2019). dplyr: A Grammar of Data Manipulation. R package version 0.8.1. URL https://CRAN.R-project.org/package=dplyr.

  • Wickham H.; François, R.; Henry, L.; Müller K. (2020). dplyr vignette: Introduction. URL http://dplyr.tidyverse.org.

  • Wickham, H.; Grolemund, G. (2016). R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. O’Reilly Media. december 2016. 522 pages. Disponível em: https://www.r4ds.co.nz.

4.4.1 Exercícios

  1. Importe a tabela selecionada contendo uma projeção do Censo Escolar Agregado por número de alunos por etapa de ensino para os anos de 2020 e 2021 .

  2. Qual é o número de alunos por série de ensino em 2019? Escreva uma nova tabela txt ou csv com esses dados.

  3. Agrupe os dados pelos UFs de SP, RJ e MG e salve uma tabela separada (txt ou csv) para cada um dos agrupamentos. DICA: Pesquise sobre como separar dataframes por grupos com dplyr e como indexar listas.

  4. Identifique o estado com menor número de alunos projetado para 2020.

  5. Crie e salve uma nova tabela contendo o número de escolas por UF.