Joins e importación: como combinar, exportar e importar en R

Cuadernos prácticos de Software y Gestión de Bases de Datos del Máster de Bioestadística (curso 2024-2025)

Author

Javier Álvarez Liébana

1 Relacionando datos: joins

Al trabajar con datos no siempre tendremos la información en una sola tabla y a veces nos interesará cruzar la información de distintas fuentes.

Para ello usaremos un clásico de todo lenguaje que maneja datos: los famosos join, que nos permitirán cruzar una o varias tablas, haciendo uso de una columna identificadora de cada una de ellas (por ejemplo, imagina que cruzamos datos de hacienda y de antecedentes penales, haciendo join por la columna DNI).

 

La estructura básica es la siguiente:

tabla_1 |>
  xxx_join(tabla_2, by = id)

Vamos a probar los distintos joins con un ejemplo sencillo

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
tb_1 <- tibble("key" = 1:3, "val_x" = c("x1", "x2", "x3"))
tb_2 <- tibble("key" = c(1, 2, 4), "val_y" = c("y1", "y2", "y3"))
tb_1
# A tibble: 3 × 2
    key val_x
  <int> <chr>
1     1 x1   
2     2 x2   
3     3 x3   
tb_2
# A tibble: 3 × 2
    key val_y
  <dbl> <chr>
1     1 y1   
2     2 y2   
3     4 y3   

1.1 Left join

  • left_join(): mantiene todos los registros de la primera tabla, y busca cuales tienen id también en la segunda (en caso de no tenerlo se rellena con NA los campos de la 2ª tabla).

En nuestra caso queremos incorporar a tb_1 la información de tb_2, identificando los registros por la columna key (by = "key", la columna por la que tiene que cruzar)

tb_1  |> 
  left_join(tb_2, by = "key")
# A tibble: 3 × 3
    key val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     3 x3    <NA> 

tb_1 |> 
  left_join(tb_2, by = "key")
# A tibble: 3 × 3
    key val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     3 x3    <NA> 

Fíjate que los registros de la primera cuya key no ha encontrado en la segunda les ha dado el valor de ausente.

1.2 Right join

  • right_join(): mantiene todos los registros de la segunda tabla, y busca cuales tienen id también en la primera.

Vamos ahora a incorporar a tb_2 la información de tb_1, identificando los registros por la columna key (by = "key")

tb_1 |> 
  right_join(tb_2, by = "key")
# A tibble: 3 × 3
    key val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     4 <NA>  y3   

tb_1 |> 
  right_join(tb_2, by = "key")
# A tibble: 3 × 3
    key val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     4 <NA>  y3   

Fíjate que ahora los registros de la segunda cuya key no ha encontrado en la primera son los que les ha dado el valor de ausente.

1.3 Claves y sufijos

Las columnas clave que usaremos para el cruce no siempre se llamarán igual.

tb_1 <- tibble("key_1" = 1:3, "val_x" = c("x1", "x2", "x3"))
tb_2 <- tibble("key_2" = c(1, 2, 4), "val_y" = c("y1", "y2", "y3"))
  • by = c("key_2" = "key_2"): le indicaremos en qué columna de cada tabla están las claves por las que vamos a cruzar.
# Left
tb_1 |> 
  left_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 3 × 3
  key_1 val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     3 x3    <NA> 
# Right
tb_1  |> 
  right_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 3 × 3
  key_1 val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     4 <NA>  y3   

Además podemos cruzar por varias columnas a la vez (interpretará como igual registro aquel que tenga el conjunto de claves igual), con by = c("var1_t1" = "var1_t2", "var2_t1" = "var2_t2", ...). Modifiquemos el ejemplo anterior

tb_1 <- tibble("k_11" = 1:3, "k_12" = c("a", "b", "c"),  "val_x" = c("x1", "x2", "x3"))
tb_2 <- tibble("k_21" = c(1, 2, 4), "k_22" = c("a", "b", "e"), "val_y" = c("y1", "y2", "y3"))
# Left
tb_1 |> 
  left_join(tb_2,
            by = c("k_11" = "k_21", "k_12" = "k_22"))
# A tibble: 3 × 4
   k_11 k_12  val_x val_y
  <dbl> <chr> <chr> <chr>
