-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy path06_manipulacion-2.qmd
304 lines (188 loc) · 16.7 KB
/
06_manipulacion-2.qmd
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
---
title: "Manipulación de datos ordenados II"
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```
Venimos hablando y trabajando con datos ordenados, o en formato largo, donde:
* cada fila es una observación
* cada columna es una variable
Podríamos organizar la informaciuón es un formato "ancho" donde:
* cada fila es un "item"
* cada columna es una variable
![](images/largo-ancho.png)
Una tabla en formato largo va a tener una cierta cantidad de columnas que cumplen el rol de *identificadores * y cuya combinación identifican una única observación y una única columna con el valor de la observación. En el ejemplo de arriba, `pais` y `anio` son las columnas identificadoras y `casos` es la columna que contiene el valor de las observaciones.
En una tabla ancha, cada observación única se identifica a partir de la intersección de filas y columnas. En el ejemplo, los países están en las filas y los años en las columnas.
En general, el formato ancho es más compacto y legible por humanos mientras que el largo es más fácil de manejar con la computadora. Si te fijás en las tablas de arriba, es más fácil comparar los valores entre países y entre años en la tabla ancha. Pero el nombre de las columnas ("1999", "2000") en realidad ¡son datos! Además este formato se empieza a complicar en cuanto hay más de dos identificadores.
Un mismo set de datos puede ser representado de forma completamente "larga", completamente "ancha" o --lo que es más común-- en un formato intermedio pero no existe una forma "correcta" de organizar los datos; cada una tiene sus ventajas y desventajas. Por esto es que es muy normal que durante un análisis los datos vayan y vuelvan entre distintos formatos dependiendo de los métodos estadísticos que se le aplican. Entonces, aprender a transformar datos anchos en largos y viceversa es un habilidad muy útil.
::: ejercicio
En las tablas de ejemplo cada país tiene el un valor observado de "casos" para cada año. ¿Cómo agregarías una nueva variable con información sobre "precios"? Dibujá un esquema en papel y lápiz en formato ancho y uno en formato largo. ¿En qué formato es más "natural" esa extensión?
:::
En esta sección vamos a usar el paquete `tidyr` para manipular datos. Si no lo tenés instalado, instalalo con el comando:
```{r eval = FALSE}
install.packages("tidyr")
```
(como siempre, recordá que esto hay que hacerlo una única vez en la consola)
Y luego cargá tidyr y dplyr con:
```{r}
library(tidyr)
library(dplyr)
```
## De ancho a largo con `pivot_longer()`
Antes, usamos el set de datos de paises que viene en el paquete datos. Vamos a usar los datos originales que tienen un formato ancho.
```{r}
paises_ancho <- readr::read_csv("https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/paises_ancho.csv")
paises_ancho
```
::: importante
¿Notaste que en el código anterior no usaste `library(readr)` para cargar el paquete y luego leer? Con la notación `paquete::funcion()` podés acceder a las funciones de un paquete sin tener que cargarlo. Es una buena forma de no tener que cargar un montón de paquetes innecesarios si vas a correr una única función de un paquete pocas veces.
:::
Esta tabla, increíblemente ancha, es muy difícil de manejar. Por ejemplo, es imposible hacer una serie de tiempo de una variable, o calcular el promedio por variable y país; ni hablar de calcular una regresión lineal.
Para convertirlo en una tabla más larga, se usa `pivot_longer()` ("longer" es "más largo" en inglés):
```{r}
paises_largo <- pivot_longer(paises_ancho,
cols = c(starts_with('pob'),
starts_with('esperanza'),
starts_with('pib_per')),
names_to = "variable_anio",
values_to = "valor"
)
paises_largo
```
El primer argumento de`pivot_longer()` es la tabla que va a modificar: `paises_ancho`. El segundo argumento se llama `cols` y es un vector con las columnas que tienen los valores a "alargar". Podría ser un vector escrito a mano (algo como `c("pib_per_capita-1952", "pib_per_capita-1957"...)`) pero con más de 30 columnas, escribir todo eso sería tedioso y probablemente estaría lleno de errores. Por eso tidyr provee funciones de ayuda para seleccionar columnas en base a patrones. El código de arriba usa `starts_with()` que, como su nombre en inglés lo indica, selecciona las columnas que *empiezan con* una determinada cadena de caracteres. El vector `c(starts_with('pob'), starts_with('esperanza'), starts_with('pib_per'))` le dice a `pivot_longer()` que seleccione las columnas que empieza con "pob", las que empiezan con "esperanza" y las que empiezan con "pib_per".
::: importante
Estas funciones accesorias para seleccionar muchas funciones se llaman "tidyselect". Si querés leer más detalles de las distintas formas que podés seleccionar variables leé la documentación usando `?tidyselect::language`.
:::
El tercer y cuarto argumento son los nombres de las columnas de "nombre" y de "valor" que va a tener la nueva tabla. Como la nueva columna de identificación tiene los datos de la variable y el año a medir, "variable_anio" es un buen nombre. Y la columna de valor va a tener... bueno, el valor.
Tomate un momento para visualizar lo que acaba de pasar. La tabla ancha tenía un montón de columnas con distintos datos. Ahora estos datos están uno arriba de otro en la columna "valor", pero para identificar el nombre de la columna de la cual vinieron, se agrega la columna "variable_anio".
![Proceso de largo a ancho](images/ancho-a-largo.png)
La columna `variable_anio` todavía no es muy útil porque contiene 2 datos, la variable (población, expectativa de vida o PBI per cápita) y el año. Sería mejor separar esta información en dos columnas llamadas "variable" y "anio". Para eso está la función `separate()`.
```{r}
paises_largo <- separate_wider_delim(paises_largo,
col = variable_anio,
delim = "-",
names = c("variable", "anio"))
paises_largo
```
El primer argumento, como siempre, es la tabla a procesar. El segundo, `col`, es la columna a separar en dos (o más) columnas nuevas. El segundo argumento indica cómo se separan los elementos, en este caso con guíon "-". El tercero, `names` es el nombre de las nuevas columnas que `separate_wider_delim()` va a crear.
Y ya casi. Pero fijate que debajo de la columna `anio` dice `<chr>`; eso significa que el tipo de la columna es caracter, pero los años son números. Usando `mutate()` podemos convertir la columna `anio` a entero usando `as.integer()`:
```{r}
paises_largo <- mutate(paises_largo,
anio = as.integer(anio))
paises_largo
```
::: ejercicio
Juntá todos los pasos anteriores en una sola cadena de operaciones usando `|>`.
:::
## De largo a ancho con `pivot_wider()`
Ahora la variable `paises_largo` está en el formato más largo posible. Tiene 5 columnas, de las cuales sólo una es la columnas con valores. Pero con los datos así no podrías hacer un gráfico de puntos que muestre la relación entre el PBI per cápita y la expectativa de vida como en la [sección de gráficos](06_graficos.html). Fijate que los valores de la columna `valor` no tienen todos las mismas unidades, por lo que operar con ese vector podría dar resultados sin sentido. Muchas veces es conveniente y natural tener los datos en un formato intermedio en donde hay múltiples columnas con los valores de distintas variables observadas.
Pasa "ensanchar" una tabla está la función `pivot_wider()` ("wider" es "más ancha" en inglés) y el código para conseguir este formato intermedio es:
```{r}
paises_medio <- pivot_wider(paises_largo, names_from = variable, values_from = valor)
paises_medio
```
Nuevamente el primer argumento es la tabla original. El segundo, `names_from` es la columna cuyos valores únicos van a convertirse en nuevas columnas. La columna `variable` tiene los valores `"población"`, `"esperanza_de_vida"` y `"pib_per_capita"` y entonces la tabla nueva tendrá tres columnas con esos nombres. El tercer argumento, `values_from`, es la columna de la cual sacar los valores.
Para volver al formato más ancho, basta con agregar más columnas en el argumento `names_from`:
```{r}
pivot_wider(paises_largo,
names_from = c(variable, anio),
names_sep = "-",
values_from = valor)
```
En esta llamada también está el argumento `names_sep`, que determina el caracter que se usa para crear el nombre de las nuevas columnas.
::: ejercicio
* Creá una nueva tabla, llamada `paises_superduper_ancho` que tenga una columna para cada variable, anio y país. (Consejo: la tabla final tiene que tener 5 filas).
* ¿Cómo es la tabla más ancha posible que podés generar con estos datos? ¿Cuántas filas y columnas tiene?
:::
## Uniendo tablas
Hasta ahora todo lo que usaste de dplyr involucra trabajar y modificar con una sola tabla a la vez, pero es muy común tener dos o más tablas con datos relacionados. En ese caso, tenemos que *unir* estas tablas. a partir de una o más variables en común o *keys*. En el mundo de dplyr hay que usar la familia de funciones `*_join()`. Hay una función cada tipo de unión que queramos hacer.
Asumiendo que querés unir dos data.frames o tablas `x` e `y` que tienen en común una variable `A`:
![](images/join.png)
* `full_join()`: devuelve todas las filas y todas las columnas de ambas tablas `x` e `y`. Cuando no coinciden los elementos en `x` o `y`, devuelve `NA` (dato faltante). Esto significa que no se pierden filas de ninguna de las dos tablas aún cuando no hay coincidencia. Está es la manera más segura de unir tablas, para no perder datos.
* `left_join()`: devuelve todas las filas de `x` y todas las columnas de `x` e `y`. Las filas en `x` que no tengan coincidencia con `y` tendrán `NA` en las nuevas columnas. Si hay múltiples coincidencias entre `x`e `y`, devuelve todas las coincidencias posibles.
* `right_join()`: es igual que `left_join()` pero intercambiando el orden de `x` e `y`. En otras palabras, `right_join(x, y)` es idéntico a `left_join(y, x)`.
* `inner_join()`: devuelve todas las filas de `x` donde hay coincidencias con `y` y todas las columnas de `x` e `y`. Si hay múltiples coincidencias entre `x` e `y`, entonces devuelve todas las coincidencias. Esto significa que eliminará las filas (observaciones) que no coincidan en ambas tablas, lo que puede ser peligroso.
* `anti_join()`: devuelve todas las filas de `x` que no tienen coincidencias con `y`. Identifica observaciones no coincidentes entre dos conjunto de datos. Es muy util para encontrar datos faltantes o para eliminar datos duplicados.
![](images/join_family.png)
Ahora vamos a seguir trabajando con las base de datos de `paises` pero nos vamos a quedar solo con las observaciones del 2007 y de paso unirlo a una nueva base de datos `co2` que contiene información de la emisión de dióxido de carbono de cada país para ese mismo año.
```{r, message=FALSE}
paises_2007 <- datos::paises %>%
filter(anio == 2007)
co2_2007 <- readr::read_csv("https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/co2_2007.csv")
co2_2007
```
Esta nueva tabla tiene 3 columnas: `codigo_iso` tiene el código ISO de 3 letras de (abreviaturas que se usan internacionalmente), `emision_co2` tiene las emisiones anuales per cápita de CO2 en toneladas, `pais` tiene el nombre del país. Esta última columna también está presente en la tabla `paises_2007` y es la que va a servir como variable llave para unir las dos tablas.
Para unir las dos tablas, cualquier función *join* requiere cierta información:
* las tablas a unir: son los dos primeros argumentos.
* qué variable o variables (se puede usar más de una!) usar para identificar coincidencias: el argumento `by`.
Unamos `paises_2007` y `co2_2007` primero con `full_join()`:
```{r}
paises_co2_2007 <- full_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
```
Si miramos de cerca la tabla unida veremos un par de cosas:
* Todas las columnas de `paises_2007` y de `co2_2007` están presentes.
* Todas las observaciones están presentes, aún los países que están presentes en `co2_2007` pero no en `paises_2007` y viceversa. En esos casos ahora tenemos `NA`. Esto genera una tabla con 241 filas.
Esta es la opción más segura si no sabemos si todas las observaciones de una tabla están presente en a otra.
Si solo nos interesa conservar las filas de la tabla *de la izquierda*, en este caso `paises_2007` entonces:
```{r}
paises_co2_2007 <- left_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
```
Ahora esperamos que la tabla resultante tenga la misma cantidad de filas que `paises_2007` y efectivamente eso ocurre. Pero al mismo tiempo varios países en esa tabla no encontraron coincidencia en `co2_2007` y por esa razón, la columna nueva columna `emisiones_co2` tiene `NA`.
Si quisiéramos quedarnos *solo* con las observaciones que están presentes en ambas tablas usamos `inner_join()`.
```{r}
paises_co2_2007 <- inner_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
```
En este caso, perdemos las filas de `co2_2007` que no encontraron coincidencia en `paises_2007` y viceversa y la tabla resultante tiene aún menos filas (119).
Finalmente, si quisieramos quedarnos con los paises para los cuales no tenemos datos de co2, o sea las observaciones en `paises_2007` que no tienen coincidencia en `co2_2007` usamos `anti_join()`.
```{r}
sin_co2_2007 <- anti_join(paises_2007, co2_2007)
sin_co2_2007
```
::: ejercicio
Estuvimos trabajando con una parte de la base de datos de emisiones. Pero también está disponible `co2_completo.csv` que contiene las emisiones para distintos años. El objetivo es que unas `paises` y `co2` teniendo en cuenta tanto el país como el año. Para eso:
1. Lee la base de datos `co2_completo.csv` en una nueva variable que se llame `co2`.
2. Revisá el nombre de las variables en esta base de datos, ¿se llaman igual que las variables en `paises`?
3. Uní las dos tablas usando `full_join()`, tené en cuenta que ahora usamos dos variables llave `pais` y `anio`. Buscá en la documentación cómo indicarle eso a la función `full_join()`.
Podés descargar los datos desde https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/co2_completo.csv
:::
### Construyendo un paquete de R paso a paso {#ex-manipulacion}
::: ejercicios
**Pivots!**
Recordás el ejercicio 3 de dplyr?
>¿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()`.
1. Usando las funciones `pivot_` alargá el resultado anterior de tal manera que queden los valores de temperatura máxima y mínima en la misma columna.
2. A continuación **usando el resultado anterior** volvé a ensanchar la tabla de tal manera que tengas columnas con los nombres de las estaciones y en las filas los valores de la media y el desvio estandar.
```{r, echo=FALSE, eval=FALSE}
library(dplyr)
library(tidyr)
estaciones_santafe <- c("NH0472", "NH0910", "NH0046", "NH0098", "NH0437")
santa_fe <- purrr::map_df(here::here(paste0("datos/", estaciones_santafe, ".csv")), readr::read_csv)
# 1.
santa_fe |>
group_by(id) |>
summarise(media = mean(temperatura_abrigo_150cm, na.rm = TRUE),
desvio = sd(temperatura_abrigo_150cm, na.rm = TRUE)) |>
pivot_longer(cols = c("media", "desvio"), names_to = "variable", values_to = "valor")
# 2.
santa_fe |>
group_by(id) |>
summarise(media = mean(temperatura_abrigo_150cm, na.rm = TRUE),
desvio = sd(temperatura_abrigo_150cm, na.rm = TRUE)) |>
pivot_longer(cols = c("media", "desvio"), names_to = "variable", values_to = "valor") |>
pivot_wider(names_from = id, values_from = valor)
```
**uniones**
A la hora de analizar datos meteorológicos, tener en cuenta la ubicación y altitud de las estaciones donde se toman las mediciones es muy importane. Sin embargo eso no está incluido en los datos con los que venimos trabajando.
Esta información son **metadatos** y suelen venir por separado (si tenemos suerte!). En este caso los tenemos y están disponibles en [https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/metadatos_completos.csv](https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/metadatos_completos.csv).
1. Lee los metadatos
2. Identifica cuales son las variables clave, aquellas que son comunes entre los datos meteorológicos con los que venís trabajando y esta nueva tabla.
3. Uní los datos con joins. ¿Cuál es la mejor opción?
```{r, echo=FALSE, eval=FALSE}
metadatos <- readr::read_csv("https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/metadatos_completos.csv")
santa_fe |>
left_join(metadatos, by = "id")
```
:::