install.packages("tidyr")
7 Manipulación de datos ordenados II
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
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.
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:
(como siempre, recordá que esto hay que hacerlo una única vez en la consola)
Y luego cargá tidyr y dplyr con:
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
7.1 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.
paises_ancho <- readr::read_csv("https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/paises_ancho.csv")
Rows: 142 Columns: 38
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): pais, continente
dbl (36): esperanza_de_vida-1952, esperanza_de_vida-1957, esperanza_de_vida-...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
paises_ancho
# A tibble: 142 × 38
pais continente `esperanza_de_vida-1952` `esperanza_de_vida-1957`
<chr> <chr> <dbl> <dbl>
1 Afganistán Asia 28.8 30.3
2 Albania Europa 55.2 59.3
3 Argelia África 43.1 45.7
4 Angola África 30.0 32.0
5 Argentina Américas 62.5 64.4
6 Australia Oceanía 69.1 70.3
7 Austria Europa 66.8 67.5
8 Baréin Asia 50.9 53.8
9 Bangladesh Asia 37.5 39.3
10 Bélgica Europa 68 69.2
# ℹ 132 more rows
# ℹ 34 more variables: `esperanza_de_vida-1962` <dbl>,
# `esperanza_de_vida-1967` <dbl>, `esperanza_de_vida-1972` <dbl>,
# `esperanza_de_vida-1977` <dbl>, `esperanza_de_vida-1982` <dbl>,
# `esperanza_de_vida-1987` <dbl>, `esperanza_de_vida-1992` <dbl>,
# `esperanza_de_vida-1997` <dbl>, `esperanza_de_vida-2002` <dbl>,
# `esperanza_de_vida-2007` <dbl>, `poblacion-1952` <dbl>, …
¿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):
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
# A tibble: 5,112 × 4
pais continente variable_anio valor
<chr> <chr> <chr> <dbl>
1 Afganistán Asia poblacion-1952 8425333
2 Afganistán Asia poblacion-1957 9240934
3 Afganistán Asia poblacion-1962 10267083
4 Afganistán Asia poblacion-1967 11537966
5 Afganistán Asia poblacion-1972 13079460
6 Afganistán Asia poblacion-1977 14880372
7 Afganistán Asia poblacion-1982 12881816
8 Afganistán Asia poblacion-1987 13867957
9 Afganistán Asia poblacion-1992 16317921
10 Afganistán Asia poblacion-1997 22227415
# ℹ 5,102 more rows
El primer argumento depivot_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”.
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”.
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()
.
paises_largo <- separate_wider_delim(paises_largo,
col = variable_anio,
delim = "-",
names = c("variable", "anio"))
paises_largo
# A tibble: 5,112 × 5
pais continente variable anio valor
<chr> <chr> <chr> <chr> <dbl>
1 Afganistán Asia poblacion 1952 8425333
2 Afganistán Asia poblacion 1957 9240934
3 Afganistán Asia poblacion 1962 10267083
4 Afganistán Asia poblacion 1967 11537966
5 Afganistán Asia poblacion 1972 13079460
6 Afganistán Asia poblacion 1977 14880372
7 Afganistán Asia poblacion 1982 12881816
8 Afganistán Asia poblacion 1987 13867957
9 Afganistán Asia poblacion 1992 16317921
10 Afganistán Asia poblacion 1997 22227415
# ℹ 5,102 more rows
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()
:
paises_largo <- mutate(paises_largo,
anio = as.integer(anio))
paises_largo
# A tibble: 5,112 × 5
pais continente variable anio valor
<chr> <chr> <chr> <int> <dbl>
1 Afganistán Asia poblacion 1952 8425333
2 Afganistán Asia poblacion 1957 9240934
3 Afganistán Asia poblacion 1962 10267083
4 Afganistán Asia poblacion 1967 11537966
5 Afganistán Asia poblacion 1972 13079460
6 Afganistán Asia poblacion 1977 14880372
7 Afganistán Asia poblacion 1982 12881816
8 Afganistán Asia poblacion 1987 13867957
9 Afganistán Asia poblacion 1992 16317921
10 Afganistán Asia poblacion 1997 22227415
# ℹ 5,102 more rows
Juntá todos los pasos anteriores en una sola cadena de operaciones usando |>
.
7.2 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. 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:
paises_medio <- pivot_wider(paises_largo, names_from = variable, values_from = valor)
paises_medio
# A tibble: 1,704 × 6
pais continente anio poblacion esperanza_de_vida pib_per_capita
<chr> <chr> <int> <dbl> <dbl> <dbl>
1 Afganistán Asia 1952 8425333 28.8 779.
2 Afganistán Asia 1957 9240934 30.3 821.
3 Afganistán Asia 1962 10267083 32.0 853.
4 Afganistán Asia 1967 11537966 34.0 836.
5 Afganistán Asia 1972 13079460 36.1 740.
6 Afganistán Asia 1977 14880372 38.4 786.
7 Afganistán Asia 1982 12881816 39.9 978.
8 Afganistán Asia 1987 13867957 40.8 852.
9 Afganistán Asia 1992 16317921 41.7 649.
10 Afganistán Asia 1997 22227415 41.8 635.
# ℹ 1,694 more rows
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
:
pivot_wider(paises_largo,
names_from = c(variable, anio),
names_sep = "-",
values_from = valor)
# A tibble: 142 × 38
pais continente `poblacion-1952` `poblacion-1957` `poblacion-1962`
<chr> <chr> <dbl> <dbl> <dbl>
1 Afganistán Asia 8425333 9240934 10267083
2 Albania Europa 1282697 1476505 1728137
3 Argelia África 9279525 10270856 11000948
4 Angola África 4232095 4561361 4826015
5 Argentina Américas 17876956 19610538 21283783
6 Australia Oceanía 8691212 9712569 10794968
7 Austria Europa 6927772 6965860 7129864
8 Baréin Asia 120447 138655 171863
9 Bangladesh Asia 46886859 51365468 56839289
10 Bélgica Europa 8730405 8989111 9218400
# ℹ 132 more rows
# ℹ 33 more variables: `poblacion-1967` <dbl>, `poblacion-1972` <dbl>,
# `poblacion-1977` <dbl>, `poblacion-1982` <dbl>, `poblacion-1987` <dbl>,
# `poblacion-1992` <dbl>, `poblacion-1997` <dbl>, `poblacion-2002` <dbl>,
# `poblacion-2007` <dbl>, `esperanza_de_vida-1952` <dbl>,
# `esperanza_de_vida-1957` <dbl>, `esperanza_de_vida-1962` <dbl>,
# `esperanza_de_vida-1967` <dbl>, `esperanza_de_vida-1972` <dbl>, …
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.
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?
7.3 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
:
full_join()
: devuelve todas las filas y todas las columnas de ambas tablasx
ey
. Cuando no coinciden los elementos enx1
, devuelveNA
(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.left_join()
: devuelve todas las filas dex
y todas las columnas dex
ey
. Las filas enx
que no tengan coincidencia cony
tendránNA
en las nuevas columnas. Si hay múltiples coincidencias entrex
ey
, devuelve todas las coincidencias posibles.right_join()
: es igual queleft_join()
pero intercambiando el orden dex
ey
. En otras palabras,right_join(x, y)
es idéntico aleft_join(y, x)
.inner_join()
: devuelve todas las filas dex
donde hay coincidencias cony
y todas las columnas dex
ey
. Si hay múltiples coincidencias entrex
ey
, entonces devuelve todas las coincidencias. Esto significa que eliminará las filas (observaciones) que no coincidan en ambas tablas, lo que puede ser peligroso.
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.
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
# A tibble: 218 × 3
codigo_iso emision_co2 pais
<chr> <dbl> <chr>
1 ABW 27.9 Aruba
2 AFG 0.0854 Afghanistán
3 AGO 1.20 Angola
4 ALB 1.32 Albania
5 AND 6.52 Andorra
6 ARB 4.10 Mundo Árabe
7 ARE 22.4 Emiratos Árabes Unidos
8 ARG 4.38 Argentina
9 ARM 1.73 Armenia
10 ATG 5.14 Antigua y Barbuda
# ℹ 208 more rows
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()
:
paises_co2_2007 <- full_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
# A tibble: 241 × 8
pais continente anio esperanza_de_vida poblacion pib_per_capita codigo_iso
<chr> <fct> <int> <dbl> <int> <dbl> <chr>
1 Afgan… Asia 2007 43.8 31889923 975. <NA>
2 Alban… Europa 2007 76.4 3600523 5937. ALB
3 Argel… África 2007 72.3 33333216 6223. <NA>
4 Angola África 2007 42.7 12420476 4797. AGO
5 Argen… Américas 2007 75.3 40301927 12779. ARG
6 Austr… Oceanía 2007 81.2 20434176 34435. AUS
7 Austr… Europa 2007 79.8 8199783 36126. AUT
8 Baréin Asia 2007 75.6 708573 29796. <NA>
9 Bangl… Asia 2007 64.1 150448339 1391. BGD
10 Bélgi… Europa 2007 79.4 10392226 33693. BEL
# ℹ 231 more rows
# ℹ 1 more variable: emision_co2 <dbl>
Si miramos de cerca la tabla unida veremos un par de cosas:
- Todas las columnas de
paises_2007
y deco2_2007
están presentes. - Todas las observaciones están presentes, aún los países que están presentes en
co2_2007
pero no enpaises_2007
y viceversa. En esos casos ahora tenemosNA
. 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:
paises_co2_2007 <- left_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
# A tibble: 142 × 8
pais continente anio esperanza_de_vida poblacion pib_per_capita codigo_iso
<chr> <fct> <int> <dbl> <int> <dbl> <chr>
1 Afgan… Asia 2007 43.8 31889923 975. <NA>
2 Alban… Europa 2007 76.4 3600523 5937. ALB
3 Argel… África 2007 72.3 33333216 6223. <NA>
4 Angola África 2007 42.7 12420476 4797. AGO
5 Argen… Américas 2007 75.3 40301927 12779. ARG
6 Austr… Oceanía 2007 81.2 20434176 34435. AUS
7 Austr… Europa 2007 79.8 8199783 36126. AUT
8 Baréin Asia 2007 75.6 708573 29796. <NA>
9 Bangl… Asia 2007 64.1 150448339 1391. BGD
10 Bélgi… Europa 2007 79.4 10392226 33693. BEL
# ℹ 132 more rows
# ℹ 1 more variable: emision_co2 <dbl>
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
.
Finalmente, si quisiéramos quedarnos solo con las observaciones que están presentes en ambas tablas usamos inner_join()
.
paises_co2_2007 <- inner_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
# A tibble: 119 × 8
pais continente anio esperanza_de_vida poblacion pib_per_capita codigo_iso
<chr> <fct> <int> <dbl> <int> <dbl> <chr>
1 Alban… Europa 2007 76.4 3600523 5937. ALB
2 Angola África 2007 42.7 12420476 4797. AGO
3 Argen… Américas 2007 75.3 40301927 12779. ARG
4 Austr… Oceanía 2007 81.2 20434176 34435. AUS
5 Austr… Europa 2007 79.8 8199783 36126. AUT
6 Bangl… Asia 2007 64.1 150448339 1391. BGD
7 Bélgi… Europa 2007 79.4 10392226 33693. BEL
8 Boliv… Américas 2007 65.6 9119152 3822. BOL
9 Bosni… Europa 2007 74.9 4552198 7446. BIH
10 Botsw… África 2007 50.7 1639131 12570. BWA
# ℹ 109 more rows
# ℹ 1 more variable: emision_co2 <dbl>
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).
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:
- Lee la base de datos
co2_completo.csv
en una nueva variable que se llameco2
. - Revisá el nombre de las variables en esta base de datos, ¿se llaman igual que las variables en
paises
? - Uní las dos tablas usando
full_join()
, tené en cuenta que ahora usamos dos variables llavepais
yanio
. Buscá en la documentación cómo indicarle eso a la funciónfull_join()
.
Podés descargar los datos desde https://raw.githubusercontent.com/rse-r/intro-programacion/main/datos/co2_completo.csv