1     1 a     x1    y1   
2     2 b     x2    y2   
3     3 c     x3    <NA> 

También podría suceder que al cruzar dos tablas, haya columnas de valores que se llamen igual

tb_1 <- tibble("key_1" = 1:3, "val" = c("x1", "x2", "x3"))
tb_2 <- tibble("key_2" = c(1, 2, 4), "val" = c("y1", "y2", "y3"))
# Left
tb_1 |> 
  left_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 3 × 3
  key_1 val.x val.y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     3 x3    <NA> 

Fíjate que por defecto nos añade los sufijos .x y .y para indicarnos de que tabla vienen. Dicho sufijo podemos especificárselo en el argumento opcional suffix = ..., que nos permita distinguir las variables de una tabla y de otra.

# Left
tb_1 |>
  left_join(tb_2, by = c("key_1" = "key_2"), suffix = c("_tabla1", "_tabla2"))
# A tibble: 3 × 3
  key_1 val_tabla1 val_tabla2
  <dbl> <chr>      <chr>     
1     1 x1         y1        
2     2 x2         y2        
3     3 x3         <NA>      

1.4 Full join

  • full_join(): mantiene todos los registros de ambas tablas.

Los dos anteriores casos forman lo que se conoce como outer joins: cruces donde se mantienen observaciones que salgan en al menos una tabla. El tercer outer join es el conocido como full_join() que nos mantendrá las observaciones de ambas tablas, añadiendo las filas que no casen con la otra tabla.

tb_1 |> 
  full_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 4 × 3
  key_1 val.x val.y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     3 x3    <NA> 
4     4 <NA>  y3   

1.5 Inner join

  • inner_join(): solo sobreviven los registros cuyo id esté en ambas tablas.

Frente a los outer join está lo que se conoce como inner join, con inner_join(): un cruce en el que solo se mantienen las observaciones que salgan en ambas tablas, solo mantiene aquellos registros matcheados.

tb_1 |> 
  inner_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 2 × 3
  key_1 val.x val.y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   

Fíjate que en términos de registros, inner_join si es conmutativa, nos da igual el orden de las tablas: lo único que cambia es el orden de las columnas que añade.

tb_1 |> 
  inner_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 2 × 3
  key_1 val.x val.y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
tb_2 |> 
  inner_join(tb_1, by = c("key_2" = "key_1"))
# A tibble: 2 × 3
  key_2 val.x val.y
  <dbl> <chr> <chr>
1     1 y1    x1   
2     2 y2    x2   

1.6 Anti/semi join

Por último tenemos dos herramientas interesantes para filtrar (no cruzar) registros: semi_join() y anti_join(). El semi join nos deja en la primera tabla los registros que cuya clave está también en la segunda (como un inner join pero sin añadir la info de la segunda tabla). Y el segundo, los anti join, hace justo lo contrario (aquellos que no están).

# semijoin
tb_1 |> 
  semi_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 2 × 2
  key_1 val  
  <int> <chr>
1     1 x1   
2     2 x2   
# antijoin
tb_1 |> 
  anti_join(tb_2, by = c("key_1" = "key_2"))
# A tibble: 1 × 2
  key_1 val  
  <int> <chr>
1     3 x3   

1.7 💻 Tu turno

Intenta realizar los siguientes ejercicios sin mirar las soluciones

Para los ejercicios usaremos las tablas disponibles en el paquete {nycflights13} (echa un vistazo antes)

library(nycflights13)
  • airlines: nombre de aerolíneas (con su abreviatura).
  • airports: datos de aeropuertos (nombres, longitud, latitud, altitud, etc).
  • flights: datos de vuelos.
  • planes: datos de los aviones.
  • weather: datos meteorológicos horarios de las estaciones LGA, JFK y EWR.

📝 Del paquete {nycflights13} cruza la tabla flights con airlines. Queremos mantener todos los registros de vuelos, añadiendo la información de las aerolíneas a dicha tabla.

Code
flights_airlines <-
  flights |> 
  left_join(airlines, by = "carrier")
flights_airlines

📝 A la tabla obtenida del cruce del apartado anterior, cruza después con los datos de los aviones en planes, pero incluyendo solo aquellos vuelos de los que tengamos información de sus aviones (y viceversa).

Code
flights_airlines_planes <- 
  flights_airlines |> 
  inner_join(planes, by = "tailnum")
