class: center, middle, inverse, title-slide .title[ # R para Ciência de Dados 2 ] .subtitle[ ## Tidyr e Dplyr 1.0 ] .author[ ###
] --- class: middle, center, inverse # Tidyr --- # Dados arrumados Dentro do `tidyverse`, uma base *tidy* é uma base fácil de se trabalhar, isto é, fácil de se fazer manipulação de dados, fácil de se criar visualizações, fácil de se ajustar modelos e por aí vai. Na prática, uma base *tidy* é aquela que se encaixa bem no *framework* do `tidyverse`, pois os pacotes como o `dplyr` e o `ggplot2` foram desenvolvidos para funcionar bem com bases *tidy*. E assim como esses pacotes motivaram o uso de bases *tidy*, o conceito *tidy* motiva o surgimento de novos *frameworks*, como o `tidymodels` para modelagem. As duas propriedades mais importantes de uma base *tidy* são: - Cada coluna é uma variável; - Cada linha é uma observação. Essa definição proporciona uma maneira consistente de se referir a variáveis (nomes de colunas) e observações (índices das linhas). --- # O pacote tidyr O pacote `tidyr` possui funções que nos ajudam a deixar uma base bagunçada em uma base *tidy*. Ou então, nos ajudam a bagunçar um pouquinho a nossa base quando isso nos ajudar a produzir o resultados que queremos. Vamos ver aqui algumas de suas principais funções: - `separate()` e `unite()`: para separar variáveis concatenadas em uma única coluna ou uni-las. - `pivot_wider()` e `pivot_longer()`: para pirvotar a base. --- # Motivação Como motivação para utilizar esssas funções, vamos utilizar a nossa boa e velha base `imdb`. ```r imdb <- readr::read_rds("../data/imdb.rds") imdb ``` ``` #> # A tibble: 3,713 × 15 #> titulo ano diretor duracao cor generos pais class…¹ orcam…² receita #> <chr> <int> <chr> <int> <chr> <chr> <chr> <chr> <int> <int> #> 1 Avatar 2009 James … 178 Color Action… USA A part… 2.37e8 7.61e8 #> 2 Pirates of … 2007 Gore V… 169 Color Action… USA A part… 3 e8 3.09e8 #> 3 The Dark Kn… 2012 Christ… 164 Color Action… USA A part… 2.5 e8 4.48e8 #> 4 John Carter 2012 Andrew… 132 Color Action… USA A part… 2.64e8 7.31e7 #> 5 Spider-Man 3 2007 Sam Ra… 156 Color Action… USA A part… 2.58e8 3.37e8 #> # … with 3,708 more rows, 5 more variables: nota_imdb <dbl>, #> # likes_facebook <int>, ator_1 <chr>, ator_2 <chr>, ator_3 <chr>, and #> # abbreviated variable names ¹classificacao, ²orcamento ``` --- # separate() A função `separate()` separa duas ou mais variáveis que estão concatenadas em uma mesma coluna. Como exemplo, vamos transformar a coluna `generos` da base IMDB em três colunas, cada uma com um dos gêneros do filme. Lembrando que os valores da coluna `generos` estão no seguinte formato: ```r imdb %>% select(generos) ``` ``` #> # A tibble: 3,713 × 1 #> generos #> <chr> #> 1 Action|Adventure|Fantasy|Sci-Fi #> 2 Action|Adventure|Fantasy #> 3 Action|Thriller #> 4 Action|Adventure|Sci-Fi #> 5 Action|Adventure|Romance #> # … with 3,708 more rows ``` --- # separate() Veja que agora, temos 3 colunas de gênero. Filmes com menos de 3 gêneros recebem `NA` na coluna `genero2` e/ou `genero3`. Os gêneros sobressalentes são descartados, assim como a coluna `generos` original. ```r imdb %>% separate(col = generos, into = c("genero1", "genero2", "genero3"), sep = "\\|") %>% select(starts_with("genero")) ``` ``` #> # A tibble: 3,713 × 3 #> genero1 genero2 genero3 #> <chr> <chr> <chr> #> 1 Action Adventure Fantasy #> 2 Action Adventure Fantasy #> 3 Action Thriller <NA> #> 4 Action Adventure Sci-Fi #> 5 Action Adventure Romance #> # … with 3,708 more rows ``` --- # unite() A função `unite()` realiza a operação inversa da função `separate()`. Como exemplo, vamos agora transformar as colunas `ator1`, `ator2` e `ator3` em uma única coluna `atores`. Lembrando que essas colunas estão no formato abaixo. ```r imdb %>% select(starts_with("ator")) ``` ``` #> # A tibble: 3,713 × 3 #> ator_1 ator_2 ator_3 #> <chr> <chr> <chr> #> 1 CCH Pounder Joel David Moore Wes Studi #> 2 Johnny Depp Orlando Bloom Jack Davenport #> 3 Tom Hardy Christian Bale Joseph Gordon-Levitt #> 4 Daryl Sabara Samantha Morton Polly Walker #> 5 J.K. Simmons James Franco Kirsten Dunst #> # … with 3,708 more rows ``` --- # unite() Veja que agora a coluna `elenco` possui os 3 atores/atrizes concatenados. Se a ordem das colunas `ator1`, `ator2` e `ator3` nos trazia a informação de protagonismo, essa informação passa a ficar implícita nesse novo formato. As 3 colunas originais são removidas da base resultante. ```r imdb %>% unite(col = "elenco", starts_with("ator"), sep = " - ") %>% select(elenco) ``` ``` #> # A tibble: 3,713 × 1 #> elenco #> <chr> #> 1 CCH Pounder - Joel David Moore - Wes Studi #> 2 Johnny Depp - Orlando Bloom - Jack Davenport #> 3 Tom Hardy - Christian Bale - Joseph Gordon-Levitt #> 4 Daryl Sabara - Samantha Morton - Polly Walker #> 5 J.K. Simmons - James Franco - Kirsten Dunst #> # … with 3,708 more rows ``` --- # Pivotagem O conceito de pivotagem no *tidyverse* se refere a mudança da estrutura da base, geralmente para alcançar o formato *tidy*. Normalmente realizamos pivotagem quando nossas linhas não são unidades observacionais ou nossas colunas não são variáveis. Ela é similiar à pivotagem do Excel, mas um pouco mais complexa. O ato de pivotar resulta em transformar uma base de dados *long* em *wide* e vice-versa. Uma base no formato *long* possui mais linhas e pode ter menos colunas, enquanto no formato *wide* poussi menos linhas e pode ter mais colunas Esses formatos são sempre relativos às colunas que estão sendo pivotadas, sendo que uma base *tidy* pode estar tanto no formato *long* quanto *wide*. Antigamente, utilizávamos as funções `gather()` e `spread()` para fazer as operações de pivotagem. Hoje em dia temos `pivot_longer()` e `pivot_wider()`. A demonstração a seguir é cortesia do [tidyexplain](https://www.garrickadenbuie.com/project/tidyexplain/#pivot-wider-and-longer). --- <img src="img/032-tidyr/pivotagem.gif" style="display: block; margin: auto;" /> --- # pivot_longer() Abaixo, transformamos as colunas `ator1`, `ator2` e `ator3` em duas colunas: `ator_atriz` e `protagonismo`. ```r imdb %>% pivot_longer( cols = starts_with("ator"), names_to = "protagonismo", values_to = "ator_atriz" ) %>% select(titulo, ator_atriz, protagonismo) ``` ``` #> # A tibble: 11,139 × 3 #> titulo ator_atriz protagonismo #> <chr> <chr> <chr> #> 1 Avatar CCH Pounder ator_1 #> 2 Avatar Joel David Moore ator_2 #> 3 Avatar Wes Studi ator_3 #> 4 Pirates of the Caribbean: At World's End Johnny Depp ator_1 #> 5 Pirates of the Caribbean: At World's End Orlando Bloom ator_2 #> # … with 11,134 more rows ``` --- # pivot_wider() A função `pivot_wider()` faz a operação inversa da `pivot_longer()`. Sem aplicarmos as duas funções em sequência, voltamos para a base original. ```r imdb_long %>% pivot_wider( names_from = protagonismo, values_from = ator_atriz ) %>% select(titulo, starts_with("ator")) ``` ``` #> # A tibble: 3,713 × 4 #> titulo ator_1 ator_2 ator_3 #> <chr> <chr> <chr> <chr> #> 1 Avatar CCH Pounder Joel David Moore Wes St… #> 2 Pirates of the Caribbean: At World's End Johnny Depp Orlando Bloom Jack D… #> 3 The Dark Knight Rises Tom Hardy Christian Bale Joseph… #> 4 John Carter Daryl Sabara Samantha Morton Polly … #> 5 Spider-Man 3 J.K. Simmons James Franco Kirste… #> # … with 3,708 more rows ``` --- class: middle, center, inverse # Dplyr 1.0 --- # O que já sabemos Já vimos que com os principais verbos do `dplyr` já conseguimos fazer diversas operações de manipulação de bases de dados. .pull-left[ - Selecionar colunas: `select()` - Ordenar linhas: `arrange()` - Filtrar linhas: `filter()` - Criar ou modificar colunas: `mutate()` - Agrupar e sumarizar: `group_by()` + `summarise()` ] .pull-right[ <img src="img/03-dplyr-plus/arte_dplyr.png" width="100%" /> ] --- # O novo dplyr A versão 1.0 do pacote `dplyr` foi oficialmente lançada em junho de 2020 e contou com diversas novidades Vamos falar das principais mudanças: - A nova função `across()`, que facilita aplicar uma mesma operação em várias colunas. - A repaginada função `rowwise()`, para fazer operações por linha. - Novas funcionalidades das funções `select()` e `rename()` e a nova função `relocate()`. Hoje vamos falar principalmente sobre a `across()` e as funções de _tidyselection_. --- # Motivação Base de dados de venda de casas na cidade de Ames, nos Estados Unidos. - 2930 linhas e 77 colunas. - Cada linha corresponde a uma casa vendida e cada coluna a uma característica da casa ou da venda. - Versão traduzida ```r install.packages("dados") casas <- dados::casas ``` - Base original: ```r install.packages("AmesHousing") data(ames_raw, package = "AmesHousing") ``` --- # across() A ideia da função `across()` é facilitar a aplicação de uma operação a diversas colunas da base. Antigamente fazíamos: ```r casas %>% group_by(geral_qualidade) %>% summarise( lote_area_media = mean(lote_area, na.rm = TRUE), venda_valor_medio = mean(venda_valor, na.rm = TRUE) ) ``` ``` #> # A tibble: 10 × 3 #> geral_qualidade lote_area_media venda_valor_medio #> <chr> <dbl> <dbl> #> 1 abaixo da média 8464. 106485. #> 2 acima da média 9788. 162130. #> 3 boa 10309. 205026. #> 4 excelente 12777. 368337. #> 5 média 9996. 134753. #> # … with 5 more rows ``` --- # across() Com a nova função `across()`, a sintaxe é simplificada. ```r casas %>% group_by(geral_qualidade) %>% summarise(across( .cols = c(lote_area, venda_valor), # Variáveis .fns = mean, na.rm = TRUE # Função )) ``` ``` #> # A tibble: 10 × 3 #> geral_qualidade lote_area venda_valor #> <chr> <dbl> <dbl> #> 1 abaixo da média 8464. 106485. #> 2 acima da média 9788. 162130. #> 3 boa 10309. 205026. #> 4 excelente 12777. 368337. #> 5 média 9996. 134753. #> # … with 5 more rows ``` --- # across() Usando `across()`, podemos facilmente aplicar uma função em todas as colunas da nossa base. O argumento padrão de `.cols` é `everything()`. ```r # Pegando apenas 5 colunas por questão de espaço casas %>% summarise(across(.fns = n_distinct)) %>% select(1:5) ``` ``` #> # A tibble: 1 × 5 #> ordem pid moradia_classe moradia_zoneamento lote_fachada #> <int> <int> <int> <int> <int> #> 1 2930 2930 16 7 129 ``` --- # across() Se quisermos selecionar as colunas a serem modificadas a partir de um teste lógico, utilizamos o ajudante `where()`. No exemplo abaixo, calculamos o número de valores distintos das colunas de categóricas: ```r # Pegando apenas 5 colunas por questão de espaço casas %>% summarise(across( .cols = where(is.character), # Variáveis .fns = n_distinct # Função )) %>% select(1:5) ``` ``` #> # A tibble: 1 × 5 #> pid moradia_classe moradia_zoneamento rua_tipo beco_tipo #> <int> <int> <int> <int> <int> #> 1 2930 16 7 2 3 ``` --- # across() Você também pode combinar testes lógicos com seleções de colunas. Calculamos as áreas médias, garantindo que pegamos apenas variáveis numéricas. ```r # Pegando apenas 4 colunas por questão de espaço casas %>% summarise(across( .cols = where(is.numeric) & contains("_area"), # Variáveis .fns = mean, na.rm = TRUE # Função )) %>% select(1:4) ``` ``` #> # A tibble: 1 × 4 #> lote_area alvenaria_area porao_area_com_acabamento_1 porao_area_com_acabamen…¹ #> <dbl> <dbl> <dbl> <dbl> #> 1 10148. 102. 443. 49.7 #> # … with abbreviated variable name ¹porao_area_com_acabamento_2 ``` --- # across() Fazer várias aplicações do `across()` também é possível: ```r casas %>% group_by(fundacao_tipo) %>% summarise( across(contains("area"), mean, na.rm = TRUE), across(where(is.character), ~sum(is.na(.x))), n_obs = n(), ) %>% select(1:2, 19:20, n_obs) ``` ``` #> # A tibble: 6 × 5 #> fundacao_tipo lote_area pid moradia_classe n_obs #> <chr> <dbl> <int> <int> <int> #> 1 bloco de concreto 10616. 0 0 1244 #> 2 concreto derrramado 10054. 0 0 1310 #> 3 laje 10250. 0 0 49 #> 4 madeira 9838. 0 0 5 #> 5 pedra 8659. 0 0 11 #> # … with 1 more row ``` --- # across() A última funcionalidade relevante do `across()` é a capacidade de receber uma lista de funções: ```r casas %>% group_by(rua_tipo) %>% summarise(across( .cols = c(lote_area, venda_valor), .fns = list("md" = mean, "mn" = median) )) ``` ``` #> # A tibble: 2 × 5 #> rua_tipo lote_area_md lote_area_mn venda_valor_md venda_valor_mn #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 cascalho 26607. 10420. 106663. 87425 #> 2 pavimentada 10080. 9436. 181101. 160375 ``` --- # across() O argumento `.names` define uma "fórmula" para a construção do nome das novas colunas: ```r casas %>% group_by(rua_tipo) %>% summarise(across( .cols = c(lote_area, venda_valor), .fns = list("md" = mean, "mn" = median), .names = "{.fn}_de_{.col}" # {nome função}_de_{nome coluna} )) ``` ``` #> # A tibble: 2 × 5 #> rua_tipo md_de_lote_area mn_de_lote_area md_de_venda_valor mn_de_venda_va…¹ #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 cascalho 26607. 10420. 106663. 87425 #> 2 pavimentada 10080. 9436. 181101. 160375 #> # … with abbreviated variable name ¹mn_de_venda_valor ``` --- # across() outros verbos O `across()` pode ser utilizado em todos os verbos do `dplyr` (com exceção do `select()` e `rename()`, já que ele não traz vantagens com relação ao que já podia ser feito) e isso unifica o modo que trabalhamos essas operações no `dplyr`. Vamos ver um exemplo para o `mutate()` e para o `filter()`. --- # across() O código abaixo transforma todas as variáveis que possuem "area" no nome, passando os valores de pés quadrados para metros quadrados. ```r casas %>% mutate(across( contains("area"), ~.x / 10.764 )) ``` Já o código a seguir filtra apenas as casas que possuem varanda aberta, cerca e lareira. ```r casas %>% filter(across( c(varanda_aberta_area, cerca_qualidade, lareira_qualidade), ~!is.na(.x) )) ``` --- # Notação: ~.x Anteriormente utilizamos uma notação nova chamada "notação de fórmula" (também conhecida como "notação lambda"). Ela é útil para simplificar a definição de novas funções: ```r # Função normal function(vec) { !is.na(vec) } # Se ela tem só 1 linha, não precisamos de chaves function(vec) !is.na(vec) # Se ela tem só 1 argumento, podemos sempre usar .x function(.x) !is.na(.x) # Se a função estiver no formato acima, podemos trocar function(.x) por ~ ~ !is.na(.x) ``` --- # Miscelânea de funções úteis Para quem quiser saber mais, vamos listar uma miscelânea de funções muito úteis, mas menos conhecidas do `dplyr`. - `bind_rows()`: para empilhar duas bases. - `case_when()`: generalização da `ifelse()` para várias condições. - `first()`, `last()`: para pegar o primeiro ou último valor de um vetor/coluna. - `na_if()`: para transformar um determinado valor de um vetor/coluna em `NA`. - `coalesce()`: para substituir os `NAs` de uma coluna pelos valores equivalentes de uma segunda coluna. - `lag()`, `lead()`: para gerar colunas defasadas. - `rename()`, `relocate()`, `c_across()`: outras novidades do `dplyr` 1.0 --- # Referências - [Documentação do dplyr](https://dplyr.tidyverse.org/) e [documentação do tidyr](https://tidyr.tidyverse.org/) - [Material de tidyverse da UFPR](http://www.leg.ufpr.br/~walmes/ensino/dsbd-linprog/slides/02-r-tidyverse.html#1) - [Livro da Curso-R](https://livro.curso-r.com/7-manipulacao.html) - [Apresentação Garret Grolemund](https://github.com/rstudio/webinars/blob/master/05-Data-Wrangling-with-R-and-RStudio/wrangling-webinar.pdf) - [Excelente blog post sobre manipulação de bases](https://www.garrickadenbuie.com/project/tidyexplain/#spread-and-gather%22)