|>
tabla_1 xxx_join(tabla_2, by = id)
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)
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:
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
<- tibble("key" = 1:3, "val_x" = c("x1", "x2", "x3"))
tb_1 <- tibble("key" = c(1, 2, 4), "val_y" = c("y1", "y2", "y3")) tb_2
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.
<- tibble("key_1" = 1:3, "val_x" = c("x1", "x2", "x3"))
tb_1 <- tibble("key_2" = c(1, 2, 4), "val_y" = c("y1", "y2", "y3")) tb_2
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
<- tibble("k_11" = 1:3, "k_12" = c("a", "b", "c"), "val_x" = c("x1", "x2", "x3"))
tb_1 <- tibble("k_21" = c(1, 2, 4), "k_22" = c("a", "b", "e"), "val_y" = c("y1", "y2", "y3")) tb_2
# 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
<- tibble("key_1" = 1:3, "val" = c("x1", "x2", "x3"))
tb_1 <- tibble("key_2" = c(1, 2, 4), "val" = c("y1", "y2", "y3")) tb_2
# 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
yband_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
yband_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.
La variable
LAU_code
representa el código como unidad administrativa local según la estandarización de la UE (ver más en https://ec.europa.eu/eurostat/web/nuts/local-administrative-units).La variable
codigo_ine
está construida uniendo el código de la provincia y el de la comunidad autónoma.
# datos 2019
<- read_csv(file = "./datos/municipios.csv") mun_data
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.
<- read_csv(file = "./datos/renta_mun.csv") renta_mun
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 ::clean_names()
janitor<-
renta_mun |>
renta_mun ::clean_names() janitor
3.1 Pregunta 1
Convierte a tidydata
renta_mun
obteniendo una tabla de 4 columnas:unidad
,year
,renta
ycodigo_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>