flights_airlines_planes

📝 Repite el ejercicio anterior pero conservando ambas variables year (en una es el año del vuelo, en la otra es el año de construcción del avión), y distinguiéndolas entre sí

Code
flights_airlines_planes <- 
  flights_airlines |> 
  inner_join(planes, by = "tailnum",
             suffix = c("_flight", "_build_aircraft"))
flights_airlines_planes

📝 Al cruce obtenido del ejercicio anterior incluye la longitud y latitud de los aeropuertos en airports, distinguiendo entre la latitud/longitud del aeropuerto en destino y en origen.

Code
flights_airlines_planes %>%
  left_join(airports %>% select(faa, lat, lon),
            by = c("origin" = "faa")) |> 
  rename(lat_origin = lat, lon_origin = lon) |> 
  left_join(airports %>% select(faa, lat, lon),
            by = c("dest" = "faa")) |> 
  rename(lat_dest = lat, lon_dest = lon)

📝 Filtra de airports solo aquellos aeropuertos de los que salgan vuelos. Repite el proceso filtrado solo aquellos a los que lleguen vuelos

Code
airports |> 
  semi_join(flights, by = c("faa" = "origin"))
airports |> 
  semi_join(flights, by = c("faa" = "dest"))

📝 ¿De cuántos vuelos no disponemos información del avión? Elimina antes los vuelos que no tengan identificar (diferente a NA) del avión

Code
flights |> 
  drop_na(tailnum) |>
  anti_join(planes, by = "tailnum") |>
  count(tailnum, sort = TRUE) # de mayor a menor ya de paso

2 🐣Caso práctico I: Beatles y Rolling Stones

Vamos a empezar a practicar joins sencillos con la tabla band_members y band_instruments ya incluidos en el paquete {dplyr}.

library(dplyr)
band_members
# A tibble: 3 × 2
  name  band   
  <chr> <chr>  
1 Mick  Stones 
2 John  Beatles
3 Paul  Beatles
band_instruments
# A tibble: 3 × 2
  name  plays 
  <chr> <chr> 
1 John  guitar
2 Paul  bass  
3 Keith guitar

En la primera tenemos una serie de artistas y la banda a la que pertenecen; en la segunda tenemos una serie de artistas y el instrumento que tocan. Además de realizar las acciones solicitadas intenta visualizar qué tabla final tendrías antes de ejecutar el código..

2.1 Pregunta 1

Dada la tabla band_members, incorpora la información de qué instrumento toca cada miembro (band_instruments) de los que tienes en esa tabla.

Code
left_join_band <-
  band_members |> 
  left_join(band_instruments, by = "name")

2.2 Pregunta 2

Dadas las tablas band_members y band_instruments, ¿qué tipo de join deberías hacer para tener una tabla completa, sin ausencias, donde todos los miembros de la banda tengan la información de su instrumento, y cada instrumento tenga un miembro asociado a él?

Code
inner_join_band <-
  band_members |>
  inner_join(band_instruments, by = "name")

2.3 Pregunta 3

Dada la tabla band_instruments, ¿cómo incorporar la información de quién toca cada instrumento (en caso de que la conozcamos)?

Code
right_join_band <-
  band_members |>
  right_join(band_instruments, by = "name")

# other option
left_join_instruments <-
  band_instruments |> 
  left_join(band_members, by = "name")

2.4 Pregunta 4

Dadas las tablas band_members y band_instruments, ¿qué tipo de join deberías hacer para tener una tabla con toda la información, tanto de los miembros como de los instrumentos, aunque haya miembros cuyo instrumento no conozcas, e instrumentos cuyo portador no conozcas?

Code
full_join_band <-
  band_members |>
  full_join(band_instruments, by = "name")

3 🐣 Caso práctico II: renta municipios

En el archivo municipios.csv tenemos guardada la información de los municipios de España a fecha de 2019.

# datos 2019
mun_data <- read_csv(file = "./datos/municipios.csv")
Rows: 8212 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (8): codauto, ine.ccaa.name, cpro, ine.prov.name, cmun, name, LAU_CODE, ...

