-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path03_manipulacion-1.qmd
More file actions
371 lines (254 loc) · 15.4 KB
/
03_manipulacion-1.qmd
File metadata and controls
371 lines (254 loc) · 15.4 KB
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
---
title: "Manipulación de datos ordenados I"
---
El paquete **dplyr** provee una enorme cantidad de funciones útiles para manipular y analizar datos de manera intuitiva y expresiva.
El espíritu detrás de dplyr es que la enorme mayoría de los análisis, por más complicados que sean, involucran combinar y encadenar una serie relativamente acotada de acciones (o verbos).
En este curso vamos a centrarnos las cinco más comunes:
- `select()`: selecciona columnas de una tabla.
- `filter()`: selecciona (o filtra) filas de una tabla a partir de una o más condiciones lógicas.
- `group_by()`: agrupa una tabla en base al valor de una o más columnas.
- `mutate()`: agrega nuevas columnas a una tabla.
- `summarise()`: calcula estadísticas para cada grupo de una tabla.
::: ejercicio
Te dieron una tabla con datos de pingüinos de 3 especies (Adelia, Barbijo y Papua).
Las columnas son: `especie`, `largo_aleta`, `largo_pico`,
En base a esos datos, te piden que calcules la proporción promedio entre el largo de la aleta y el largo del pico para las especies Adelia y Barbijo.
¿En qué orden ejecutarías estos pasos para obtener el resultado deseado?
- usar `summarise()` para calcular la estadística `mean(proporcion_aleta_pico)` para cada `especie`
- usar `group_by()` para agrupar por la columna `especie`
- usar `mutate()` para agregar una columna llamada `proporcion_aleta_pico` calculada como `largo_aleta/largo_pico`.
- usar `filter()` para seleccionar las filas donde la columna `especie` no es Papua.
:::
Para usar dplyr primero hay que instalarlo (esto hay que hacerlo una sola vez por computadora) con el comando:
```{r, eval = FALSE}
install.packages("dplyr")
```
y luego cargarlo en memoria con
```{r message=FALSE, warning=FALSE}
library(dplyr)
```
Volvé a cargar los datos de pingüinos (para un recordatorio, podés ir a [Lectura de datos ordenados](02_lectura.qmd)):
```{r message=FALSE, warning=FALSE}
library(readr)
pinguinos <- read_csv("datos/pinguinos.csv")
```
## Seleccionando columnas con `select()`
Para quedarse únicamente con las columnas de especie y largo del pico, usá `select()`
```{r}
select(pinguinos, especie, largo_pico_mm)
```
¿Dónde quedó este resultado?
Si te fijás en la tabla `pinguinos`, su formato no cambió, sigue teniendo todas las columnas originales a pesar de nuestro select:
```{r}
pinguinos
```
`select()` y el resto de las funciones de dplyr siempre generan una nueva tabla y nunca modifican la tabla original.
Para guardar la tabla con las dos columnas `especie` e `largo_pico_mm` tenés que asignar el resultado a una nueva variable.
```{r}
pinguinos_isla <- select(pinguinos, especie, largo_pico_mm)
pinguinos_isla
```

