19 Uniones
19.1 Introducción
Es raro que un análisis de datos involucre solo un único marco de datos. Por lo general, tiene muchos marcos de datos y debe unirlos para responder las preguntas que le interesan. Este capítulo le presentará dos tipos importantes de uniones:
- Combinaciones mutantes, que agregan nuevas variables a un marco de datos a partir de observaciones coincidentes en otro.
- Combinaciones de filtrado, que filtran las observaciones de un marco de datos en función de si coinciden o no con una observación en otro.
Comenzaremos analizando las llaves, las variables que se utilizan para conectar un par de marcos de datos en una combinación. Cimentamos la teoría con un examen de las llaves en los conjuntos de datos del paquete nycflights13, luego usamos ese conocimiento para comenzar a unir marcos de datos. A continuación, analizaremos cómo funcionan las uniones, centrándonos en su acción en las filas. Terminaremos con una discusión sobre las uniones no equitativas, una familia de uniones que proporcionan una forma más flexible de hacer coincidir llaves que la relación de igualdad predeterminada.
19.1.1 Requisitos previos
En este capítulo, exploraremos los cinco conjuntos de datos relacionados de nycflights13 utilizando las funciones de combinación de dplyr.
19.2 Llaves
Para comprender las uniones, primero debe comprender cómo se pueden conectar dos tablas a través de un par de llaves, dentro de cada tabla. En esta sección, aprenderá sobre los dos tipos de llave y verá ejemplos de ambos en los conjuntos de datos del paquete nycflights13. También aprenderá cómo verificar que sus llaves sean válidas y qué hacer si su tabla no tiene una llave.
19.2.1 Llaves primarias y foráneas
Cada unión implica un par de llaves: una llave principal y una llave externa. Una Llave principal es una variable o un conjunto de variables que identifica de forma única cada observación. Cuando se necesita más de una variable, la llave se denomina Llave compuesta. Por ejemplo, en nycflights13:
-
airlines
registra dos datos sobre cada aerolínea: su código de aerolínea y su nombre completo. Puede identificar una línea aérea con su código de dos letras, haciendo quecarrier
sea la llave principal.airlines #> # A tibble: 16 × 2 #> carrier name #> <chr> <chr> #> 1 9E Endeavor Air Inc. #> 2 AA American Airlines Inc. #> 3 AS Alaska Airlines Inc. #> 4 B6 JetBlue Airways #> 5 DL Delta Air Lines Inc. #> 6 EV ExpressJet Airlines Inc. #> # ℹ 10 more rows
-
airports
registra datos sobre cada aeropuerto. Puede identificar cada aeropuerto por su código de aeropuerto de tres letras, haciendo quefaa
sea la llave principal.airports #> # A tibble: 1,458 × 8 #> faa name lat lon alt tz dst #> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> #> 1 04G Lansdowne Airport 41.1 -80.6 1044 -5 A #> 2 06A Moton Field Municipal Airport 32.5 -85.7 264 -6 A #> 3 06C Schaumburg Regional 42.0 -88.1 801 -6 A #> 4 06N Randall Airport 41.4 -74.4 523 -5 A #> 5 09J Jekyll Island Airport 31.1 -81.4 11 -5 A #> 6 0A9 Elizabethton Municipal Airpo… 36.4 -82.2 1593 -5 A #> # ℹ 1,452 more rows #> # ℹ 1 more variable: tzone <chr>
-
planes
registra datos sobre cada plano. Puede identificar un avión por su número de cola, haciendo quetailnum
sea la llave principal.planes #> # A tibble: 3,322 × 9 #> tailnum year type manufacturer model engines #> <chr> <int> <chr> <chr> <chr> <int> #> 1 N10156 2004 Fixed wing multi… EMBRAER EMB-145XR 2 #> 2 N102UW 1998 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> 3 N103US 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> 4 N104UW 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> 5 N10575 2002 Fixed wing multi… EMBRAER EMB-145LR 2 #> 6 N105UW 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> # ℹ 3,316 more rows #> # ℹ 3 more variables: seats <int>, speed <int>, engine <chr>
-
weather
registra datos sobre el clima en los aeropuertos de origen. Puede identificar cada observación por la combinación de ubicación y hora, haciendo queorigin
ytime_hour
sean la llave principal compuesta.weather #> # A tibble: 26,115 × 15 #> origin year month day hour temp dewp humid wind_dir #> <chr> <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> #> 1 EWR 2013 1 1 1 39.0 26.1 59.4 270 #> 2 EWR 2013 1 1 2 39.0 27.0 61.6 250 #> 3 EWR 2013 1 1 3 39.0 28.0 64.4 240 #> 4 EWR 2013 1 1 4 39.9 28.0 62.2 250 #> 5 EWR 2013 1 1 5 39.0 28.0 64.4 260 #> 6 EWR 2013 1 1 6 37.9 28.0 67.2 240 #> # ℹ 26,109 more rows #> # ℹ 6 more variables: wind_speed <dbl>, wind_gust <dbl>, …
Una llave externa es una variable (o conjunto de variables) que corresponde a una llave principal en otra tabla. Por ejemplo:
-
flights$tailnum
es una llave foránea que corresponde a la llave primariaplanes$tailnum
. -
flights$carrier
es una llave foránea que corresponde a la llave primariaairlines$carrier
. -
flights$origin
es una llave foránea que corresponde a la llave primariaairports$faa
. -
flights$dest
es una llave foránea que corresponde a la llave primariaairports$faa
. -
flights$origin
-flights$time_hour
es una llave foránea compuesta que corresponde a la llave primaria compuestaweather$origin
-weather$time_hour
.
Estas relaciones se resumen visualmente en Figura 19.1.
Notará una buena característica en el diseño de estas claves: las claves principal y externa casi siempre tienen los mismos nombres, lo que, como verá en breve, hará que su vida de unión sea mucho más fácil. También vale la pena señalar la relación opuesta: casi todos los nombres de variables utilizados en varias tablas tienen el mismo significado en cada lugar. Sólo hay una excepción: year
significa año de salida en flights
y año de fabricación en planes
. Esto se volverá importante cuando empecemos a unir tablas.
19.2.2 Comprobación de llaves primarias
Ahora que hemos identificado las claves principales en cada tabla, es una buena práctica verificar que realmente identifiquen de manera única cada observación. Una forma de hacerlo es contar, count()
, las llaves primarias y buscar entradas donde n
sea mayor que uno. Esto revela que planes
y weather
se ven bien:
También debe verificar si faltan valores en sus llaves principales — si falta un valor, ¡entonces no puede identificar una observación!
planes |>
filter(is.na(tailnum))
#> # A tibble: 0 × 9
#> # ℹ 9 variables: tailnum <chr>, year <int>, type <chr>, manufacturer <chr>,
#> # model <chr>, engines <int>, seats <int>, speed <int>, engine <chr>
weather |>
filter(is.na(time_hour) | is.na(origin))
#> # A tibble: 0 × 15
#> # ℹ 15 variables: origin <chr>, year <int>, month <int>, day <int>,
#> # hour <int>, temp <dbl>, dewp <dbl>, humid <dbl>, wind_dir <dbl>, …
19.2.3 Llaves sustitutas
Hasta ahora no hemos hablado de la llave principal para flights
. No es muy importante aquí, porque no hay marcos de datos que lo usen como clave externa, pero aún así es útil considerarlo porque es más fácil trabajar con observaciones si tenemos alguna forma de describirlas a otros.
Después de pensar un poco y experimentar, determinamos que hay tres variables que juntas identifican de manera única cada vuelo:
¿La ausencia de duplicados convierte automáticamente a time_hour
-carrier
-flight
en una llave principal? Sin duda es un buen comienzo, pero no lo garantiza. Por ejemplo, ¿son la altitud y la latitud una buena clave principal para airports
?
Identificar un aeropuerto por su altitud y latitud es claramente una mala idea y, en general, no es posible saber solo a partir de los datos si una combinación de variables constituye o no una buena clave primaria. Pero para los vuelos, la combinación de time_hour
, carrier
y flight
parece razonable porque sería realmente confuso para una aerolínea y sus clientes si hubiera varios vuelos con el mismo número de vuelo en el aire al mismo tiempo.
Dicho esto, sería mejor que introdujéramos una clave sustituta numérica simple usando el número de fila:
flights2 <- flights |>
mutate(id = row_number(), .before = 1)
flights2
#> # A tibble: 336,776 × 20
#> id year month day dep_time sched_dep_time dep_delay arr_time
#> <int> <int> <int> <int> <int> <int> <dbl> <int>
#> 1 1 2013 1 1 517 515 2 830
#> 2 2 2013 1 1 533 529 4 850
#> 3 3 2013 1 1 542 540 2 923
#> 4 4 2013 1 1 544 545 -1 1004
#> 5 5 2013 1 1 554 600 -6 812
#> 6 6 2013 1 1 554 558 -4 740
#> # ℹ 336,770 more rows
#> # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, …
Las claves sustitutas pueden ser particularmente útiles cuando se comunican con otros humanos: es mucho más fácil decirle a alguien que mire el vuelo 2001 que decir que mire el UA430 que partió 9am 2013-01-03.
19.2.4 Ejercicios
Nos olvidamos de dibujar la relación entre
weather
yairports
en Figura 19.1. ¿Cuál es la relación y cómo debería aparecer en el diagrama?weather
solo contiene información de los tres aeropuertos de origen en NYC. Si contuviera registros meteorológicos de todos los aeropuertos de EE.UU. ¿qué conexión adicional haría conflights
?Las variables
year
,month
,day
,hour
yorigin
casi forman una clave compuesta paraweather
, pero hay una hora que tiene observaciones duplicadas. ¿Puedes averiguar qué tiene de especial esa hora?Sabemos que algunos días del año son especiales y en ellos vuelan menos personas de lo habitual (por ejemplo, la víspera de Navidad y el día de Navidad). ¿Cómo podría representar esos datos como un marco de datos? ¿Cuál sería la llave principal? ¿Cómo se conectaría a los marcos de datos existentes?
Dibuje un diagrama que ilustre las conexiones entre los marcos de datos
Batting
,People
ySalaries
en el paquete Lahman. Dibuja otro diagrama que muestre la relación entrePeople
,Managers
,AwardsManagers
. ¿Cómo caracterizaría la relación entre los data framesBatting
,Pitching
, yFielding
?
19.3 Uniones básicas
Ahora que comprende cómo se conectan los marcos de datos a través de claves, podemos comenzar a usar uniones para comprender mejor el conjunto de datos de flights
. dplyr proporciona seis funciones de unión: left_join()
, inner_join()
, right_join()
, full_join()
, semi_join()
y anti_join()
. Todas tienen la misma interfaz: toman un par de marcos de datos (x
e y
) y devuelven un marco de datos. El orden de las filas y columnas en la salida está determinado principalmente por x
.
En esta sección, aprenderá a usar una unión mutante, left_join()
, y dos uniones de filtrado, semi_join()
y anti_join()
. En la siguiente sección, aprenderá exactamente cómo funcionan estas funciones y sobre las inner_join()
, right_join()
y full_join()
restantes.
19.3.1 Uniones mutantes
Una unión mutante le permite combinar variables de dos marcos de datos: primero hace coincidir las observaciones por sus claves, luego copia las variables de un marco de datos al otro. Al igual que mutate()
, las funciones de combinación agregan variables a la derecha, por lo que si su conjunto de datos tiene muchas variables, no verá las nuevas. Para estos ejemplos, facilitaremos ver lo que sucede creando un conjunto de datos más estrecho con solo seis variables1:
flights2 <- flights |>
select(year, time_hour, origin, dest, tailnum, carrier)
flights2
#> # A tibble: 336,776 × 6
#> year time_hour origin dest tailnum carrier
#> <int> <dttm> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA
#> # ℹ 336,770 more rows
Hay cuatro tipos de unión mutante, pero hay una que usará casi todo el tiempo: left_join()
. Es especial porque la salida siempre tendrá las mismas filas que x
, el dataframe al que estas uniendo2. El uso principal de left_join()
es agregar metadatos adicionales. Por ejemplo, podemos usar left_join()
para agregar el nombre completo de la aerolínea a los datos de flights2
:
flights2 |>
left_join(airlines)
#> Joining with `by = join_by(carrier)`
#> # A tibble: 336,776 × 7
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA United Air Lines In…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA United Air Lines In…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA American Airlines I…
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 JetBlue Airways
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Delta Air Lines Inc.
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA United Air Lines In…
#> # ℹ 336,770 more rows
O podríamos averiguar la temperatura y la velocidad del viento cuando partió cada avión:
flights2 |>
left_join(weather |> select(origin, time_hour, temp, wind_speed))
#> Joining with `by = join_by(time_hour, origin)`
#> # A tibble: 336,776 × 8
#> year time_hour origin dest tailnum carrier temp wind_speed
#> <int> <dttm> <chr> <chr> <chr> <chr> <dbl> <dbl>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 39.0 12.7
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 39.9 15.0
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 39.0 15.0
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 39.0 15.0
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 39.9 16.1
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 39.0 12.7
#> # ℹ 336,770 more rows
O qué tamaño de avión estaba volando:
flights2 |>
left_join(planes |> select(tailnum, type, engines, seats))
#> Joining with `by = join_by(tailnum)`
#> # A tibble: 336,776 × 9
#> year time_hour origin dest tailnum carrier type
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Fixed wing multi en…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA Fixed wing multi en…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Fixed wing multi en…
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 Fixed wing multi en…
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Fixed wing multi en…
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Fixed wing multi en…
#> # ℹ 336,770 more rows
#> # ℹ 2 more variables: engines <int>, seats <int>
Cuando left_join()
no encuentra una coincidencia para una fila en x
, completa las nuevas variables con los valores que faltan. Por ejemplo, no hay información sobre el avión con el número de cola N3ALAA
, por lo que faltarán el type
, los engines
y los seats
:
flights2 |>
filter(tailnum == "N3ALAA") |>
left_join(planes |> select(tailnum, type, engines, seats))
#> Joining with `by = join_by(tailnum)`
#> # A tibble: 63 × 9
#> year time_hour origin dest tailnum carrier type engines seats
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr> <int> <int>
#> 1 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 2 2013 2013-01-02 18:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 3 2013 2013-01-03 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 4 2013 2013-01-07 19:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 5 2013 2013-01-08 17:00:00 JFK ORD N3ALAA AA <NA> NA NA
#> 6 2013 2013-01-16 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> # ℹ 57 more rows
Volveremos a este problema unas cuantas veces en el resto del capítulo.
19.3.2 Especificación de llaves de combinación
Por defecto, left_join()
usará todas las variables que aparecen en ambos marcos de datos como llave de unión, la llamada unión natural. Esta es una heurística útil, pero no siempre funciona. Por ejemplo, ¿qué sucede si tratamos de unir flights2
con el conjunto de datos completo planes
?
flights2 |>
left_join(planes)
#> Joining with `by = join_by(year, tailnum)`
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier type manufacturer
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA <NA> <NA>
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA <NA> <NA>
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA <NA> <NA>
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA> <NA>
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL <NA> <NA>
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA <NA> <NA>
#> # ℹ 336,770 more rows
#> # ℹ 5 more variables: model <chr>, engines <int>, seats <int>, …
Nos faltan muchas coincidencias porque nuestra combinación intenta usar tailnum
y year
como clave compuesta. Tanto flights
como planes
tienen una columna de year
pero significan cosas diferentes: flights$year
es el año en que ocurrió el vuelo y planes$year
es el año en que se construyó el avión. Solo queremos unirnos en tailnum
, por lo que debemos proporcionar una especificación explícita con join_by ()
:
flights2 |>
left_join(planes, join_by(tailnum))
#> # A tibble: 336,776 × 14
#> year.x time_hour origin dest tailnum carrier year.y
#> <int> <dttm> <chr> <chr> <chr> <chr> <int>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 1999
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 1998
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 1990
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 2012
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 1991
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 2012
#> # ℹ 336,770 more rows
#> # ℹ 7 more variables: type <chr>, manufacturer <chr>, model <chr>, …
Tenga en cuenta que las variables year
se eliminan de la ambigüedad en la salida con un sufijo (year.x
y year.y
), que le indica si la variable proviene del argumento x
o y
. Puede anular los sufijos predeterminados con el argumento suffix
.
join_by(tailnum)
es corto para join_by(tailnum == tailnum)
. Es importante saber acerca de esta forma más completa por dos razones. En primer lugar, describe la relación entre las dos tablas: las claves deben ser iguales. Es por eso que este tipo de unión a menudo se denomina equi-unión. Aprenderá acerca de las uniones no equitativas en Sección 19.5.
En segundo lugar, es cómo especifica diferentes claves de combinación en cada tabla. Por ejemplo, hay dos formas de unirs las tablas flight2
y airports
: ya sea por dest
o origin
:
flights2 |>
left_join(airports, join_by(dest == faa))
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA George Bush Interco…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA George Bush Interco…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Miami Intl
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA>
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Hartsfield Jackson …
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Chicago Ohare Intl
#> # ℹ 336,770 more rows
#> # ℹ 6 more variables: lat <dbl>, lon <dbl>, alt <dbl>, tz <dbl>, …
flights2 |>
left_join(airports, join_by(origin == faa))
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Newark Liberty Intl
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA La Guardia
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA John F Kennedy Intl
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 John F Kennedy Intl
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL La Guardia
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Newark Liberty Intl
#> # ℹ 336,770 more rows
#> # ℹ 6 more variables: lat <dbl>, lon <dbl>, alt <dbl>, tz <dbl>, …
En el código anterior, es posible que vea una forma diferente de especificar las claves de combinación, utilizando un vector de caracteres:
-
by = "x"
corresponde ajoin_by(x)
. -
by = c("a" = "x")
corresponde ajoin_by(a == x)
.
Ahora que existe, preferimos join_by()
ya que proporciona una especificación más clara y flexible.
inner_join()
, right_join()
, full_join()
tienen la misma que left_join()
. La diferencia es qué filas conservan: la combinación izquierda mantiene todas las filas en x
, la combinación derecha mantiene todas las filas en y
, la combinación completa mantiene todas las filas en x
o y
, y la combinación interna solo mantiene las filas que aparecen tanto en x
como en y
. Volveremos a esto con más detalle más adelante.
19.3.3 Filtrado de uniones
Como puede suponer, la acción principal de una unión de filtrado es filtrar las filas. Hay dos tipos: semi-uniones y anti-uniones. Semi-uniones mantienen todas las filas en x
que tienen una coincidencia en y
. Por ejemplo, podríamos usar una semi-unión para filtrar el conjunto de datos airports
para mostrar solo los aeropuertos de origen:
airports |>
semi_join(flights2, join_by(faa == origin))
#> # A tibble: 3 × 8
#> faa name lat lon alt tz dst tzone
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 EWR Newark Liberty Intl 40.7 -74.2 18 -5 A America/New_York
#> 2 JFK John F Kennedy Intl 40.6 -73.8 13 -5 A America/New_York
#> 3 LGA La Guardia 40.8 -73.9 22 -5 A America/New_York
O solo los destinos:
airports |>
semi_join(flights2, join_by(faa == dest))
#> # A tibble: 101 × 8
#> faa name lat lon alt tz dst tzone
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 ABQ Albuquerque Internati… 35.0 -107. 5355 -7 A America/Denver
#> 2 ACK Nantucket Mem 41.3 -70.1 48 -5 A America/New_Yo…
#> 3 ALB Albany Intl 42.7 -73.8 285 -5 A America/New_Yo…
#> 4 ANC Ted Stevens Anchorage… 61.2 -150. 152 -9 A America/Anchor…
#> 5 ATL Hartsfield Jackson At… 33.6 -84.4 1026 -5 A America/New_Yo…
#> 6 AUS Austin Bergstrom Intl 30.2 -97.7 542 -6 A America/Chicago
#> # ℹ 95 more rows
Anti-joins son lo contrario: devuelven todas las filas en x
que no tienen una coincidencia en y
. Son útiles para encontrar valores perdidos que están implícitos en los datos, el tema de Sección 18.3. Los valores faltantes implícitos no se muestran como NA
, sino que solo existen como una ausencia. Por ejemplo, podemos encontrar filas que faltan en airports
buscando vuelos que no tengan un aeropuerto de destino coincidente:
O podemos encontrar qué tailnum
s faltan en planes
:
19.3.4 Ejercicios
Encuentra las 48 horas (en el transcurso de todo el año) que tienen los peores retrasos. Haga una referencia cruzada con los datos del
weather
. ¿Puedes ver algún patrón?-
Imagina que has encontrado los 10 destinos más populares usando este código:
¿Cómo puede encontrar todos los vuelos a esos destinos?
¿Todos los vuelos que salen tienen los datos meteorológicos correspondientes a esa hora?
¿Qué tienen en común los números de cola que no tienen un registro coincidente en
planes
? (Pista: una variable explica ~90% de los problemas.)Agregue una columna a
planes
que enumere cadacarrier
que ha volado ese avión. Es de esperar que haya una relación implícita entre el avión y la línea aérea, porque cada avión lo pilota una sola línea aérea. Confirme o rechace esta hipótesis utilizando las herramientas que ha aprendido en los capítulos anteriores.Añade la latitud y la longitud del aeropuerto de origen y de destino a
flights
. ¿Es más fácil cambiar el nombre de las columnas antes o después de la unión?-
Calcule el retraso promedio por destino, luego únase al marco de datos
airports
para que pueda mostrar la distribución espacial de los retrasos. Aquí hay una manera fácil de dibujar un mapa de los Estados Unidos.:airports |> semi_join(flights, join_by(faa == dest)) |> ggplot(aes(x = lon, y = lat)) + borders("state") + geom_point() + coord_quickmap()
Es posible que desee utilizar el
size
o elcolor
de los puntos para mostrar el retraso promedio de cada aeropuerto. ¿Qué pasó el 13 de junio de 2013? Dibuje un mapa de los retrasos y luego use Google para hacer una referencia cruzada con el clima.
19.4 ¿Cómo funcionan las uniones?
Ahora que ha usado combinaciones varias veces, es hora de aprender más sobre cómo funcionan, enfocándose en cómo cada fila en x
coincide con las filas en y
. Comenzaremos presentando una representación visual de las uniones, usando los simples tibbles definidos a continuación y que se muestran en Figura 19.2. En estos ejemplos, usaremos una sola llave llamada key
y una sola columna de valor (val_x
y val_y
), pero todas las ideas se generalizan a múltiples llaves y múltiples valores.
Figura 19.3 introduce la base para nuestra representación visual. Muestra todas las posibles coincidencias entre x
e y
como la intersección entre las líneas dibujadas desde cada fila de x
y cada fila de y
. Las filas y columnas en la salida están determinadas principalmente por x
, por lo que la tabla x
es horizontal y se alinea con la salida.
Para describir un tipo específico de combinación, indicamos coincidencias con puntos. Las coincidencias determinan las filas en la salida, un nuevo marco de datos que contiene la clave, los valores x y los valores y. Por ejemplo, Figura 19.4 muestra una combinación interna, donde las filas se retienen si y solo si las claves son iguales.
Podemos aplicar los mismos principios para explicar las uniones externas, que mantienen las observaciones que aparecen en al menos uno de los marcos de datos. Estas uniones funcionan agregando una observación “virtual” adicional a cada marco de datos. Esta observación tiene una clave que coincide si ninguna otra clave coincide, y los valores se completan con NA
. Hay tres tipos de uniones externas:
-
Una unión izquierda mantiene todas las observaciones en
x
, Figura 19.5. Cada fila dex
se conserva en la salida porque puede volver a coincidir con una fila deNA
s eny
. -
Una unión derecha mantiene todas las observaciones en
y
, Figura 19.6. Cada fila dey
se conserva en la salida porque puede volver a hacer coincidir una fila deNA
s enx
. La salida aún coincide conx
tanto como sea posible; cualquier fila adicional dey
se agrega al final. -
Una combinación completa mantiene todas las observaciones que aparecen en
x
oy
, Figura 19.7. Cada fila dex
ey
se incluye en la salida porque tantox
comoy
tienen una fila de reserva deNA
. Una vez más, la salida comienza con todas las filas desdex
, seguidas de las filas restantesy
no coincidentes.
Otra forma de mostrar cómo difieren los tipos de unión externa es con un diagrama de Venn, como en Figura 19.8. Sin embargo, esta no es una gran representación porque, si bien puede refrescar su memoria sobre qué filas se conservan, no ilustra lo que sucede con las columnas.
Las uniones que se muestran aquí son las denominadas equi uniones, donde las filas coinciden si las claves son iguales. Las uniones equitativas son el tipo de unión más común, por lo que normalmente omitiremos el prefijo equi y solo diremos “unión interna” en lugar de “unión interna equi”. Volveremos a las uniones no equitativas en Sección 19.5.
19.4.1 Coincidencia de filas
Hasta ahora hemos explorado lo que sucede si una fila en x
coincide con cero o una fila en y
. ¿Qué sucede si coincide con más de una fila? Para comprender lo que sucede, primero limitemos nuestro enfoque a inner_join()
y luego dibujemos una imagen, Figura 19.9.
Hay tres resultados posibles para una fila en x
:
- Si no coincide con nada, se descarta.
- Si coincide con 1 fila en
y
, se conserva. - Si coincide con más de 1 fila en
y
, se duplica una vez para cada coincidencia.
En principio, esto significa que no hay una correspondencia garantizada entre las filas de la salida y las filas de x
, pero en la práctica, esto rara vez causa problemas. Hay, sin embargo, un caso particularmente peligroso que puede provocar una explosión combinatoria de filas. Imagina unir las siguientes dos tablas:
Mientras que la primera fila en df1
solo coincide con una fila en df2
, la segunda y la tercera fila coinciden con dos filas. Esto a veces se denomina unión de muchos a muchos
y hará que dplyr emita una advertencia:
df1 |>
inner_join(df2, join_by(key))
#> Warning in inner_join(df1, df2, join_by(key)): Detected an unexpected many-to-many relationship between `x` and `y`.
#> ℹ Row 2 of `x` matches multiple rows in `y`.
#> ℹ Row 2 of `y` matches multiple rows in `x`.
#> ℹ If a many-to-many relationship is expected, set `relationship =
#> "many-to-many"` to silence this warning.
#> # A tibble: 5 × 3
#> key val_x val_y
#> <dbl> <chr> <chr>
#> 1 1 x1 y1
#> 2 2 x2 y2
#> 3 2 x2 y3
#> 4 2 x3 y2
#> 5 2 x3 y3
Si está haciendo esto deliberadamente, puede configurar relationship = "many-to-many"
, como sugiere la advertencia.
19.4.2 Filtrado de uniones
El número de coincidencias también determina el comportamiento de las uniones de filtrado. El semi-join mantiene filas en x
que tienen una o más coincidencias en y
, como en Figura 19.10. El anti-join mantiene filas en x
que coinciden con cero filas en y
, como en Figura 19.11. En ambos casos, solo es importante la existencia de una coincidencia; no importa cuantas veces coincida. Esto significa que las uniones de filtrado nunca duplican filas como lo hacen las uniones mutantes.
19.5 Non-equi joins
Hasta ahora solo has visto uniones de igualdad, uniones donde las filas coinciden si la tecla x
es igual a la tecla y
. Ahora relajaremos esa restricción y analizaremos otras formas de determinar si un par de filas coinciden.
Pero antes de que podamos hacer eso, debemos revisar una simplificación que hicimos anteriormente. En equi-joins, las teclas x
e y
son siempre iguales, por lo que solo necesitamos mostrar una en la salida. Podemos solicitar que dplyr mantenga ambas claves con keep = TRUE
, lo que lleva al siguiente código y inner_join()
redibujado en Figura 19.12.
x |> inner_join(y, join_by(key == key), keep = TRUE)
#> # A tibble: 2 × 4
#> key.x val_x key.y val_y
#> <dbl> <chr> <dbl> <chr>
#> 1 1 x1 1 y1
#> 2 2 x2 2 y2
Cuando nos alejamos de las combinaciones de igualdad, siempre mostraremos las claves, porque los valores de las claves a menudo serán diferentes. Por ejemplo, en lugar de hacer coincidir solo cuando la x$key
y la y$key
son iguales, podríamos hacer coincidir siempre que la x$key
sea mayor o igual que la y$key
, lo que lleva a Figura 19.13. Las funciones de combinación de dplyr entienden esta distinción entre combinaciones equi y no equi, por lo que siempre mostrará ambas teclas cuando realice una combinación no equi.
Unión no equitativa no es un término particularmente útil porque solo le dice qué no es la unión, no qué es. dplyr ayuda identificando cuatro tipos particularmente útiles de unión no equitativa:
- Uniones cruzadas coinciden con cada par de filas.
-
Uniones de desigualdad use
<
,<=
,>
y>=
en lugar de==
. - Las uniones continuas son similares a las uniones de desigualdad, pero solo encuentran la coincidencia más cercana.
- Las uniones superpuestas son un tipo especial de unión de desigualdades diseñadas para trabajar con rangos.
Cada uno de estos se describe con más detalle en las siguientes secciones.
19.5.1 Uniones cruzadas
Una unión cruzada coincide con todo, como en Figura 19.14, generando el producto cartesiano de filas. Esto significa que la salida tendrá filas nrow(x) * nrow(y)
.
Las uniones cruzadas son útiles cuando se generan permutaciones. Por ejemplo, el siguiente código genera todos los pares de nombres posibles. Dado que estamos uniendo df
a sí mismo, esto a veces se denomina autounión. Las uniones cruzadas usan una función de unión diferente porque no hay distinción entre inner/left/right/full cuando estás haciendo coincidir cada fila.
df <- tibble(name = c("John", "Simon", "Tracy", "Max"))
df |> cross_join(df)
#> # A tibble: 16 × 2
#> name.x name.y
#> <chr> <chr>
#> 1 John John
#> 2 John Simon
#> 3 John Tracy
#> 4 John Max
#> 5 Simon John
#> 6 Simon Simon
#> # ℹ 10 more rows
19.5.2 Uniones de desigualdad
Las uniones de desigualdad usan <
, <=
, >=
o >
para restringir el conjunto de posibles coincidencias, como en Figura 19.13 y Figura 19.15.
Las uniones de desigualdad son extremadamente generales, tan generales que es difícil encontrar casos de uso específicos significativos. Una pequeña técnica útil es usarlos para restringir la unión cruzada de modo que, en lugar de generar todas las permutaciones, generemos todas las combinaciones:
df <- tibble(id = 1:4, name = c("John", "Simon", "Tracy", "Max"))
df |> inner_join(df, join_by(id < id))
#> # A tibble: 6 × 4
#> id.x name.x id.y name.y
#> <int> <chr> <int> <chr>
#> 1 1 John 2 Simon
#> 2 1 John 3 Tracy
#> 3 1 John 4 Max
#> 4 2 Simon 3 Tracy
#> 5 2 Simon 4 Max
#> 6 3 Tracy 4 Max
19.5.3 Uniones rodantes
Las combinaciones rotativas son un tipo especial de combinación de desigualdad donde en lugar de obtener todas las filas que satisfacen la desigualdad, obtienes solo la fila más cercana, como en Figura 19.16. Puede convertir cualquier combinación de desigualdad en una combinación continua agregando closest()
. Por ejemplo, join_by(closest(x <= y))
coincide con la y
más pequeña que es mayor o igual que x, y join_by(closest(x > y))
coincide con la y
más grande que es menor que x
.
Las uniones rotativas son particularmente útiles cuando tiene dos tablas de fechas que no se alinean perfectamente y desea encontrar (por ejemplo) la fecha más cercana en la tabla 1 que viene antes (o después) de alguna fecha en la tabla 2.
Por ejemplo, imagina que estás a cargo de la comisión de planificación de fiestas de tu oficina. Su empresa es bastante barata, por lo que en lugar de tener fiestas individuales, solo tiene una fiesta una vez cada trimestre. Las reglas para determinar cuándo se realizará una fiesta son un poco complejas: las fiestas siempre son los lunes, te saltas la primera semana de enero porque mucha gente está de vacaciones y el primer lunes del tercer trimestre de 2022 es el 4 de julio, por lo que eso tiene que ser retrasado una semana. Eso lleva a los siguientes días de fiesta:
Ahora imagina que tienes una tabla de cumpleaños de los empleados:
set.seed(123)
employees <- tibble(
name = sample(babynames::babynames$name, 100),
birthday = ymd("2022-01-01") + (sample(365, 100, replace = TRUE) - 1)
)
employees
#> # A tibble: 100 × 2
#> name birthday
#> <chr> <date>
#> 1 Kemba 2022-01-22
#> 2 Orean 2022-06-26
#> 3 Kirstyn 2022-02-11
#> 4 Amparo 2022-11-11
#> 5 Belen 2022-03-25
#> 6 Rayshaun 2022-01-11
#> # ℹ 94 more rows
Y para cada empleado queremos encontrar la fecha de la primera fiesta que viene después (o en) su cumpleaños. Podemos expresar eso con una unión rodante:
employees |>
left_join(parties, join_by(closest(birthday >= party)))
#> # A tibble: 100 × 4
#> name birthday q party
#> <chr> <date> <int> <date>
#> 1 Kemba 2022-01-22 1 2022-01-10
#> 2 Orean 2022-06-26 2 2022-04-04
#> 3 Kirstyn 2022-02-11 1 2022-01-10
#> 4 Amparo 2022-11-11 4 2022-10-03
#> 5 Belen 2022-03-25 1 2022-01-10
#> 6 Rayshaun 2022-01-11 1 2022-01-10
#> # ℹ 94 more rows
Sin embargo, hay un problema con este enfoque: las personas que cumplen años antes del 10 de enero no organizan una fiesta:
Para resolver ese problema, necesitaremos abordar el problema de una manera diferente, con uniones superpuestas.
19.5.4 Uniones superpuestas
Las uniones superpuestas proporcionan tres ayudantes que usan uniones de desigualdad para facilitar el trabajo con intervalos:
-
between(x, y_lower, y_upper)
es abreviatura parax >= y_lower, x <= y_upper
. -
within(x_lower, x_upper, y_lower, y_upper)
es abreviatura parax_lower >= y_lower, x_upper <= y_upper
. -
overlaps(x_lower, x_upper, y_lower, y_upper)
es abreviatura parax_lower <= y_upper, x_upper >= y_lower
.
Sigamos con el ejemplo del cumpleaños para ver cómo podría usarlos. Hay un problema con la estrategia que usamos anteriormente: no hay fiesta antes de los cumpleaños del 1 al 9 de enero. Por lo tanto, sería mejor ser explícito sobre los rangos de fechas que abarca cada fiesta y hacer un caso especial para esos cumpleaños anticipados:
parties <- tibble(
q = 1:4,
party = ymd(c("2022-01-10", "2022-04-04", "2022-07-11", "2022-10-03")),
start = ymd(c("2022-01-01", "2022-04-04", "2022-07-11", "2022-10-03")),
end = ymd(c("2022-04-03", "2022-07-11", "2022-10-02", "2022-12-31"))
)
parties
#> # A tibble: 4 × 4
#> q party start end
#> <int> <date> <date> <date>
#> 1 1 2022-01-10 2022-01-01 2022-04-03
#> 2 2 2022-04-04 2022-04-04 2022-07-11
#> 3 3 2022-07-11 2022-07-11 2022-10-02
#> 4 4 2022-10-03 2022-10-03 2022-12-31
Hadley es terriblemente malo ingresando datos, por lo que también quería verificar que los períodos de las fiestas no se superpusieran. Una forma de hacer esto es usar una autounión para verificar si algún intervalo de inicio-fin se superpone con otro:
parties |>
inner_join(parties, join_by(overlaps(start, end, start, end), q < q)) |>
select(start.x, end.x, start.y, end.y)
#> # A tibble: 1 × 4
#> start.x end.x start.y end.y
#> <date> <date> <date> <date>
#> 1 2022-04-04 2022-07-11 2022-07-11 2022-10-02
Vaya, hay una superposición, así que solucionemos ese problema y continuemos:
Ahora podemos emparejar a cada empleado con su partido. Este es un buen lugar para usar unmatched = "error"
porque queremos averiguar rápidamente si a algún empleado no se le asignó una fiesta.
employees |>
inner_join(parties, join_by(between(birthday, start, end)), unmatched = "error")
#> # A tibble: 100 × 6
#> name birthday q party start end
#> <chr> <date> <int> <date> <date> <date>
#> 1 Kemba 2022-01-22 1 2022-01-10 2022-01-01 2022-04-03
#> 2 Orean 2022-06-26 2 2022-04-04 2022-04-04 2022-07-10
#> 3 Kirstyn 2022-02-11 1 2022-01-10 2022-01-01 2022-04-03
#> 4 Amparo 2022-11-11 4 2022-10-03 2022-10-03 2022-12-31
#> 5 Belen 2022-03-25 1 2022-01-10 2022-01-01 2022-04-03
#> 6 Rayshaun 2022-01-11 1 2022-01-10 2022-01-01 2022-04-03
#> # ℹ 94 more rows
19.5.5 Ejercicios
-
¿Puedes explicar qué está pasando con las claves en esta unión equitativa? ¿Por qué son diferentes?
x |> full_join(y, join_by(key == key)) #> # A tibble: 4 × 3 #> key val_x val_y #> <dbl> <chr> <chr> #> 1 1 x1 y1 #> 2 2 x2 y2 #> 3 3 x3 <NA> #> 4 4 <NA> y3 x |> full_join(y, join_by(key == key), keep = TRUE) #> # A tibble: 4 × 4 #> key.x val_x key.y val_y #> <dbl> <chr> <dbl> <chr> #> 1 1 x1 1 y1 #> 2 2 x2 2 y2 #> 3 3 x3 NA <NA> #> 4 NA <NA> 4 y3
Al encontrar si algún período de fiesta se superponía con otro período de fiesta, usamos
q < q
enjoin_by ()
¿Por qué? ¿Qué pasa si eliminas esta desigualdad?
19.6 Resumen
En este capítulo, aprendió a usar combinaciones de mutación y filtrado para combinar datos de un par de marcos de datos. En el camino, aprendió cómo identificar claves y la diferencia entre claves primarias y externas. También comprende cómo funcionan las uniones y cómo averiguar cuántas filas tendrá la salida. Finalmente, ha logrado vislumbrar el poder de las uniones no equitativas y ha visto algunos casos de uso interesantes.
Este capítulo concluye la parte “Transformar” del libro, donde la atención se centró en las herramientas que podría usar con columnas y tibbles individuales. Aprendió sobre las funciones dplyr y base para trabajar con vectores lógicos, números y tablas completas, funciones stringr para trabajar con cadenas, funciones lubridate para trabajar con fechas y horas y funciones forcats para trabajar con factores.
En la siguiente parte del libro, aprenderá más sobre cómo obtener varios tipos de datos en R de forma ordenada.