ℹ 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.
mun_data
# A tibble: 8,212 × 8
   codauto ine.ccaa.name cpro  ine.prov.name cmun  name      LAU_CODE codigo_ine
   <chr>   <chr>         <chr> <chr>         <chr> <chr>     <chr>    <chr>     
 1 01      Andalucía     04    Almería       001   Abla      04001    04001     
 2 01      Andalucía     04    Almería       002   Abrucena  04002    04002     
 3 01      Andalucía     04    Almería       003   Adra      04003    04003     
 4 01      Andalucía     04    Almería       004   Albanchez 04004    04004     
 5 01      Andalucía     04    Almería       005   Alboloduy 04005    04005     
 6 01      Andalucía     04    Almería       006   Albox     04006    04006     
 7 01      Andalucía     04    Almería       007   Alcolea   04007    04007     
 8 01      Andalucía     04    Almería       008   Alcóntar  04008    04008     
 9 01      Andalucía     04    Almería       009   Alcudia … 04009    04009     
10 01      Andalucía     04    Almería       010   Alhabia   04010    04010     
# ℹ 8,202 more rows

Por otro lado el archivo renta_mun contiene datos de la renta per capita edmai de cada unidad administrativa (municipios, distritos, provincias, comunidades autonónomas, etc) para diferentes años.

renta_mun <- read_csv(file = "./datos/renta_mun.csv")
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)
Rows: 55273 Columns: 7
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Unidad, codigo_ine
dbl (5): 2019, 2018, 2017, 2016, 2015

ℹ 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.
renta_mun
# A tibble: 55,273 × 7
   Unidad                          `2019` `2018` `2017` `2016` `2015` codigo_ine
   <chr>                            <dbl>  <dbl>  <dbl>  <dbl>  <dbl> <chr>     
 1 44001 Ababuj                        NA     NA     NA     NA     NA 44001     
 2 4400101 Ababuj distrito 01          NA     NA     NA     NA     NA 4400101   
 3 4400101001 Ababuj sección 01001     NA     NA     NA     NA     NA 4400101001
 4 40001 Abades                     11429  10731  10314   9816   9904 40001     
 5 4000101 Abades distrito 01       11429  10731  10314   9816   9904 4000101   
 6 4000101001 Abades sección 01001  11429  10731  10314   9816   9904 4000101001
 7 10001 Abadía                      8954   8589   8207   7671   8416 10001     
 8 1000101 Abadía distrito 01        8954   8589   8207   7671   8416 1000101   
 9 1000101001 Abadía sección 01001   8954   8589   8207   7671   8416 1000101001
10 27001 Abadín                     10791  10258   9762   9478   9116 27001     
# ℹ 55,263 more rows

Antes de empezar vamos a normalizar nombres de variables haciendo uso de clean_names() del paquete {janitor}.

mun_data <-
  mun_data |> 
  janitor::clean_names()
renta_mun <-
  renta_mun |> 
  janitor::clean_names()

3.1 Pregunta 1

Convierte a tidydata renta_mun obteniendo una tabla de 4 columnas: unidad, year, renta y codigo_ine (sin ausentes y cada dato del tipo correcto)

Code
renta_mun_tidy <-
  renta_mun |> 
  pivot_longer(cols = contains("x"), names_to = "year",
               values_to = "renta", names_prefix = "x",
               names_transform = list(year = as.numeric),
               values_drop_na = TRUE)

3.2 Pregunta 2

Si te fijas en la tabla anterior, tenemos datos de diferentes unidades administrativas que no siempre son municipios. Sabiendo que todos los municipios tienen un código de 5 caracteres (que representan todos ellos números), filtra sólo aquellos registros que correspondan a unidades municipales.

Code
renta_mun_tidy <-
  renta_mun_tidy |>
  filter(str_detect(codigo_ine, pattern = "[0-9]{5}") & 
           str_length(codigo_ine) == 5)

3.3 Pregunta 3

A continuación separa adecuadamente la variable de unidad en dos columnas: una con el código (que ya tiene, por lo que debe eliminar uno de los dos) y el nombre. Elimina los espacios sobrantes (echa un vistazo a las opciones del paquete {stringr}).

Code
renta_mun_tidy <-
  renta_mun_tidy |>
  separate(col = "unidad", into = c("cod_rm", "name"), sep = 5) |> 
  select(-cod_rm) |> 
  mutate(name = str_trim(name)) 

3.4 Pregunta 4

