7  Manipulación de datos ordenados II

Venimos hablando y trabajando con datos ordenados, o en formato largo, donde:

Podríamos organizar la informaciuón es un formato “ancho” donde:

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”.

Proceso de largo a ancho

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 tablas x e y. Cuando no coinciden los elementos en x1, 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.

  • 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 xe 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.

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 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:

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:

  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