## Filtrando filas con `filter()`
Ahora podés usar `filter()` para quedarte con sólo unas filas.
Por ejemplo, para quedarse con los pingüinos de la especie Adelia:
```{r}
filter(pinguinos, especie == "Adelia")
```
La mayoría de los análisis consisten en varios pasos que van generando tablas intermedias (en el primer desafío usaste 4 pasos para calcular la proporción entre el largo de la aleta y del pico)
La única tabla que te interesa es la última, por lo que ir asignando variables nuevas en cada paso intermedio es tedioso y poco práctico.
Para eso se usa el operador 'pipe' (`|>`).
El operador 'pipe' (`|>`) agarra el resultado de una función y se lo pasa a la siguiente.
Esto permite escribir el código como una cadena de funciones que van operando sobre el resultado de la anterior.
Las dos operaciones anteriores (seleccionar tres columnas y luego filtrar las filas correspondientes a Argentina) se pueden escribir uno después del otro y sin asignar los resultados intermedios a nuevas variables de esta forma:
```{r}
pinguinos |>
filter(especie == "Adelia") |>
select(especie, largo_pico_mm)
```
La forma de "leer" esto es "Tomá los datos en el data.frame `pinguinos`, **después** aplicale `filter()` tal que la especie sea Adelia, **después** aplicale `select()` para quedarte con la especie y el largo_pico_mm.
::: importante
Es posible que te encuentres o te hayas encontrado con esta otra versión del operador pipe `%>%`.
Esta es la pipe del paquete `magritter`, que usamos durante muchísimo tiempo (y seguimos usando) hasta que la nueva pipe `|>` se sumo a R 4.0.0.
:::
Cómo vimos, el primer argumento de todas las funciones de dplyr es el data frame sobre el cual van a operar, pero notá que en las líneas con `select()` y `filter()` no escribís la tabla explícitamente.
Esto es porque la pipe implícitamente pasa el resultado de las líneas anteriores como el primer argumento de la función siguiente.
Toma el data frame `pinguinos` y se lo pasa al primer argumento de `filter()`.
Luego el data frame resultante de esa operación pasa como el primer argumento de la función `select()` gracias al `|>`.
::: tip
En RStudio podés escribir `|>` usando el atajo de teclado Ctr + Shift + M.
¡Probalo!
:::
::: ejercicio
Completá esta cadena de código para producir una tabla que contenga los pingüinos con aletas mayores a 200 mm y la información de la especie y el largo de la aleta.
```{r, eval = FALSE}
pinguinos |>
filter(largo_aleta_mm > ___) |>
select(_____, ____)
```
:::
Como seguramente notaste en el ejercicio cuando aplicamos filtros usamos operaciones lógicas que devuelven `TRUE` o `FALSE` según si cumplen o no con la condición.
Para esto usamos _operadores lógicos_ como `==`, `>`, `<`, `>=`, `<=`, etc. R tiene varios operadores lógicos:
|Operador | Definición |
|---------|------------|
| < |menor que|
| x\|y | x **o** y |
| <= | menor o igual a |
| is.na(x) | chequea si x es NA |
| !is.na(x) | chequea si x **no** es NA |
| > | mayor que |
| >= | mayor o igual a |
| x %in% y | chequea si x esta en y |
| !(x %in% y) | chequea si x **no esta** en y |
| == | igual a |
| != | no igual a |
|!x | no x |
| x & z | x **y** z |
::: ejercicio
Es hora de revisar lo que vimos hasta acá.
1. Descargá el archivo que se encuentra [en este link](https://github.com/rse-r/intro-programacion/blob/main/ejercicios/dplyr.qmd) y guardalo en tu proyecto de trabajo en clase.
2. Resolvé los ejercicios propuestos de lectura (yapa!) y de filtros. En algunos casos tendrías que reemplazar los `_____` con el código correspondiente. En otros casos el ejercicio te pedirá escribir el código desde cero.
3. El archivo tiene más ejercicios, pero no te adelantes. Vamos a ver esos temas a continuación.
:::
## Agrupando y reduciendo con `group_by() |> summarise()`
Si quisieramos responder ¿cuál es el largo promedio de la aleta para cada especie de pingüino? tenemos que usar un combo de funciones: `group_by() |> summarise()`.
Es decir, primero agrupamos las filas de nuestro data.frame según la columna `especie` (esto genera 3 grupos) y luego calculamos el promedio de `largo_aleta_mm` para cada grupo.
Vamos paso a paso.
```{r}
pinguinos |>
group_by(especie)
```
A primera vista parecería que la función no hizo nada, pero fijate que el resultado ahora dice que tiene grupos ("Groups: "), y nos dice qué columna es la que agrupa los datos ("especie") y cuántos grupos hay ("3").
Cualquier operación que hagamos a continuación del `group_by()` van a aplicarse *para cada grupo*.
Para ver esto en acción, usá `summarise()` para computar el promedio del largo de la aleta.
```{r}
pinguinos |>
group_by(especie) |>
summarise(aleta_promedio = mean(largo_aleta_mm, na.rm = TRUE))
```
¡Tadá!
`summarise()` devuelve una tabla con una columna para la especie y otra nueva, llamada "aleta_promedio" que contiene el promedio de del largo de la aleta para cada grupo.
`group_by()` permite agrupar en base a múltiples columnas y `summarise()` permite generar múltiples columnas de resumen.
El siguiente código calcula la cantidad de pingüinos por especie y por isla y el promedio de su masa corporal.
```{r}
pinguinos |>
group_by(especie, isla) |>
summarise(cantidad = n(),
promedio_masa_corporal = mean(masa_corporal_g, na.rm = TRUE))
```
::: tip
La función `n()` cuenta la cantidad de elementos en cada grupo, en este caso la cantidad de pingüinos por especie e isla.
A diferencia de otras funciones que venimos usando no requiere de ningún argumento para funcionar en el contexto de `summarise()`
:::
El resultado va a siempre ser una tabla con la misma cantidad de filas que grupos y una cantidad de columnas igual a la cantidad de columnas usadas para agrupar y los estadísticos computados.
::: ejercicio
¿Cuál te imaginás que va a ser el resultado del siguiente código?
¿Cuántas filas y columnas va a tener?
(Tratá de pensarlo antes de correrlo.)
```{r, eval = FALSE}
pinguinos |>
summarise(promedio_pico = mean(largo_pico_mm, na.rm = TRUE))
```
:::
El combo `group_by() |> summarise()` se puede resumir en esta figura.
Las filas de una tabla se dividen en grupos, y luego cada grupo se "resume" en una fila en función del estadístico usado.

### `reframe()` entra a la cancha
`summarise()` funciona re bien hasta que que nos encontramos con la necesidad de hacer cuentas más complejas.
Por ejemplo, supongamos que queremos calcular el cuantil 0.25 de la masa corporal porque decidimos que todos los pinguinos con un peso por debajo de ese valor son "pequeños".
Con lo que vimos hasta ahora podemos calcularlo.
```{r}
pinguinos |>
group_by(especie) |>
summarise(cuantil_masa = quantile(masa_corporal_g, c(0.25), na.rm = TRUE))
```
Pero se vuelve tedioso si queremos otros cuantiles, por ejemplo el 0.75 (porque decidimos los pingüinos con masa mayor a ese valor sn "grandes").
Si intentamos extender el código anterior además del resultado vamos a terminar con un warning enorme.
```{r}
pinguinos |>
group_by(especie) |>
summarise(cuantil_masa = quantile(masa_corporal_g, c(0.25, 0.75), na.rm = TRUE))
```
El problema es que estamos opteniendo 2 valores para cada grupo, es decir el cuantil 0.25 y el 0.75 para cada especie.
En estos casos tenemos que usar `reframe()` que funciona de manera similar a `summarise()` pero está pensada para estos casos donde calculamos más de un valor por grupo.
```{r}
pinguinos |>
group_by(especie) |>
reframe(cuantil_masa = quantile(masa_corporal_g, c(0.25, 0.75), na.rm = TRUE))
```
## Creando nuevas columnas con `mutate()`
Todo esto está bien para hacer cálculos con columnas previamente existentes, pero muchas veces tenés que crear nuevas columnas.
Podríamos querer expresar la masa corporal de los pingüinos en kilos en vez de gramos.
```{r}
pinguinos |>
mutate(masa_corporal_kg = masa_corporal_g/100)
```
Recordá que las funciones de dplyr nunca modifican la tabla original.
`mutate()` devolvió una nueva tabla que es igual a la tabla `pinguinos` pero con la columna "masa_corporal_kg" agregada al final.
Si no asignamos este resultado a una variable, no podremos usarlo luego.
La función `mutate()` además de permitir crear nuevas columnas, también permite modificar o actualizar columnas existentes.
Para eso, solo es necesario asignarle el nombre de la columa que queremos modificar.
```{r}
pinguinos |>
mutate(especie = toupper(especie))
```
::: ejercicio
¿Te acordás del ejercicio que arrancaste cuando vimos filtros? Es hora de completarlo!
1. Completá los ejercicios de [este archivo](https://github.com/rse-r/intro-programacion/blob/main/ejercicios/dplyr.qmd) que ya deberías tener en tu proyecto.
2. Te vas a encontrar con ejercicios para practicar `summarise()` y `mutate()`. De nuevo, en algunos casos tendrías que reemplazar los `_____` con el código correspondiente. En otros casos el ejercicio te pedirá escribir el código desde cero.
3. Cuando hayas terminado, *knitea* el archivo para generar un html y ver como queda el resultado.
:::
## Construyendo un paquete de R paso a paso {#ex-exploracion}
::: transversal
Ahora que ya tenés las herramientas para manipular y transformar datos ordenados, es hora de familiarizarte más con los datos de las estaciones meteorológicas.
Te proponemos responder a las siguientes preguntas manipulando los datos con los verbos de dplyr que vimos hasta ahora.
Pero seguramente necesites nuevas funciones, te iremos dando pistas en el camino.
Pero antes de eso, sería útil tener todos los datos de las estaciones en un solo data.frame.
Seguramente los leiste por separado y cada uno tiene un nombre distinto.
La función `rbind()` permite unir data.frames uno debajo del otro.
Usá el siguiente código y completalo para generar un data.frame con los datos de todas las estaciones.
```r
nuevo_nombre <- rbind(___, ____, ____)
```
*Preguntas*
1. ¿Cuántas observaciones de temperatura se hicieron en cada estación?
2. ¿Cuál es la temperatura mínima registrada y la máxima registrada en cada estación?
3. ¿Cuál es el promedio de la temperatura de abrigo a 150 cm en cada estación? ¿Y el desvío estandar? Pista: la mayoría de las funciones tienen un argumento para *sacar* los `NA` del cálculo, revisá la documentación de `mean()` y de `sd()`.
4. ¿Cuál es la proporción de `NA` en temperatura de abrigo a 150 cm? Pista: podés calcular la proporción como cantidad de NA dividido la cantidad total de observaciones.
La función `is.na()` devuelve `TRUE` si el valor es un `NA`, al mismo tiempo `TRUE` es igual a `1` (así es como R lo interpreta) por lo que `sum(is.na(variable))` te va a dar la cantidad de `NA` en esa variable.
5. ¿Cuál es el promedio anual de la temperatura de abrigo a 150 cm en cada estación? Pista, podés extraer el año de la variable `fecha` con anio = `year(fecha)` y usar esa nueva variable para agrupar los datos.
6. ¿Cuál es la precipitación acumulada mensual en cada estación? Pista: la precipitación acumulada mensual es la *suma* de la precipitación en cada mes.
En este caso necesitas agrupar los datos por año y mes, una posibilidad es usar `floor_date(fecha, "month")`.
```{r message=FALSE, warning=FALSE, echo=FALSE, eval=FALSE}
estaciones_santafe <- c("NH0472", "NH0910", "NH0046", "NH0098", "NH0437")
santa_fe <- purrr::map_df(here::here(paste0("datos/", estaciones_santafe, ".csv")), readr::read_csv)
# 1. ¿Cuántas observaciones de temperatura se hicieron en cada estación?
santa_fe |>
group_by(id) |>
count() # o summarise(n = n())
# 2. ¿Cuál es la temperatura mínima registrada y la máxima registrada en cada estación?
santa_fe |>
group_by(id) |>
summarise(t_min = min(temperatura_abrigo_150cm_minima, na.rm = TRUE),
t_max = max(temperatura_abrigo_150cm_maxima, na.rm = TRUE))
# 3. ¿Cuál es el promedio de la temperatura de abrigo a 150 cm en cada estación? ¿Y el desvío estandar?
santa_fe |>
group_by(id) |>
summarise(t_media = mean(temperatura_abrigo_150cm, na.rm = TRUE),
t_ds = sd(temperatura_abrigo_150cm, na.rm = TRUE))
# 4. ¿Cuál es la proporción de `NA` en temperatura de abrigo a 150 cm?
santa_fe |>
group_by(id) |>
summarise(nas = sum(is.na(temperatura_abrigo_150cm)),
n_total = n(),
prop_na = nas/n_total)
# 5. ¿Cuál es el promedio anual de la temperatura de abrigo a 150 cm en cada estación?
santa_fe |>
group_by(id, anio = lubridate::year(fecha)) |>
summarise(t_anual = mean(temperatura_abrigo_150cm, na.rm = TRUE))
# 6. ¿Cuál es la precipitación acumulada mensual en cada estación?
santa_fe |>
group_by(id, anio_mes = lubridate::floor_date(fecha, "month")) |>
summarise(pp_mensual = sum(precipitacion_pluviometrica, na.rm = TRUE))
```
:::