¿En qué año fue mayor la renta media? ¿Y más baja? ¿Cuál fue la renta mediana de los municipios de España en 2019?

summary_renta <-
  renta_mun_tidy |> 
  summarise("mean_renta" = mean(renta, na.rm = TRUE),
            .by = year)
summary_renta |>
  slice_min(mean_renta, n = 1)
# A tibble: 1 × 2
   year mean_renta
  <dbl>      <dbl>
1  2015     10083.
summary_renta |>
  slice_max(mean_renta, n = 1)
# A tibble: 1 × 2
   year mean_renta
  <dbl>      <dbl>
1  2019     11672.
renta_mun_tidy |> 
  filter(year == 2019) |> 
  summarise("median_renta" = median(renta, na.rm = TRUE))
# A tibble: 1 × 1
  median_renta
         <dbl>
1        11462

3.5 Pregunta 5

Haz lo que consideres para obtener la provincia con la renta media más alta en 2019 y la más baja. Asegúrate de obtener su nombre.

Code
summary_by_prov <-
  renta_mun_tidy |> 
  filter(year == 2019) |> 
  left_join(mun_data, by = "codigo_ine", suffix = c("", "_rm")) |> 
  select(-contains("rm")) |> 
  summarise("mean_by_prov" = mean(renta, na.rm = TRUE),
            .by = c("cpro", "ine_prov_name"))

summary_by_prov |> 
  slice_max(mean_by_prov, n = 1)
# A tibble: 1 × 3
  cpro  ine_prov_name mean_by_prov
  <chr> <chr>                <dbl>
1 20    Gipuzkoa            15890.
Code
summary_by_prov |> 
  slice_min(mean_by_prov, n = 1)
# A tibble: 1 × 3
  cpro  ine_prov_name mean_by_prov
  <chr> <chr>                <dbl>
1 06    Badajoz              8805.

3.6 Pregunta 6

Obten de cada ccaa el nombre del municipio con mayor renta en 2019.

Code
renta_mun_tidy |> 
  filter(year == 2019) |> 
  left_join(mun_data, by = "codigo_ine", suffix = c("", "_rm")) |> 
  select(-contains("rm")) |> 
  slice_max(renta, by = "codauto")
# A tibble: 19 × 10
   name   codigo_ine  year renta codauto ine_ccaa_name cpro  ine_prov_name cmun 
   <chr>  <chr>      <dbl> <dbl> <chr>   <chr>         <chr> <chr>         <chr>
 1 Navas… 40904       2019 20242 07      Castilla y L… 40    Segovia       904  
 2 Almar… 10019       2019 13940 11      Extremadura   10    Cáceres       019  
 3 Oleir… 15058       2019 16447 12      Galicia       15    Coruña, A     058  
 4 Laukiz 48053       2019 21672 16      País Vasco    48    Bizkaia       053  
 5 Lumbr… 26091       2019 24007 17      Rioja, La     26    Rioja, La     091  
 6 Murcia 30030       2019 11631 14      Murcia, Regi… 30    Murcia        030  
 7 Rajad… 08178       2019 23401 09      Cataluña      08    Barcelona     178  
 8 Alocén 19023       2019 19700 08      Castilla - L… 19    Guadalajara   023  
 9 Arguis 22037       2019 19219 02      Aragón        22    Huesca        037  
10 Alcud… 04009       2019 14196 01      Andalucía     04    Almería       009  
11 Arang… 31023       2019 15517 15      Navarra, Com… 31    Navarra       023  
12 Santa… 35021       2019 15982 05      Canarias      35    Palmas, Las   021  
13 Rocaf… 46216       2019 17872 10      Comunitat Va… 46    Valencia/Val… 216  
14 Pozue… 28115       2019 26367 13      Madrid, Comu… 28    Madrid        115  
15 Valld… 07063       2019 19565 04      Balears, Ill… 07    Balears, Ill… 063  
16 Valde… 39093       2019 15574 06      Cantabria     39    Cantabria     093  
17 Tever… 33072       2019 15409 03      Asturias, Pr… 33    Asturias      072  
18 Ceuta  51001       2019 12038 18      Ceuta         51    Ceuta         001  
19 Melil… 52001       2019 11463 19      Melilla       52    Melilla       001  
# ℹ 1 more variable: lau_code <chr>