-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy path073-tidyr.Rmd
307 lines (218 loc) · 11.2 KB
/
073-tidyr.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
## O pacote tidyr {#tidyr}
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}` possui funções que nos ajudam a transformar 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 pivotar a base.
- `nest()` e `unnest()`: para criar *list columns*.
Como motivação para utilizar esssas funções, vamos utilizar a nossa boa e velha base `imdb`. Essa base pode ser baixada [clicando aqui](https://github.com/curso-r/livro-material/raw/master/assets/data/imdb.rds).
```{r, include = FALSE}
library(dplyr)
library(tidyr)
library(ggplot2)
imdb <- readr::read_rds("assets/data/imdb.rds")
```
```{r, eval = FALSE}
library(dplyr)
library(tidyr)
library(ggplot2)
imdb <- readr::read_rds("imdb.rds")
```
### `separate()` e `unite()`
A função `separate()` separa duas ou mais variáveis que estão concatenadas em uma mesma coluna. A sintaxe da função está apresentada abaixo.
```{r eval=FALSE}
dados %>%
separate(
col = coluna_velha,
into = c("colunas", "novas"),
sep = "separador"
)
```
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 %>% pull(generos) %>% head()
```
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 = ","
)
```
Também podemos usar a função `separate()` para separar os nomes que fazem parte da coluna `elenco`:
```{r}
imdb_atuacao <- imdb %>%
separate(
col = elenco,
into = c("atuacao1", "atuacao2", "atuacao3"),
sep = ","
)
imdb_atuacao
```
A função `unite()` realiza a operação inversa da função `separate()`. Ela concatena os valores de várias variáveis em uma única coluna. A sintaxe é a seguinte:
```{r eval=FALSE, echo=TRUE}
dados %>%
unite(
col = coluna_nova,
colunas_para_juntar,
sep = "separador"
)
```
Usando a base `imdb_atuacao` gerada no exemplo anterior, vamos agora transformar as colunas `atuacao1`, `atuacao2` e `atuacao3` em uma única coluna `atuacao_principal`. Lembrando que essas colunas estão no formato abaixo.
```{r}
imdb_atuacao %>% select(starts_with("atuacao")) %>% head(3)
```
Veja que agora a coluna `atuacao_principal` possui os 3 atores concatenados. Se a ordem das colunas `atuacao1`, `atuacao2` e `atuacao3` 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_atuacao %>%
unite(
col = "atuacao_principal",
starts_with("atuacao"),
sep = " - "
) %>%
select(atuacao_principal)
```
### Pivotagem
O conceito de pivotagem no *tidyverse* se refere a mudança da estrutura da base, geralmente para alcançar o formato *tidy*.
Geralmente 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.
Agora, no lugar de `gather()`, utilizamos a função `pivot_longer()`. Abaixo, usando a base `imdb_atuacao` gerada anteriormente, transformamos as colunas `atuacao1`, `atuacao2` e `atuacao3` em duas colunas: `ator_atriz` e `protagonismo`.
```{r}
imdb_atuacao %>%
pivot_longer(
cols = starts_with("atuacao"),
names_to = "protagonismo",
values_to = "ator_atriz"
) %>%
select(titulo, ator_atriz, protagonismo) %>%
head(6)
```
Se considerarmos que na análise da base IMDB cada observação deve ser um filme, então essa nova base já não mais *tidy*, pois agora cada filme aparece em três linhas diferentes, uma vez para cada um de seus atores.
Nesse sentido, embora possa parecer que a variável `protagonismo` estava implícita na base original, ela não é uma variável de fato. Todos filmes têm um `ator_1`, um `ator_2` e um `ator_3`. Não existe nenhuma informação sobre o filme que podemos tirar da coluna `protagonismo`, pois ela qualifica apenas os atores, não o filme em si.
A função `pivot_wider()` faz a operação inversa da `pivot_longer()`. Se aplicarmos as duas funções em sequência, voltamos para a base original.
```{r}
imdb_atuacao %>%
pivot_longer(
cols = starts_with("atuacao"),
names_to = "ator_protagonismo",
values_to = "ator_nome"
) %>%
pivot_wider(
names_from = "ator_protagonismo",
values_from = "ator_nome"
) %>%
head(4)
```
A base `imdb` não possui nenhuma variável que faça sentido aplicarmos diretamente a função `pivot_wider()`. Vamos então considerar a seguinte tabela derivada da base `imdb`:
```{r}
tab_romance_terror <- imdb %>%
filter(ano >= 2010) %>%
mutate(
genero = case_when(
stringr::str_detect(generos, "Romance") ~ "Romance",
stringr::str_detect(generos, "Horror") ~ "Horror",
TRUE ~ NA_character_
)
) %>%
filter(!is.na(genero)) %>%
group_by(ano, genero) %>%
summarise(receita_media = mean(receita, na.rm = TRUE))
```
Essa tabela possui a receita média dos filmes de romance e terror nos anos de 2010 a 2016.
Para apresentar essa tabela em uma reunião, por exemplo, pode ficar ser mais agradável ter os anos nas colunas e não nas linhas. Para isso, basta utilizarmos a função `pivot_wider()`.
```{r}
# Tabela original
tab_romance_terror
```
```{r}
# Tabela com os anos nas colunas
tab_romance_terror %>%
pivot_wider(
names_from = ano,
values_from = receita_media
)
```
Esse é um caso que *bagunçar* um pouquinho a tabela nos trouxe um resultado desejado.
### *List columns*
Um terceiro conceito de dados *tidy* é que cada célula da tabela possui um valor. No entanto, quando estamos programando, muitas vezes vale apena abandonar essa definição e guardarmos objetos mais complexos nas células de uma tabela.
Utilizando as chamadas *list columns* podemos guardar virtualmente qualquer objeto em nossas *tibbles*, como gráficos, resultados de modelos ou até mesmo outras tabelas.
Uma forma de trabalhar com *list columns* consiste em utilizarmos as funções
- `nest()`: para criar uma *list column*;
- `unnest()`: para desfazer uma *list column*.
A forma mais simples de utilizarmos uma *list column* é *aninhar* a nossa base com relação a uma variável.
```{r}
imdb_nest <- imdb %>%
group_by(ano) %>%
nest() %>%
arrange(ano)
head(imdb_nest, 8)
```
A base `imdb_nest` possui duas colunas `ano` e `data` e uma linha para cada ano. Na coluna `data`, temos o restante da base `imdb`, recortada para cada um dos anos.
Abaixo, acessmos os dados do único filme de 1916 (primeira linha da base `imdb_nest`).
```{r}
imdb_nest$data[[1]]
```
Imagine que queiramos fazer, para cada ano, um gráfico de dispersão da receita contra o orçamento dos filmes lançados no ano.
Com a base no formato de *list columns*, basta criarmos uma função para gerar o gráfico e utilizarmos a função `purrr::map()`.
Abaixo, construímos a função `fazer_grafico_dispersao()`, que será aplicada a cada uma das bases contidas na coluna `data` da base `imdb_nest`. Os gráficos, respectivamos a cada ano, são salvos na coluna `grafico`.
```{r}
fazer_grafico_dispersao <- function(tab) {
tab %>%
ggplot(aes(x = orcamento, y = receita)) +
geom_point()
}
imdb_graficos <- imdb_nest %>%
mutate(
grafico = purrr::map(data, fazer_grafico_dispersao)
)
head(imdb_graficos, 6)
```
Para acessar cada um dos gráficos, basta rodar o código abaixo.
```{r, fig.height=5, fig.width=5, fig.align="center"}
# Pegando o gráfico referente ao ano de 2000
imdb_graficos$grafico[[74]]
```
Ou, escolhendo diretamente pelo ano
```{r, fig.height=5, fig.width=5, fig.align="center"}
imdb_graficos %>%
filter(ano == 2000) %>%
pull(grafico)
```
A função `unnest()` remove a estrutura de *list column*. Fazendo a operação abaixo, voltamos para a base `imdb` original.
```{r}
imdb_nest %>%
unnest(cols = "data")
```
### Exercícios
**1.** Crie 5 novas colunas de idiomas na base imdb, cada uma com um dos idiomas contidos na coluna `idioma`. Para os filmes com menos de 5 idiomas, substitua os valores `NA` pela string "Inexistente".
**2.** Substitua os "????" no código abaixo para criar uma tabela do lucro médio dos filmes ao longo dos anos de 2000 a 2020, com cada ano sendo uma coluna da base.
```{r, eval = FALSE}
imdb %>%
mutate(lucro = receita - orcamento) %>%
filter(ano %in% "????") %>%
group_by("????") %>%
summarise(lucro_medio = "????") %>%
pivot_wider(names_from = "????", values_from = "????")
```
Para os exercícios 3, 4 e 5, vamos utilize a base `pokemon`, disponível no pacote `basesCursoR`.
```{r, eval = FALSE}
install.packages("remotes")
remotes::install_github("curso-r/basesCursoR")
pokemon <- basesCursoR::pegar_base("pokemon")
```
**3.** Utilize a função `unite()` para juntar as duas colunas de tipo em uma única coluna na base pokemon.
**4.** Utilize a função `unite()` para juntar as três colunas de cor em uma única coluna na base pokemon. Faça isso sem remover as 3 colunas originais.
**5.** Utilizando a base `pokemon`, resolva os itens abaixo:
**a.** Utilize a função `pivot_longer()` para criar uma única coluna de tipo na base pokemon.
**b.** Utilize a base criada no item (a) e escreva um código para descobrir qual o tipo mais frequente na base, independentemente se ele é primário (tipo_1) ou secundário (tipo_2).
**6.** DESAFIO! Escreva uma função que receba uma base qualquer e o nome de uma coluna numérica dessa base e retorne uma figura com um gráfico de dispersão da coluna escolhida contra cada uma das outras variáveis numéricas da base.