forked from cienciadedatos/r4ds
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path13-relational-data.qmd
582 lines (408 loc) · 27.7 KB
/
13-relational-data.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
# Datos relacionales
## Introducción
Es raro que un análisis de datos involucre una única tabla de datos. Lo típico es que tengas muchas tablas que debes combinar para responder a tus preguntas de interés. De manera colectiva, se le llama _datos relacionales_ a esas múltiples tablas de datos, ya que sus relaciones, y no solo los conjuntos de datos individuales, son importantes.
Las relaciones siempre se definen sobre un par de tablas. Todas las otras relaciones se construyen sobre esta idea simple: las relaciones entre tres o más tablas son siempre una propiedad de las relaciones entre cada par. ¡A veces ambos elementos de un par pueden ser la misma tabla! Esto se necesita si, por ejemplo, tienes una tabla de personas y cada persona tiene una referencia a sus padres.
Para trabajar con datos relacionales necesitas verbos que funcionen con pares de tablas. Existen tres familias de verbos diseñadas para trabajar con datos relacionales:
* __Uniones de transformación__ (del inglés _mutating joins_), que agregan nuevas variables a un *data frame* a partir de las observaciones coincidentes en otra tabla.
* __Uniones de filtro__ (del inglés _filtering joins_), que filtran observaciones en un *data frame* con base en si coinciden o no con una observación de otra tabla.
* __Operaciones de conjuntos__ (del inglés _set operations_), que tratan las observaciones como elementos de un conjunto.
El lugar más común para encontrar datos relacionales es en un sistema _relacional_ de administración de bases de datos (*Relational Data Base Management System* en inglés), un concepto que abarca casi todas las bases de datos modernas. Si has usado una base de datos con anterioridad, casi seguramente fue SQL. Si es así, los conceptos de este capítulo debiesen ser familiares, aunque su expresión en **dplyr** es un poco distinta. En términos generales, **dplyr** es un poco más fácil de usar que SQLes, ya que dplyr se especializa en el análisis de datos: facilita las operaciones habituales, a expensas de dificultar otras que no se requieren a menudo para el análisis de datos.
### Prerrequisitos
Vamos a explorar datos relacionales contenidos en el paquete **datos** usando los verbos para dos tablas de **dplyr**.
```{r setup, message = FALSE}
library(tidyverse)
library(datos)
```
## Datos sobre vuelos {#nycflights13-relational}
Usaremos datos sobre vuelos desde y hacia Nueva York para aprender sobre datos relacionales^[NdT. El texto original se refiere al paquete nycflights13 cuya traducción se incluye en el paquete **datos**.]. El paquete **datos** contiene cinco _tibbles_ que utilizaremos para este propósito: `aerolineas`, `aeropuertos`, `aviones` y `clima`, que se relacionan con la tabla `vuelos` (que se usó en el capítulo sobre [transformación de datos]):
* `aerolineas` permite observar el nombre completo de la aerolínea a partir de su código abreviado:
```{r}
aerolineas
```
* `aeropuertos` entrega información de cada aeropuerto, identificado por su `código`:
```{r}
aeropuertos
```
* `aviones` entrega información de cada avión, identificado por su `codigo_cola`:
```{r}
aviones
```
* `clima` entrega información del clima en cada aeropuerto de Nueva York para cada hora:
```{r}
clima
```
Una forma de mostrar las relaciones entre las diferentes tablas es mediante un diagrama:
```{r, echo = FALSE}
knitr::include_graphics("diagrams_w_text_as_path/es/relational-nycflights.svg")
```
Este diagrama es un poco abrumador, ¡pero es simple comparado con algunos que verás en el exterior! La clave para entender estos diagramas es recordar que cada relación siempre involucra un par de tablas. No necesitas entender todo el diagrama, necesitas entender la cadena de relaciones entre las tablas que te interesan.
En estos datos:
* `vuelos` se connecta con `aviones` a través de la variable `codigo_cola`.
* `vuelos` se conecta con `aerolineas` a través de la variable `codigo_carrier`.
* `vuelos` se conecta con `aeropuertos` de dos formas: a través de las variables `origen` y
`destino`.
* `vuelos` se conecta con `clima` a través de las variables `origen` (la ubicación),
`anio`, `mes`, `dia` y `hora` (el horario).
### Ejercicios
1. Imagina que necesitas dibujar (aproximadamente) la ruta que cada avión vuela desde su origen
hasta el destino. ¿Qué variables necesitarías? ¿Qué tablas necesitarías combinar?
1. Olvidamos dibujar la relación entre `clima` y `aeropuertos`. ¿Cuál es la relación y cómo debería
aparecer en el diagrama?
1. `clima` únicamente contiene información de los aeropuertos de origen (Nueva York).
Si incluyera registros para todos los aeropuertos de EEUU, ¿qué relación tendría con `vuelos`?
1. Sabemos que hay días "especiales" en el año y pocas personas suelen volar en ellos.
¿Cómo se representarían en un data frame? ¿Cuáles serían las claves primarias de esa tabla?
¿Cómo se conectaría con las tablas existentes?
## Claves
Las variables usadas para conectar cada par de variables se llaman _claves_ (del inglés _key_). Una clave es una variable (o un conjunto de variables) que identifican de manera única una observación. En casos simples, una sola variable es suficiente para identificar una observación. Por ejemplo, cada avión está identificado de forma única por su `codigo_cola`. En otros casos, se pueden necesitar múltiples variables. Por ejemplo, para identificar una observación en `clima` se necesitan cinco variables: `anio`, `mes`, `dia`, `hora` y `origen`.
Existen dos tipos de claves:
* Una _clave primaria_ identifica únicamente una observación en su propia tabla.
Por ejemplo, `aviones$codigo_cola` es una clave primaria, ya que identifica de
manera única cada avión en la tabla `aviones`.
* Una _clave foránea_ únicamente identifica una observación en otra tabla.
Por ejemplo, `vuelos$codigo_cola` es una clave foránea, ya que aparece en la
tabla `vuelos`, en la que une cada vuelo con un único avión.
Una variable puede ser clave primaria _y_ clave foránea a la vez. Por ejemplo, `origen` es parte de la clave primaria `clima` y también una clave foránea de `aeropuertos`.
Una vez que identificas las claves primarias en tus tablas, es una buena práctica verificar que identifican de forma única cada observación. Una forma de hacerlo es usar `count()` con las claves primarias y buscar las entradas con `n` mayor a uno:
```{r}
aviones %>%
count(codigo_cola) %>%
filter(n > 1)
clima %>%
count(anio, mes, dia, hora, origen) %>%
filter(n > 1)
```
A veces una tabla puede no tener una clave primaria explícita: cada fila es una observación, pero no existe una combinación de variables que la identifique de forma confiable. Por ejemplo, ¿cuál es la clave primaria en la tabla `vuelos`? Quizás pienses que podría ser la fecha más el vuelo o el código de cola, pero ninguna de esas variables es única:
```{r}
vuelos %>%
count(anio, mes, dia, vuelo) %>%
filter(n > 1)
vuelos %>%
count(anio, mes, dia, codigo_cola) %>%
filter(n > 1)
```
Al comenzar a trabajar con estos datos, ingenuamente asumimos que cada número de vuelo sería usado solo una vez al día: eso haría mucho más simple comunicar problemas con un vuelo específico. ¡Desafortunadamente este no es el caso! Si una tabla no tiene una clave primaria, a veces es útil incluir una con `mutate()` y `row_number()` (_número de fila_). Eso simplifica hacer coincidir observaciones una vez que haz hecho algunos filtros y quieres volver a verificar con los datos originales. Esto se llama __clave subrogada__.
Una clave primaria y su correspondiente clave foránea en otra tabla forman una __relación__. Las relaciones son típicamente uno-a-muchos. Por ejemplo, cada vuelo tiene un avión, pero cada avión tiene muchos vuelos. En otros datos, ocasionalmente verás relaciones uno-a-uno. Puedes pensar esto como un caso especial de uno-a-muchos. Puedes modelar relaciones muchos-a-muchos como relaciones de la forma muchos-a-uno y uno-a-muchos. Por ejemplo, en estos datos existe una relación muchos-a-muchos entre aerolíneas y aeropuertos: cada aerolínea vuela a muchos aeropuertos, cada aeropuerto recibe a muchas aerolíneas.
### Ejercicios
1. Agrega una clave subrogada a `vuelos`.
1. Identifica las claves en los siguientes conjuntos de datos
1. `datos::bateadores`
1. `datos::nombres`
1. `datos::atmosfera`
1. `datos::vehiculos`
1. `datos::diamantes`
(Puede que necesites leer un poco de documentación.)
1. Dibuja un diagrama que ilustre las conexiones entre las tablas `bateadores`,
`personas` y `salarios` incluidas en el paquete **datos**. Dibuja otro diagrama que muestre la
relación entre `personas`, `dirigentes` y `premios_dirigentes`.
¿Cómo caracterizarías las relación entre `bateadores`, `lanzadores` y `jardineros`?
## Uniones de transformación {#mutating-joins}
La primera herramienta que miraremos para combinar pares de variables es la __unión de transformación__ (_mutating join_). Una unión de transformación te permite combinar variables a partir de dos tablas. Primero busca coincidencias de observaciones de acuerdo a sus claves y luego copia las variables de una tabla en la otra.
Tal como `mutate()`, las funciones de unión agregan variables hacia la derecha, por lo que si tienes muchas variables inicialmente, las nuevas variables no se imprimirán. Para estos ejemplos, crearemos un conjunto de datos más angosto para que sea más fácil ver qué es lo que está ocurriendo:
```{r}
vuelos2 <- vuelos %>%
select(anio:dia, hora, origen, destino, codigo_cola, aerolinea)
vuelos2
```
(Recuerda que en RStudio puedes también usar `View()` para evitar este problema.)
Imagina que quieres incluir el nombre completo de la aerolínea en `vuelos2`. Puedes combinar los datos de `aerolinas` y `vuelos2` con `left_join()` (*union_izquierda*):
```{r}
vuelos2 %>%
select(-origen, -destino) %>%
left_join(aerolineas, by = "aerolinea")
```
El resultado de unir `aerolineas` y `vuelos2` es la inclusión de una variable adicional: `nombre`. Esta es la razón de que llamemos unión de transformación a este tipo de unión. En este caso, podrías obtener el mismo resultado usando `mutate()` junto a las operaciones de filtro de R base:
```{r}
vuelos2 %>%
select(-origen, -destino) %>%
mutate(nombre = aerolineas$nombre[match(aerolinea, aerolineas$aerolinea)])
```
Sin embargo, esto último es difícil de generalizar cuando necesitas hacer coincidir múltiples variables y requiere hacer una lectura detenida para entender lo que se quiere hacer.
En las siguientes secciones explicaremos en detalle cómo funcionan las uniones de transformación. Comenzarás aprendiendo una representación visual útil de las uniones. Luego usaremos eso para explicar las cuatro uniones de transformación: la unión interior y las tres uniones exteriores. Cuando trabajas con datos reales, las claves no siempre identifican a las observaciones de forma única. Es por eso que a continuación hablaremos de lo que ocurre cuando no existe una coincidencia única. Finalmente, aprenderás cómo decirle a **dplyr** qué variables son las claves para una unión determinada.
### Entendiendo las uniones
Para ayudarte a entender las uniones, usaremos una representación gráfica:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-setup.svg")
```
```{r}
x <- tribble(
~key, ~val_x,
1, "x1",
2, "x2",
3, "x3"
)
y <- tribble(
~key, ~val_y,
1, "y1",
2, "y2",
4, "y3"
)
```
La columna coloreada representa la variable "clave": estas se usan para unir filas entre las tablas. La columa gris representa la columna "valor" que se usa en todo el proceso. En estos ejemplos te mostraremos una única clave, pero la idea es generalizable de manera directa a múltiples claves y múltiples valores.
Una unión es una forma de conectar cada fila en `x` con cero, una o más filas en `y`. El siguiente diagrama muestra cada coincidencia potencial como una intersección de pares de líneas.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-setup2.svg")
```
(Si observas detenidamente, te darás cuenta de que hemos cambiado el orden de las columnas clave y valor en `x`. Esto es para enfatizar que las uniones encuentran coincidencias basadas en las claves; el valor simplemente se traslada durante el proceso.)
En la unión que mostramos, las coincidencias se indican con puntos. El número de puntos es igual al número de coincidencias y al número de filas en la salida.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-inner.svg")
```
### Unión interior {#inner-join}
La forma más simple de unión es la _unión interior_ (del inglés _inner join_). Una unión interior une pares de observaciones siempre que sus claves sean iguales:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-inner.svg")
```
(Para ser precisos, esto corresponde a una _unión de igualdad_ (o _equijoin_) interior, debido a que las claves se unen usando el operador de igualdad. Dado que muchas uniones son uniones de igualdad, por lo general omitimos esa especificación.)
El output de una unión interior es un nuevo data frame que contiene la clave, los valores de x y los valores de y. Usamos `by` (*según*) para indicar a dplyr qué variable es la clave:
```{r}
x %>%
inner_join(y, by = "key")
```
La propiedad más importante de una unión interior es que las filas no coincidentes no se incluyen en el resultado. Esto significa que generalmente las uniones interiores no son apropiadas para su uso en el análisis de datos dado que es muy fácil perder observaciones.
### Uniones exteriores {#outer-join}
Una unión interior mantiene las observaciones que aparecen en ambas tablas. Una __unión exterior__ mantiene las observaciones que aparecen en al menos una de las tablas. Existen tres tipos de uniones exteriores:
* Una __unión izquierda__ (_left join_) mantiene todas las observaciones en `x`.
* Una __unión derecha__ (_right join_) mantiene todas las observaciones en `y`.
* Una __unión completa__ (_full join_) mantiene todas las observaciones en `x` e `y`.
Estas uniones funcionan agregando una observación "virtual" adicional a cada tabla. Esta observación tiene una clave que siempre coincide (de no haber otras claves coincidentes) y un valor que se llena con `NA`.
Gráficamente corresponde a lo siguiente:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-outer.svg")
```
La unión que más frecuentemente se usa es la unión izquierda: úsala cuando necesites buscar datos adicionales en otra tabla, dado que preserva las observaciones originales incluso cuando no hay coincidencias. La unión izquierda debiera ser tu unión por defecto, a menos que tengas un motivo importante para preferir una de las otras.
Otra forma de ilustrar diferentes tipos de uniones es mediante un diagrama de Venn:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-venn.svg")
```
Sin embargo, esta no es una buena representación. Puede ayudar a recordar qué uniones preservan las observaciones en qué tabla pero esto tiene una limitante importante: un diagrama de Venn no puede mostrar qué ocurre con las claves que no identifican de manera única una observación.
### Claves duplicadas {#join-matches}
Hasta ahora todos los diagramas han asumido que las claves son únicas. Pero ese no siempre es así. Esta sección explica qué ocurre en esos casos. Existen dos posibilidades:
1. Una tabla tiene claves duplicadas. Esto es útil cuando quieres agregar información
adicional dado que típicamente existe una relación uno a muchos.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-one-to-many.svg")
```
Nota que hemos puesto la columna clave en una posición ligeramente distinta en el output.
Esto refleja que la clave es una clave primaria en `y` y una clave foránea en `x`.
```{r}
x <- tribble(
~key, ~val_x,
1, "x1",
2, "x2",
2, "x3",
1, "x4"
)
y <- tribble(
~key, ~val_y,
1, "y1",
2, "y2"
)
left_join(x, y, by = "key")
```
1. Ambas tablas tienen claves duplicadas. Esto es usualmente un error debido a que
en ninguna de las tablas las claves identifican de manera única una observación.
Cuando unes claves duplicadas, se obtienen todas las posibles combinaciones, es decir, el producto cartesiano:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-many-to-many.svg")
```
```{r}
x <- tribble(
~key, ~val_x,
1, "x1",
2, "x2",
2, "x3",
3, "x4"
)
y <- tribble(
~key, ~val_y,
1, "y1",
2, "y2",
2, "y3",
3, "y4"
)
left_join(x, y, by = "key")
```
### Definiendo las columnas clave {#join-by}
Hasta ahora, los pares de tablas siempre se han unido de acuerdo a una única variable y esa variable tiene el mismo nombre en ambas tablas. Esta restricción se expresa de la forma `by = "key"`. Puedes usar otros valores de `by` para conectar las tablas de otras maneras:
* Por defecto, `by = NULL`, usa todas las variables que aparecen en ambas tablas,
lo que se conoce como unión __natural__. Por ejemplo, las tablas vuelos y clima
coinciden en sus variables comunes: `anio`, `mes`, `dia`, `hora` y `origen`.
```{r}
vuelos2 %>%
left_join(clima)
```
* Un vector de caracteres, `by = "x"`. Esto es similar a una unión natural,
pero usa algunas de las variables comunes. Por ejemplo, `vuelos` y `aviones`
tienen la variable `anio`, pero esta significa cosas distintas en cada tabla
por lo que queremos unir por `codigo_cola`.
```{r}
vuelos2 %>%
left_join(aviones, by = "codigo_cola")
```
Nota que la variable `anio` (que aparece en los dos data frames de entrada,
pero que no es igual en ambos casos) se desambigua con un sufijo en el output.
* Un vector de caracteres con nombres: `by = c("a" = "b")`. Esto va a unir la
variable `a` en la tabla `x` con la variabla `b` en la tabla `y`. Las variables de
`x` se usarán en el output.
Por ejemplo, si queremos dibujar un mapa necesitamos combinar los datos de vuelos con los datos de aeropuertos, que contienen la ubicación de cada uno (`latitud` y `longitud`). Cada vuelo tiene un `aeropuerto` de origen y destino, por lo que necesitamos especficar cuál queremos unir:
```{r}
vuelos2 %>%
left_join(aeropuertos, c("origen" = "codigo_aeropuerto"))
vuelos2 %>%
left_join(aeropuertos, c("destino" = "codigo_aeropuerto"))
```
### Ejercicios
1. Calcula el atraso promedio por destino y luego une los datos en `aeropuertos`
para que puedas mostrar la distribución espacial de los atrasos. Esta es una forma fácil de dibujar un mapa de los Estados Unidos:
```{r, eval = FALSE}
aeropuertos %>%
semi_join(vuelos, c("codigo_aeropuerto" = "destino")) %>%
ggplot(aes(longitud, latitud)) +
borders("state") +
geom_point() +
coord_quickmap()
```
(No te preocupes si no entiendes quá hace `semi_join()`; lo aprenderás a continuación.)
Quizás quieras usar `size` o `colour` para editar los puntos y mostrar
el atraso promedio de cada aeropuerto.
2. Agrega la ubicación de origen _y_ destino (por ejemplo, `latitud` y `longitud`)
a `vuelos`.
3. ¿Existe una relación entre la antiguedad de un avión y sus atrasos?
4. ¿Qué condiciones climáticas hacen más probables los atrasos?
5. ¿Qué sucedió el día 13 de junio de 2013? Muestra el patrón espacial de los atrasos. Luego, usa un buscador para encontrar referencias cruzadas con el clima.
```{r, eval = FALSE, include = FALSE}
peores <- filter(vuelos, !is.na(horario_salida), mes == 6, dia == 13)
peores %>%
group_by(destino) %>%
summarise(atraso = mean(atraso_llegada), n = n()) %>%
filter(n > 5) %>%
inner_join(aeropuertos, by = c("destino" = "codigo_aeropuerto")) %>%
ggplot(aes(longitud, latitud)) +
borders("state") +
geom_point(aes(size = n, colour = atraso)) +
coord_quickmap()
```
### Otras implementaciones
`base::merge()` puede realizar los cuatro tipos de uniones de transformación:
dplyr | merge
-------------------|-------------------------------------------
`inner_join(x, y)` | `merge(x, y)`
`left_join(x, y)` | `merge(x, y, all.x = TRUE)`
`right_join(x, y)` | `merge(x, y, all.y = TRUE)`,
`full_join(x, y)` | `merge(x, y, all.x = TRUE, all.y = TRUE)`
La ventaja de los verbos específicos de **dplyr** es que muestran de manera clara la intención del código: la diferencia entre las uniones es realmente importante pero se esconde en los argumentos de `merge()`. Las uniones de **dplyr** son considerablemente más rápidas y no generan problemas con el orden de las filas
SQL es una inspiración para las convenciones de **dplyr**, por lo que su traducción es directa:
dplyr | SQL
-----------------------------|-------------------------------------------
`inner_join(x, y, by = "z")` | `SELECT * FROM x INNER JOIN y USING (z)`
`left_join(x, y, by = "z")` | `SELECT * FROM x LEFT OUTER JOIN y USING (z)`
`right_join(x, y, by = "z")` | `SELECT * FROM x RIGHT OUTER JOIN y USING (z)`
`full_join(x, y, by = "z")` | `SELECT * FROM x FULL OUTER JOIN y USING (z)`
Nota que "INNER" y "OUTER" son opcionales, por lo que a menudo se omiten.
Unir distintas variables entre tablas, por ejemplo `inner_join(x, y, by = c("a" = "b"))`, usa una sintaxis ligeramente distinta en SQL: `SELECT * FROM x INNER JOIN y ON x.a = y.b`. Como la sintaxis sugiere, SQL soporta un rango más amplio de tipos de uniones que **dplyr**, ya que puedes conectar tablas usando restricciones distintas a las de igualdad (a veces llamadas de no-igualdad).
## Uniones de filtro {#filtering-joins}
Las uniones de filtro unen observaciones de la misma forma que las uniones de transformación pero afectan a las observaciones, no a las variables. Existen dos tipos:
* `semi_join(x, y)` __mantiene__ todas las observaciones en `x` con coincidencias en `y`.
* `anti_join(x, y)` __descarta__ todas las observaciones en `x` con coincidencias en `y`.
Las semi uniones son útiles para unir tablas resumen previamente filtradas con las filas originales. Por ejemplo, imagina que encontraste los diez destinos más populares:
```{r}
destinos_populares <- vuelos %>%
count(destino, sort = TRUE) %>%
head(10)
destinos_populares
```
Ahora quieres encontrar cada vuelo que fue a alguno de esos destinos. Puedes construir un filtro:
```{r}
vuelos %>%
filter(destino %in% destinos_populares$destino)
```
Pero es difícil extender este enfoque a varias variables. Por ejemplo, imagina que encontraste los diez días con los atrasos promedio más altos. ¿Cómo construirías un filtro que use `anio`, `mes` y `dia` para buscar coincidencias con `vuelos`?
Puedes, en cambio, usar `semi_join()`, que conecta dos tablas de manera similar a una unión de transformación, pero en lugar de agregar nuevas columnas, mantiene las filas en `x` que tienen coincidencias en `y`:
```{r}
vuelos %>%
semi_join(destinos_populares)
```
Gráficamente, un `semi_join()` se ve de la siguiente forma:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-semi.svg")
```
Solo la existencia de coincidencias es importante; da igual qué observaciones son coincidentes. Esto significa que las uniones de filtro nunca duplican filas como lo hacen las uniones de transformación:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-semi-many.svg")
```
La operación inversa de `semi_join()` es `anti_join()`. `anti_join()` mantiene las filas que _no_ tienen coincidencias:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams_w_text_as_path/es/join-anti.svg")
```
Las anti uniones son útiles para encontrar desajustes. Por ejemplo, al conectar `aviones` y `vuelos`, podría interesarte saber que existen muchos `vuelos` que no tienen coincidencias en `aviones`:
```{r}
vuelos %>%
anti_join(aviones, by = "codigo_cola") %>%
count(codigo_cola, sort = TRUE)
```
### Ejercicios
1. ¿Qué significa que a un vuelo le falte `codigo_cola`? ¿Qué tienen en común los códigos
de cola que no tienen registros coincidentes en `aviones`? (Pista: una variable explica
~90% de los problemas.)
1. Filtra los vuelos para mostrar únicamente los aviones que han realizado al menos cien
viajes.
1. Combina `datos::vehiculos` y `datos::comunes` para encontrar los registros de los
modelos más comunes.
1. Encuentra las 48 horas (en el transcurso del año) que tengan los peores atrasos. Haz
una referencia cruzada con la tabla `clima`. ¿Puedes observar patrones?
1. ¿Qué te indica `anti_join(vuelos, aeropuertos, by = c("destino" = "codigo_aeropuerto"))`?
¿Qué te indica `anti_join(aeropuertos, vuelos, by = c("codigo_aeropuerto" = "destino"))`?
1. Puedes esperar que exista una relación implícita entre aviones y aerolíneas, dado que cada
avión es operado por una única aerolínea. Confirma o descarta esta hipótesis usando las
herramientas que aprendiste más arriba.
## Problemas con las uniones
Los datos con los que has estado trabajando en este capítulo han sido limpiados de modo que tengas la menor cantidad de problemas posibles. Tus datos difícilmente estarán tan ordenados, por lo que hay algunas consideraciones y pasos a tener en cuenta para que las uniones funcionen adecuadamente sobre tus propios datos.
1. Comienza identificando las variables que forman las claves primarias en cada tabla.
Usualmente debieras hacerlo considerando tus conocimientos de los datos, no observando
empíricamente las combinaciones de variables que resultan en un identificador único.
Si te centras en las variables sin pensar en sus significados, puedes tener la (mala)
suerte de encontrar una combinación única en tus datos pero que no sea válida en general.
Por ejemplo, la altura y la longitud identifican de manera única cada aeropuerto, ¡pero
no son buenos identificadores!
```{r}
aeropuertos %>% count(altura, longitud) %>% filter(n > 1)
```
1. Verifica que ninguna de las variables en la clave primaria esté perdida.
¡Si hay un valor faltante no podrá identificar una observación!
1. Verifica que las claves foráneas coincidan con las claves primarias en
otra tabla. La mejor forma de hacerlo es mediante un `anti_join()`. Es común
que las claves no coincidan debido a errores en la entrada de datos. Arreglar
este problema requiere mucho trabajo.
Si tienes claves perdidas, debes tener cuidado en el uso de unión interior
versus unión exterior y considerar cuidadosamente si quieres descartar las filas
que no tengan coincidencias.
Ten en cuenta que verificar el número de filas antes y después de unir no es suficiente para asegurar
que la unión funcionó de forma exitosa. Si tienes una unión interior con claves duplicadas en ambas tablas,
puedes tener la mala suerte de que el número de filas descartadas sea igual al número de filas duplicadas.
## Operaciones de conjuntos {#set-operations}
El tipo final de verbo para dos tablas son las operaciones de conjunto. Si bien lo usamos de manera poco frecuente, en ocasiones es útil cuando quieres dividir un filtro complejo en partes maś simples. Todas estas operaciones funcionan con una fila completa, comparando los valores de cada variable. Esto espera que los input `x` e `y` tengan las mismas variables y trata las observaciones como conjuntos:
* `intersect(x, y)`: devuelve las observaciones comunes en `x` e `y`.
* `union(x, y)`: devuelve las observaciones únicas en `x` e `y`.
* `setdiff(x, y)`: devuelve las observaciones en `x` pero no en `y`.
Dados los siguientes datos simples:
```{r}
df1 <- tribble(
~x, ~y,
1, 1,
2, 1
)
df2 <- tribble(
~x, ~y,
1, 1,
1, 2
)
```
Las cuatro posibilidades son:
```{r}
intersect(df1, df2)
# Nota que obtenemos 3 filas, no 4
union(df1, df2)
setdiff(df1, df2)
setdiff(df2, df1)
```