Capítulo20 Datos Relacionados

## [1] "2026-09-09"

El tema proviene de los siguientes sitios.

English: https://r4ds.had.co.nz/relational-data.html

Español: https://es.r4ds.hadley.nz/13-relational-data.html


20.1 Temas: Datos Relacionados

  • Qué es una clave: primaria, foránea y sustituta
  • Cómo se comprueba que una clave lo es de verdad
  • Uniones de transformación (mutating joins): left_join(), inner_join(), right_join(), full_join()
  • Uniones de filtro (filtering joins): semi_join(), anti_join()
  • Definir las columnas clave con by
  • Los tres problemas que arruinan una unión

20.2 Por qué hace falta más de una tabla

Hasta ahora cada análisis cabía en una tabla. En un proyecto real casi nunca es así: los datos llegan repartidos en varias, y hay que juntarlos. Y eso está bien hecho, no es un descuido. Si en vuelos guardáramos el nombre completo de la aerolínea en cada fila, escribiríamos “United Air Lines Inc.” 336 776 veces, y el día que hubiera que corregir una letra habría que corregirla en todas.

La solución es guardar cada cosa una sola vez, en su propia tabla, y conectarlas por una columna común. A un conjunto de tablas conectadas así se le llama datos relacionados, y es como funcionan todas las bases de datos del mundo.

En este capítulo trabajamos con cinco tablas del paquete datos, que juntas describen los vuelos que salieron de Nueva York en 2013:

  • vuelos es la tabla central: una fila por vuelo.
  • aerolineas traduce el código de dos letras al nombre completo de la compañía.
  • aeropuertos da el nombre, la latitud y la longitud de cada aeropuerto.
  • aviones describe cada avión: año de fabricación, modelo, número de asientos.
  • clima da las condiciones del tiempo en cada aeropuerto de origen, hora por hora.

Y se conectan así:

  • vuelos$aerolinea apunta a aerolineas$aerolinea
  • vuelos$codigo_cola apunta a aviones$codigo_cola
  • vuelos$origen y vuelos$destino apuntan a aeropuertos$codigo_aeropuerto
  • vuelos se conecta con clima por cinco columnas a la vez: anio, mes, dia, hora y origen

Antes de escribir una sola línea de código, dibuja en papel las tablas y las flechas entre ellas. Los errores de unión casi siempre vienen de no tener claro cuál columna apunta a cuál.


library(tidyverse)
library(datos)

Mira las cinco tablas antes de unir nada. Fíjate sobre todo en qué columnas comparten:

vuelos # data set #1
## # A tibble: 336,776 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # ℹ 336,766 more rows
## # ℹ 13 more variables: horario_llegada <int>, llegada_programada <int>,
## #   atraso_llegada <dbl>, aerolinea <chr>, vuelo <int>, codigo_cola <chr>,
## #   origen <chr>, destino <chr>, tiempo_vuelo <dbl>, distancia <dbl>,
## #   hora <dbl>, minuto <dbl>, fecha_hora <dttm>
names(vuelos)
##  [1] "anio"               "mes"                "dia"               
##  [4] "horario_salida"     "salida_programada"  "atraso_salida"     
##  [7] "horario_llegada"    "llegada_programada" "atraso_llegada"    
## [10] "aerolinea"          "vuelo"              "codigo_cola"       
## [13] "origen"             "destino"            "tiempo_vuelo"      
## [16] "distancia"          "hora"               "minuto"            
## [19] "fecha_hora"
aerolineas # data set #2
## # A tibble: 16 × 2
##    aerolinea nombre                     
##    <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.   
##  7 F9        Frontier Airlines Inc.     
##  8 FL        AirTran Airways Corporation
##  9 HA        Hawaiian Airlines Inc.     
## 10 MQ        Envoy Air                  
## 11 OO        SkyWest Airlines Inc.      
## 12 UA        United Air Lines Inc.      
## 13 US        US Airways Inc.            
## 14 VX        Virgin America             
## 15 WN        Southwest Airlines Co.     
## 16 YV        Mesa Airlines Inc.
names(aerolineas)
## [1] "aerolinea" "nombre"
aeropuertos
## # A tibble: 1,458 × 8
##    codigo_aeropuerto nombre  latitud longitud altura zona_horaria horario_verano
##    <chr>             <chr>     <dbl>    <dbl>  <dbl>        <dbl> <chr>         
##  1 04G               Lansdo…    41.1    -80.6   1044           -5 A             
##  2 06A               Moton …    32.5    -85.7    264           -6 A             
##  3 06C               Schaum…    42.0    -88.1    801           -6 A             
##  4 06N               Randal…    41.4    -74.4    523           -5 A             
##  5 09J               Jekyll…    31.1    -81.4     11           -5 A             
##  6 0A9               Elizab…    36.4    -82.2   1593           -5 A             
##  7 0G6               Willia…    41.5    -84.5    730           -5 A             
##  8 0G7               Finger…    42.9    -76.8    492           -5 A             
##  9 0P2               Shoest…    39.8    -76.6   1000           -5 U             
## 10 0S9               Jeffer…    48.1   -123.     108           -8 A             
## # ℹ 1,448 more rows
## # ℹ 1 more variable: zona_horaria_iana <chr>
names(aeropuertos)
## [1] "codigo_aeropuerto" "nombre"            "latitud"          
## [4] "longitud"          "altura"            "zona_horaria"     
## [7] "horario_verano"    "zona_horaria_iana"
aviones
## # A tibble: 3,322 × 9
##    codigo_cola  anio tipo           fabricante modelo motores asientos velocidad
##    <chr>       <int> <chr>          <chr>      <chr>    <int>    <int>     <int>
##  1 N10156       2004 Ala fija mult… EMBRAER    EMB-1…       2       55        NA
##  2 N102UW       1998 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  3 N103US       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  4 N104UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  5 N10575       2002 Ala fija mult… EMBRAER    EMB-1…       2       55        NA
##  6 N105UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  7 N107US       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  8 N108UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  9 N109UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
## 10 N110UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
## # ℹ 3,312 more rows
## # ℹ 1 more variable: tipo_motor <chr>
clima
## # A tibble: 26,115 × 15
##    origen  anio   mes   dia  hora temperatura punto_rocio humedad
##    <chr>  <int> <int> <int> <int>       <dbl>       <dbl>   <dbl>
##  1 EWR     2013     1     1     1        39.0        26.1    59.4
##  2 EWR     2013     1     1     2        39.0        27.0    61.6
##  3 EWR     2013     1     1     3        39.0        28.0    64.4
##  4 EWR     2013     1     1     4        39.9        28.0    62.2
##  5 EWR     2013     1     1     5        39.0        28.0    64.4
##  6 EWR     2013     1     1     6        37.9        28.0    67.2
##  7 EWR     2013     1     1     7        39.0        28.0    64.4
##  8 EWR     2013     1     1     8        39.9        28.0    62.2
##  9 EWR     2013     1     1     9        39.9        28.0    62.2
## 10 EWR     2013     1     1    10        41          28.0    59.6
## # ℹ 26,105 more rows
## # ℹ 7 more variables: direccion_viento <dbl>, velocidad_viento <dbl>,
## #   velocidad_rafaga <dbl>, precipitacion <dbl>, presion <dbl>,
## #   visibilidad <dbl>, fecha_hora <dttm>
vuelos
## # A tibble: 336,776 × 19
##     anio   mes   dia horario_salida salida_programada atraso_salida
##    <int> <int> <int>          <int>             <int>         <dbl>
##  1  2013     1     1            517               515             2
##  2  2013     1     1            533               529             4
##  3  2013     1     1            542               540             2
##  4  2013     1     1            544               545            -1
##  5  2013     1     1            554               600            -6
##  6  2013     1     1            554               558            -4
##  7  2013     1     1            555               600            -5
##  8  2013     1     1            557               600            -3
##  9  2013     1     1            557               600            -3
## 10  2013     1     1            558               600            -2
## # ℹ 336,766 more rows
## # ℹ 13 more variables: horario_llegada <int>, llegada_programada <int>,
## #   atraso_llegada <dbl>, aerolinea <chr>, vuelo <int>, codigo_cola <chr>,
## #   origen <chr>, destino <chr>, tiempo_vuelo <dbl>, distancia <dbl>,
## #   hora <dbl>, minuto <dbl>, fecha_hora <dttm>
aviones
## # A tibble: 3,322 × 9
##    codigo_cola  anio tipo           fabricante modelo motores asientos velocidad
##    <chr>       <int> <chr>          <chr>      <chr>    <int>    <int>     <int>
##  1 N10156       2004 Ala fija mult… EMBRAER    EMB-1…       2       55        NA
##  2 N102UW       1998 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  3 N103US       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  4 N104UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  5 N10575       2002 Ala fija mult… EMBRAER    EMB-1…       2       55        NA
##  6 N105UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  7 N107US       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  8 N108UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
##  9 N109UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
## 10 N110UW       1999 Ala fija mult… AIRBUS IN… A320-…       2      182        NA
## # ℹ 3,312 more rows
## # ℹ 1 more variable: tipo_motor <chr>
aviones %>%
  count(codigo_cola) %>%
  filter(n > 1)
## # A tibble: 0 × 2
## # ℹ 2 variables: codigo_cola <chr>, n <int>

20.3 Claves

Una clave es la columna, o el conjunto de columnas, que identifica una observación sin ambigüedad. Hay tres tipos, y conviene distinguirlos:

  • Una clave primaria identifica una observación en su propia tabla. codigo_cola es la clave primaria de aviones: cada avión aparece una sola vez.
  • Una clave foránea identifica una observación de otra tabla. codigo_cola en vuelos es una clave foránea: ahí se repite miles de veces, una por cada vuelo que hizo ese avión.
  • Una clave sustituta es la que uno se inventa cuando la tabla no tiene ninguna, casi siempre un número de fila (mutate(id = row_number())).

A veces la clave es una sola columna y a veces hacen falta varias. vuelos no tiene clave primaria: ni el número de vuelo ni la fecha bastan, porque el mismo vuelo sale todos los días y varios vuelos salen a la misma hora.

20.3.1 Cómo se comprueba una clave

Hay una sola manera, y es la que aparece una y otra vez en los bloques de abajo: contar y quedarse con lo que se repite. Si la columna es de verdad una clave, el resultado tiene que estar vacío.

La receta para verificar una clave: count() seguido de filter(n > 1).

  • tabla |> count(columna) |> filter(n > 1): si devuelve cero filas, esa columna es una clave.
  • Con varias columnas: count(a, b, c) |> filter(n > 1), que comprueba si la combinación es única.
  • Ojo con los NA: una columna con valores faltantes no sirve como clave, porque un NA no identifica nada. Compruébalo aparte con filter(is.na(columna)).

Ese filter(n > 1) que devuelve una tabla vacía es una prueba superada, no un fallo. Si devuelve filas, ahí tienes tus duplicados.

Identify the keys in the following datasets

Lahman::Batting, babynames::babynames nasaweather::atmos fueleconomy::vehicles ggplot2::diamonds

head(Lahman::Batting)
##    playerID yearID stint teamID lgID  G AB R H X2B X3B HR RBI SB CS BB SO IBB
## 1 aardsda01   2004     1    SFN   NL 11  0 0 0   0   0  0   0  0  0  0  0   0
## 2 aardsda01   2006     1    CHN   NL 45  2 0 0   0   0  0   0  0  0  0  0   0
## 3 aardsda01   2007     1    CHA   AL 25  0 0 0   0   0  0   0  0  0  0  0   0
## 4 aardsda01   2008     1    BOS   AL 47  1 0 0   0   0  0   0  0  0  0  1   0
## 5 aardsda01   2009     1    SEA   AL 73  0 0 0   0   0  0   0  0  0  0  0   0
## 6 aardsda01   2010     1    SEA   AL 53  0 0 0   0   0  0   0  0  0  0  0   0
##   HBP SH SF GIDP
## 1   0  0  0    0
## 2   0  1  0    0
## 3   0  0  0    0
## 4   0  0  0    0
## 5   0  0  0    0
## 6   0  0  0    0
head(datos::bateadores)
##   id_jugador id_anio orden_equipos id_equipo id_liga juegos al_bate carreras
## 1  aardsda01    2004             1       SFN      NL     11       0        0
## 2  aardsda01    2006             1       CHN      NL     45       2        0
## 3  aardsda01    2007             1       CHA      AL     25       0        0
## 4  aardsda01    2008             1       BOS      AL     47       1        0
## 5  aardsda01    2009             1       SEA      AL     73       0        0
## 6  aardsda01    2010             1       SEA      AL     53       0        0
##   golpes dobles triples cuadrangulares carreras_empujadas bases_robadas
## 1      0      0       0              0                  0             0
## 2      0      0       0              0                  0             0
## 3      0      0       0              0                  0             0
## 4      0      0       0              0                  0             0
## 5      0      0       0              0                  0             0
## 6      0      0       0              0                  0             0
##   atrapado_robando base_bolas ponches base_intencional golpeado
## 1                0          0       0                0        0
## 2                0          0       0                0        0
## 3                0          0       0                0        0
## 4                0          0       1                0        0
## 5                0          0       0                0        0
## 6                0          0       0                0        0
##   toque_sacrificio elavado_sacrificio doble_matanza
## 1                0                  0             0
## 2                1                  0             0
## 3                0                  0             0
## 4                0                  0             0
## 5                0                  0             0
## 6                0                  0             0
Lahman::Batting %>% 
  count(playerID) %>% 
  filter(n>1) |> 
  head()
##    playerID  n
## 1 aardsda01  9
## 2 aaronha01 23
## 3 aaronto01  7
## 4  aasedo01 13
## 5  abadan01  3
## 6  abadfe01 12
bateadores %>%
count(id_jugador,id_anio,orden_equipos) %>%
filter(n > 1) |> 
  head()
## [1] id_jugador    id_anio       orden_equipos n            
## <0 rows> (or 0-length row.names)
Lahman::Batting %>% 
  count(playerID, yearID, stint) %>% 
  filter(n>1) |> 
  head()
## [1] playerID yearID   stint    n       
## <0 rows> (or 0-length row.names)
#babynames::babynames
babynames::babynames %>% 
  count(year, name, sex) %>% 
  filter(n>1)
## # A tibble: 0 × 4
## # ℹ 4 variables: year <dbl>, name <chr>, sex <chr>, n <int>

¿Cuál fue el primer año que su nombre aparece en la base de datos?

babynames::babynames %>% 
filter(name =="Abimelys")
## # A tibble: 0 × 5
## # ℹ 5 variables: year <dbl>, sex <chr>, name <chr>, n <int>, prop <dbl>
max(babynames::babynames$year)
## [1] 2017
nasaweather::atmos
## # A tibble: 41,472 × 11
##      lat  long  year month surftemp  temp pressure ozone cloudlow cloudmid
##    <dbl> <dbl> <int> <int>    <dbl> <dbl>    <dbl> <dbl>    <dbl>    <dbl>
##  1  36.2 -114.  1995     1     273.  272.      835   304      7.5     34.5
##  2  33.7 -114.  1995     1     280.  282.      940   304     11.5     32.5
##  3  31.2 -114.  1995     1     285.  285.      960   298     16.5     26  
##  4  28.7 -114.  1995     1     289.  291.      990   276     20.5     14.5
##  5  26.2 -114.  1995     1     292.  293.     1000   274     26       10.5
##  6  23.7 -114.  1995     1     294.  294.     1000   264     30        9.5
##  7  21.2 -114.  1995     1     295   295.     1000   258     29.5     11  
##  8  18.7 -114.  1995     1     298.  297.     1000   252     26.5     17.5
##  9  16.2 -114.  1995     1     300.  298.     1000   250     27.5     18.5
## 10  13.7 -114.  1995     1     300.  299.     1000   250     26       16.5
## # ℹ 41,462 more rows
## # ℹ 1 more variable: cloudhigh <dbl>
nasaweather::atmos %>% 
  count(lat, long, year, month) %>% 
  filter(n>1)
## # A tibble: 0 × 5
## # ℹ 5 variables: lat <dbl>, long <dbl>, year <int>, month <int>, n <int>
fueleconomy::vehicles
## # A tibble: 33,442 × 12
##       id make  model        year class trans drive   cyl displ fuel    hwy   cty
##    <dbl> <chr> <chr>       <dbl> <chr> <chr> <chr> <dbl> <dbl> <chr> <dbl> <dbl>
##  1 13309 Acura 2.2CL/3.0CL  1997 Subc… Auto… Fron…     4   2.2 Regu…    26    20
##  2 13310 Acura 2.2CL/3.0CL  1997 Subc… Manu… Fron…     4   2.2 Regu…    28    22
##  3 13311 Acura 2.2CL/3.0CL  1997 Subc… Auto… Fron…     6   3   Regu…    26    18
##  4 14038 Acura 2.3CL/3.0CL  1998 Subc… Auto… Fron…     4   2.3 Regu…    27    19
##  5 14039 Acura 2.3CL/3.0CL  1998 Subc… Manu… Fron…     4   2.3 Regu…    29    21
##  6 14040 Acura 2.3CL/3.0CL  1998 Subc… Auto… Fron…     6   3   Regu…    26    17
##  7 14834 Acura 2.3CL/3.0CL  1999 Subc… Auto… Fron…     4   2.3 Regu…    27    20
##  8 14835 Acura 2.3CL/3.0CL  1999 Subc… Manu… Fron…     4   2.3 Regu…    29    21
##  9 14836 Acura 2.3CL/3.0CL  1999 Subc… Auto… Fron…     6   3   Regu…    26    17
## 10 11789 Acura 2.5TL        1995 Comp… Auto… Fron…     5   2.5 Prem…    23    18
## # ℹ 33,432 more rows
fueleconomy::vehicles %>% 
    count(id) %>% 
  filter(n>1)
## # A tibble: 0 × 2
## # ℹ 2 variables: id <dbl>, n <int>
ggplot2::diamonds
## # A tibble: 53,940 × 10
##    carat cut       color clarity depth table price     x     y     z
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1  0.23 Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
##  2  0.21 Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
##  3  0.23 Good      E     VS1      56.9    65   327  4.05  4.07  2.31
##  4  0.29 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
##  5  0.31 Good      J     SI2      63.3    58   335  4.34  4.35  2.75
##  6  0.24 Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
##  7  0.24 Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
##  8  0.26 Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
##  9  0.22 Fair      E     VS2      65.1    61   337  3.87  3.78  2.49
## 10  0.23 Very Good H     VS1      59.4    61   338  4     4.05  2.39
## # ℹ 53,930 more rows
ggplot2::diamonds %>% 
      count(carat, cut, color, clarity, depth, price, x, y, z) %>% 
  filter(n>1)
## # A tibble: 283 × 10
##    carat cut       color clarity depth price     x     y     z     n
##    <dbl> <ord>     <ord> <ord>   <dbl> <int> <dbl> <dbl> <dbl> <int>
##  1  0.23 Very Good E     VVS2     60.9   530  3.96  3.99  2.42     2
##  2  0.24 Very Good E     VVS1     61.7   485  3.95  3.99  2.45     2
##  3  0.24 Very Good G     VVS2     62     449  4     4.03  2.49     2
##  4  0.26 Ideal     G     SI1      62     394  4.08  4.11  2.54     2
##  5  0.3  Good      J     VS1      63.4   394  4.23  4.26  2.69     2
##  6  0.3  Very Good D     SI1      62.5   552  4.26  4.28  2.67     2
##  7  0.3  Very Good E     SI1      62.9   526  4.25  4.3   2.69     2
##  8  0.3  Very Good E     VVS2     61.4   789  4.25  4.28  2.62     2
##  9  0.3  Very Good G     VS2      63     526  4.29  4.31  2.71     2
## 10  0.3  Very Good H     SI1      62.9   421  4.28  4.31  2.7      2
## # ℹ 273 more rows

Draw a diagram illustrating the connections between the Batting, People, and Salaries tables in the Lahman package. Draw another diagram that shows the relationship between People, Managers, AwardsManagers.

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.

names(Lahman::Batting)
##  [1] "playerID" "yearID"   "stint"    "teamID"   "lgID"     "G"       
##  [7] "AB"       "R"        "H"        "X2B"      "X3B"      "HR"      
## [13] "RBI"      "SB"       "CS"       "BB"       "SO"       "IBB"     
## [19] "HBP"      "SH"       "SF"       "GIDP"
names(Lahman::Salaries)
## [1] "yearID"   "teamID"   "lgID"     "playerID" "salary"
names(Lahman::People)
##  [1] "playerID"     "birthYear"    "birthMonth"   "birthDay"     "birthCity"   
##  [6] "birthCountry" "birthState"   "deathYear"    "deathMonth"   "deathDay"    
## [11] "deathCountry" "deathState"   "deathCity"    "nameFirst"    "nameLast"    
## [16] "nameGiven"    "weight"       "height"       "bats"         "throws"      
## [21] "debut"        "bbrefID"      "finalGame"    "retroID"      "deathDate"   
## [26] "birthDate"

20.4 Uniones de transformaciones

Una unión de transformación (mutating join) añade columnas de una tabla a otra, emparejando las filas por la clave. El nombre viene de que hace lo mismo que mutate(): la tabla sale con más columnas que entró.

  • mutating join

left_join(), inner_join(), full_join(), right_join() (dplyr): combinar tablas.

  • Qué hacen: unen dos tablas emparejando filas por una columna clave (by =).
  • left_join(x, y): conserva todas las filas de x; inner_join solo las coincidencias; full_join todas las de ambas.
  • De filtro: semi_join() y anti_join() no añaden columnas, solo filtran filas de x.
vuelos2 <- vuelos %>%
  dplyr::select(anio, dia, hora, origen, destino, codigo_cola, aerolinea)
vuelos2
## # A tibble: 336,776 × 7
##     anio   dia  hora origen destino codigo_cola aerolinea
##    <int> <int> <dbl> <chr>  <chr>   <chr>       <chr>    
##  1  2013     1     5 EWR    IAH     N14228      UA       
##  2  2013     1     5 LGA    IAH     N24211      UA       
##  3  2013     1     5 JFK    MIA     N619AA      AA       
##  4  2013     1     5 JFK    BQN     N804JB      B6       
##  5  2013     1     6 LGA    ATL     N668DN      DL       
##  6  2013     1     5 EWR    ORD     N39463      UA       
##  7  2013     1     6 EWR    FLL     N516JB      B6       
##  8  2013     1     6 LGA    IAD     N829AS      EV       
##  9  2013     1     6 JFK    MCO     N593JB      B6       
## 10  2013     1     6 LGA    ORD     N3ALAA      AA       
## # ℹ 336,766 more rows
names(aerolineas)
## [1] "aerolinea" "nombre"
aerolineas
## # A tibble: 16 × 2
##    aerolinea nombre                     
##    <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.   
##  7 F9        Frontier Airlines Inc.     
##  8 FL        AirTran Airways Corporation
##  9 HA        Hawaiian Airlines Inc.     
## 10 MQ        Envoy Air                  
## 11 OO        SkyWest Airlines Inc.      
## 12 UA        United Air Lines Inc.      
## 13 US        US Airways Inc.            
## 14 VX        Virgin America             
## 15 WN        Southwest Airlines Co.     
## 16 YV        Mesa Airlines Inc.
vuelos2 %>%
  dplyr::select(-origen, -destino) %>%
  left_join(aerolineas, by = "aerolinea")
## # A tibble: 336,776 × 6
##     anio   dia  hora codigo_cola aerolinea nombre                  
##    <int> <int> <dbl> <chr>       <chr>     <chr>                   
##  1  2013     1     5 N14228      UA        United Air Lines Inc.   
##  2  2013     1     5 N24211      UA        United Air Lines Inc.   
##  3  2013     1     5 N619AA      AA        American Airlines Inc.  
##  4  2013     1     5 N804JB      B6        JetBlue Airways         
##  5  2013     1     6 N668DN      DL        Delta Air Lines Inc.    
##  6  2013     1     5 N39463      UA        United Air Lines Inc.   
##  7  2013     1     6 N516JB      B6        JetBlue Airways         
##  8  2013     1     6 N829AS      EV        ExpressJet Airlines Inc.
##  9  2013     1     6 N593JB      B6        JetBlue Airways         
## 10  2013     1     6 N3ALAA      AA        American Airlines Inc.  
## # ℹ 336,766 more rows

Ahora vamos a entender las cuatro uniones con dos tablas mínimas, de tres filas cada una. Fíjate en que la clave 1 y la 2 están en las dos, pero la 3 solo está en x y la 4 solo está en y. Todo el capítulo se resume en qué le pasa a esas dos filas sueltas.

x <- tribble(
  ~key, ~val_x,
  1, "x1",
  2, "x2",
  3, "x3"
)
y <- tribble(
  ~key, ~val_y,
  1, "y1",
  2, "y2",
  4, "y3"
)

x
## # A tibble: 3 × 2
##     key val_x
##   <dbl> <chr>
## 1     1 x1   
## 2     2 x2   
## 3     3 x3
y
## # A tibble: 3 × 2
##     key val_y
##   <dbl> <chr>
## 1     1 y1   
## 2     2 y2   
## 3     4 y3

inner_join() se queda solo con lo que coincide. La fila 3 de x y la fila 4 de y desaparecen, así que el resultado tiene dos filas:

x %>% 
  inner_join(y, by="key")
## # A tibble: 2 × 3
##     key val_x val_y
##   <dbl> <chr> <chr>
## 1     1 x1    y1   
## 2     2 x2    y2

inner_join() pierde filas sin avisar. Es la causa número uno de análisis equivocados con datos relacionados: uno une dos tablas, sigue trabajando, y nunca se entera de que a mitad de camino se quedó sin la tercera parte de sus datos. Compara siempre nrow() antes y después de unir.

Las otras tres son uniones exteriores: conservan filas que no tienen pareja y rellenan con NA lo que falta. Se diferencian solo en cuál tabla es la que manda:

  • left_join(x, y) conserva todas las filas de x. Es la que se usa el 90 % de las veces, porque casi siempre uno tiene una tabla principal y quiere añadirle información de una tabla de consulta.
  • right_join(x, y) conserva todas las de y. Es lo mismo que left_join(y, x) con las columnas en otro orden.
  • full_join(x, y) conserva todas las de las dos.
#left_join

x %>% 
  left_join(y, 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>
#right_join

x %>% 
  right_join(y, 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
#full_join

x %>% 
  full_join(y, by="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

El mismo ejemplo con nombres y edades, que se lee mejor. Fíjate en Orlando, que no tiene edad, y en la persona de 22 años, que no tiene nombre: son la fila 3 y la fila 4 de antes.

x <- tribble(
  ~key, ~nombre_x,
  1, "Juan",
  2, "Maria",
  3, "Orlando"
)
y <- tribble(
  ~key, ~edad_y,
  1, 20,
  2, 21,
  4, 22
)

x
## # A tibble: 3 × 2
##     key nombre_x
##   <dbl> <chr>   
## 1     1 Juan    
## 2     2 Maria   
## 3     3 Orlando
y
## # A tibble: 3 × 2
##     key edad_y
##   <dbl>  <dbl>
## 1     1     20
## 2     2     21
## 3     4     22
x %>% 
  full_join(y, by="key")
## # A tibble: 4 × 3
##     key nombre_x edad_y
##   <dbl> <chr>     <dbl>
## 1     1 Juan         20
## 2     2 Maria        21
## 3     3 Orlando      NA
## 4     4 <NA>         22
x %>% 
  inner_join(y, by="key")
## # A tibble: 2 × 3
##     key nombre_x edad_y
##   <dbl> <chr>     <dbl>
## 1     1 Juan         20
## 2     2 Maria        21
x %>% 
  left_join(y, by="key")
## # A tibble: 3 × 3
##     key nombre_x edad_y
##   <dbl> <chr>     <dbl>
## 1     1 Juan         20
## 2     2 Maria        21
## 3     3 Orlando      NA
x %>% 
  right_join(y, by="key")
## # A tibble: 3 × 3
##     key nombre_x edad_y
##   <dbl> <chr>     <dbl>
## 1     1 Juan         20
## 2     2 Maria        21
## 3     4 <NA>         22
x %>% 
  full_join(y, by="key")
## # A tibble: 4 × 3
##     key nombre_x edad_y
##   <dbl> <chr>     <dbl>
## 1     1 Juan         20
## 2     2 Maria        21
## 3     3 Orlando      NA
## 4     4 <NA>         22
x %>% 
  inner_join(y, by="key")
## # A tibble: 2 × 3
##     key nombre_x edad_y
##   <dbl> <chr>     <dbl>
## 1     1 Juan         20
## 2     2 Maria        21

20.5 Las uniones con los datos de verdad

Con las tablas de vuelos, la unión típica es exactamente la del principio: añadirle a vuelos2 el nombre completo de la aerolínea, que vive en otra tabla.

vuelos2 %>%
  dplyr::select(-origen, -destino) %>%
  left_join(aerolineas, by = "aerolinea")
## # A tibble: 336,776 × 6
##     anio   dia  hora codigo_cola aerolinea nombre                  
##    <int> <int> <dbl> <chr>       <chr>     <chr>                   
##  1  2013     1     5 N14228      UA        United Air Lines Inc.   
##  2  2013     1     5 N24211      UA        United Air Lines Inc.   
##  3  2013     1     5 N619AA      AA        American Airlines Inc.  
##  4  2013     1     5 N804JB      B6        JetBlue Airways         
##  5  2013     1     6 N668DN      DL        Delta Air Lines Inc.    
##  6  2013     1     5 N39463      UA        United Air Lines Inc.   
##  7  2013     1     6 N516JB      B6        JetBlue Airways         
##  8  2013     1     6 N829AS      EV        ExpressJet Airlines Inc.
##  9  2013     1     6 N593JB      B6        JetBlue Airways         
## 10  2013     1     6 N3ALAA      AA        American Airlines Inc.  
## # ℹ 336,766 more rows
names(vuelos2)
## [1] "anio"        "dia"         "hora"        "origen"      "destino"    
## [6] "codigo_cola" "aerolinea"
names(clima)
##  [1] "origen"           "anio"             "mes"              "dia"             
##  [5] "hora"             "temperatura"      "punto_rocio"      "humedad"         
##  [9] "direccion_viento" "velocidad_viento" "velocidad_rafaga" "precipitacion"   
## [13] "presion"          "visibilidad"      "fecha_hora"

Cuando dos tablas comparten varias columnas con el mismo nombre y no se dice nada, dplyr las usa todas como clave. Eso se llama unión natural, y es cómodo pero peligroso: basta con que una de las tablas cambie de columnas para que la unión cambie de significado sin que nadie lo note. El segundo bloque hace lo mismo pero diciendo explícitamente cuáles columnas usar, que es lo recomendable:

vuelos2 %>%
left_join(clima)
## # A tibble: 3,974,448 × 18
##     anio   dia  hora origen destino codigo_cola aerolinea   mes temperatura
##    <int> <int> <dbl> <chr>  <chr>   <chr>       <chr>     <int>       <dbl>
##  1  2013     1     5 EWR    IAH     N14228      UA            1        39.0
##  2  2013     1     5 EWR    IAH     N14228      UA            2        28.0
##  3  2013     1     5 EWR    IAH     N14228      UA            3        35.1
##  4  2013     1     5 EWR    IAH     N14228      UA            4        45.0
##  5  2013     1     5 EWR    IAH     N14228      UA            5        44.1
##  6  2013     1     5 EWR    IAH     N14228      UA            6        72.0
##  7  2013     1     5 EWR    IAH     N14228      UA            7        75.0
##  8  2013     1     5 EWR    IAH     N14228      UA            8        72.0
##  9  2013     1     5 EWR    IAH     N14228      UA            9        73.9
## 10  2013     1     5 EWR    IAH     N14228      UA           10        53.1
## # ℹ 3,974,438 more rows
## # ℹ 9 more variables: punto_rocio <dbl>, humedad <dbl>, direccion_viento <dbl>,
## #   velocidad_viento <dbl>, velocidad_rafaga <dbl>, precipitacion <dbl>,
## #   presion <dbl>, visibilidad <dbl>, fecha_hora <dttm>
#> Joining, by = c("anio", "mes", "dia", "hora", "origen")
vuelos2 %>%
left_join(clima, by = c("anio", "dia", "hora", "origen"))
## # A tibble: 3,974,448 × 18
##     anio   dia  hora origen destino codigo_cola aerolinea   mes temperatura
##    <int> <int> <dbl> <chr>  <chr>   <chr>       <chr>     <int>       <dbl>
##  1  2013     1     5 EWR    IAH     N14228      UA            1        39.0
##  2  2013     1     5 EWR    IAH     N14228      UA            2        28.0
##  3  2013     1     5 EWR    IAH     N14228      UA            3        35.1
##  4  2013     1     5 EWR    IAH     N14228      UA            4        45.0
##  5  2013     1     5 EWR    IAH     N14228      UA            5        44.1
##  6  2013     1     5 EWR    IAH     N14228      UA            6        72.0
##  7  2013     1     5 EWR    IAH     N14228      UA            7        75.0
##  8  2013     1     5 EWR    IAH     N14228      UA            8        72.0
##  9  2013     1     5 EWR    IAH     N14228      UA            9        73.9
## 10  2013     1     5 EWR    IAH     N14228      UA           10        53.1
## # ℹ 3,974,438 more rows
## # ℹ 9 more variables: punto_rocio <dbl>, humedad <dbl>, direccion_viento <dbl>,
## #   velocidad_viento <dbl>, velocidad_rafaga <dbl>, precipitacion <dbl>,
## #   presion <dbl>, visibilidad <dbl>, fecha_hora <dttm>

Y cuando las columnas se llaman distinto en cada tabla, se dice con c("nombre_en_x" = "nombre_en_y"). Aquí el aeropuerto de origen se llama origen en vuelos2 y codigo_aeropuerto en aeropuertos:

names(vuelos2)
## [1] "anio"        "dia"         "hora"        "origen"      "destino"    
## [6] "codigo_cola" "aerolinea"
names(aeropuertos)
## [1] "codigo_aeropuerto" "nombre"            "latitud"          
## [4] "longitud"          "altura"            "zona_horaria"     
## [7] "horario_verano"    "zona_horaria_iana"
head(vuelos2, n=3)
## # A tibble: 3 × 7
##    anio   dia  hora origen destino codigo_cola aerolinea
##   <int> <int> <dbl> <chr>  <chr>   <chr>       <chr>    
## 1  2013     1     5 EWR    IAH     N14228      UA       
## 2  2013     1     5 LGA    IAH     N24211      UA       
## 3  2013     1     5 JFK    MIA     N619AA      AA
aeropuertos
## # A tibble: 1,458 × 8
##    codigo_aeropuerto nombre  latitud longitud altura zona_horaria horario_verano
##    <chr>             <chr>     <dbl>    <dbl>  <dbl>        <dbl> <chr>         
##  1 04G               Lansdo…    41.1    -80.6   1044           -5 A             
##  2 06A               Moton …    32.5    -85.7    264           -6 A             
##  3 06C               Schaum…    42.0    -88.1    801           -6 A             
##  4 06N               Randal…    41.4    -74.4    523           -5 A             
##  5 09J               Jekyll…    31.1    -81.4     11           -5 A             
##  6 0A9               Elizab…    36.4    -82.2   1593           -5 A             
##  7 0G6               Willia…    41.5    -84.5    730           -5 A             
##  8 0G7               Finger…    42.9    -76.8    492           -5 A             
##  9 0P2               Shoest…    39.8    -76.6   1000           -5 U             
## 10 0S9               Jeffer…    48.1   -123.     108           -8 A             
## # ℹ 1,448 more rows
## # ℹ 1 more variable: zona_horaria_iana <chr>
vuelos2 %>%
 right_join(aeropuertos, c("origen" = "codigo_aeropuerto"))
## # A tibble: 338,231 × 14
##     anio   dia  hora origen destino codigo_cola aerolinea nombre         latitud
##    <int> <int> <dbl> <chr>  <chr>   <chr>       <chr>     <chr>            <dbl>
##  1  2013     1     5 EWR    IAH     N14228      UA        Newark Libert…    40.7
##  2  2013     1     5 LGA    IAH     N24211      UA        La Guardia        40.8
##  3  2013     1     5 JFK    MIA     N619AA      AA        John F Kenned…    40.6
##  4  2013     1     5 JFK    BQN     N804JB      B6        John F Kenned…    40.6
##  5  2013     1     6 LGA    ATL     N668DN      DL        La Guardia        40.8
##  6  2013     1     5 EWR    ORD     N39463      UA        Newark Libert…    40.7
##  7  2013     1     6 EWR    FLL     N516JB      B6        Newark Libert…    40.7
##  8  2013     1     6 LGA    IAD     N829AS      EV        La Guardia        40.8
##  9  2013     1     6 JFK    MCO     N593JB      B6        John F Kenned…    40.6
## 10  2013     1     6 LGA    ORD     N3ALAA      AA        La Guardia        40.8
## # ℹ 338,221 more rows
## # ℹ 5 more variables: longitud <dbl>, altura <dbl>, zona_horaria <dbl>,
## #   horario_verano <chr>, zona_horaria_iana <chr>

Un ejemplo de para qué sirve todo esto: quedarse solo con los aeropuertos que de verdad reciben vuelos, y dibujarlos en un mapa. El semi_join() filtra sin añadir columnas, que es justo lo que hace falta aquí.

library(maps)

aeropuertos %>%
semi_join(vuelos, c("codigo_aeropuerto" = "destino")) %>%
ggplot(aes(longitud, latitud)) +
borders("state") +
geom_point() +
coord_quickmap()

  • mutate()
  • select()
  • left_join()
  • match()

20.6 Entendiendo las uniones

Todas las uniones funcionan igual por dentro. Imagina las dos tablas una al lado de la otra: para cada fila de la izquierda, R busca en la derecha las filas cuya clave coincida, y pega las columnas. Lo único que cambia entre las cinco funciones es qué hacer con las filas que no encontraron pareja.

20.6.1 Unión interiores

inner_join() es la unión interior: solo sobrevive lo que aparece en las dos tablas. Es la más limpia de entender y la más fácil de usar mal, porque el resultado nunca dice cuántas filas se quedaron por el camino.

Úsala cuando la ausencia de pareja signifique de verdad que esa fila no debe seguir en el análisis, y comprueba siempre cuántas filas perdiste.

20.6.2 Unión exteriores

Las tres uniones exteriores (left_join(), right_join(), full_join()) conservan filas sin pareja y ponen NA en las columnas que no pudieron llenar.

La regla práctica: empieza siempre por left_join(). Si tu tabla principal tiene 234 filas, después de un left_join() sigue teniendo 234 (salvo por el problema de la sección siguiente), y los NA que aparezcan te dicen exactamente qué no encontró pareja. Es la unión que no pierde datos y que además te informa.

Los NA que salen de un left_join() no son un fallo: son el resultado más informativo de la operación. Cuéntalos con filter(is.na(columna_nueva)) y mira qué tienen en común. Casi siempre revelan un problema en la tabla de consulta: una categoría que se te olvidó, un código escrito de dos maneras, un espacio de más.

20.6.3 Claves duplicadas

Cuando la clave está duplicada en ambas tablas, la unión genera todas las combinaciones posibles (producto cartesiano) y el resultado tendrá más filas de las esperadas. Verifica con count(clave) %>% filter(n > 1) antes de unir.

Conviene distinguir dos situaciones, porque una es normal y la otra es un error.

Duplicada en una sola tabla: normal. Es el caso de vuelos y aerolineas: la aerolínea UA aparece miles de veces en vuelos y una sola vez en aerolineas. El resultado tiene tantas filas como vuelos, y el nombre de la compañía se repite. Es exactamente lo que uno quiere.

Duplicada en las dos tablas: casi siempre un error. Si la clave se repite dos veces a cada lado, esa fila produce cuatro filas en el resultado. La tabla crece sin que nadie lo pida y los promedios que se calculen después estarán mal.

a <- tribble(
  ~clave, ~valor_a,
  1, "a1",
  1, "a2"
)

b <- tribble(
  ~clave, ~valor_b,
  1, "b1",
  1, "b2"
)

left_join(a, b, by = "clave")   # 4 filas, no 2
## # A tibble: 4 × 3
##   clave valor_a valor_b
##   <dbl> <chr>   <chr>  
## 1     1 a1      b1     
## 2     1 a1      b2     
## 3     1 a2      b1     
## 4     1 a2      b2

El hábito que evita el 90 % de estos problemas: después de cada unión, comprueba nrow(). Si cambió y no esperabas que cambiara, para y averigua por qué antes de seguir.


20.7 Definiendo las columnas clave

El argumento by tiene cuatro formas, y vale la pena conocerlas todas:

Forma Qué hace
by = NULL (o no ponerlo) Unión natural: usa todas las columnas con el mismo nombre en las dos tablas
by = "clave" Usa solo esa columna
by = c("a", "b") Usa las dos columnas a la vez
by = c("x" = "y") La columna se llama x en la primera tabla y y en la segunda

La unión natural (by = NULL) es cómoda para explorar y mala idea en un script que vas a guardar. vuelos y clima comparten anio, mes, dia, hora y origen, pero también podrían compartir mañana una columna nueva, y entonces la unión cambiaría de significado sin que tú tocaras nada. En un análisis serio, escribe siempre el by completo.


20.8 Otras implementaciones

  • merge()

merge() es la función de base R que hace lo mismo. Funciona, pero es más lenta, tiene los argumentos al revés de lo que uno espera, y no distingue con claridad las cuatro uniones:

dplyr merge() de base R
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 = TRUE)

Los nombres de dplyr dicen lo que hacen; los argumentos de merge() hay que recordarlos. Si te encuentras un merge() en código ajeno, esta tabla te dice cuál unión era.


20.9 Uniones de filtro

Las uniones de filtro (semi_join() y anti_join()) nunca añaden columnas nuevas: solo conservan o descartan filas de la tabla de la izquierda según coincidan con la de la derecha.

  • semi_join()
  • anti_join()

semi_join() y anti_join() (dplyr): filtrar una tabla usando otra.

  • semi_join(x, y): conserva las filas de x que tienen pareja en y.
  • anti_join(x, y): conserva las filas de x que no la tienen.
  • Ninguna de las dos añade columnas, y ninguna duplica filas aunque la clave esté repetida en y. Por eso son más seguras que un inner_join() cuando lo único que quieres es filtrar.

De las dos, la que más se usa en la práctica es anti_join(), y no para filtrar sino para diagnosticar: contesta la pregunta “¿qué hay en mi tabla que no encontró pareja?”. Es lo primero que hay que correr cuando un left_join() devuelve más NA de los esperados.

vuelos |> 
  anti_join(aviones, by = "codigo_cola") |> 
  count(aerolinea, sort = TRUE)
## # A tibble: 10 × 2
##    aerolinea     n
##    <chr>     <int>
##  1 MQ        25397
##  2 AA        22558
##  3 UA         1693
##  4 9E         1044
##  5 B6          830
##  6 US          699
##  7 FL          187
##  8 DL          110
##  9 F9           50
## 10 WN           38

Los vuelos que no encontraron su avión no están repartidos al azar entre las aerolíneas: se concentran en unas pocas. Eso no es un error de R, es un dato sobre cómo se recogió la información, y es justo el tipo de hallazgo que un inner_join() habría borrado en silencio.


20.10 Problemas con las uniones

Tres cosas salen mal casi siempre, y las tres se detectan antes de unir:

  1. La clave no es única. Comprueba las dos tablas con count(clave) |> filter(n > 1). Si la clave se repite en las dos, la unión va a multiplicar filas.
  2. La clave tiene valores faltantes. Un NA no empareja con nada, ni siquiera con otro NA. Búscalos con filter(is.na(clave)) antes de unir.
  3. La clave está escrita de dos maneras. Es el problema más común y el más invisible: "SJU " con un espacio al final no es "SJU", "Carolina" no es "CAROLINA", y un código leído como número perdió sus ceros de delante. anti_join() los saca a la luz enseguida.

La rutina completa, en cuatro líneas, antes de cualquier unión seria: comprobar la clave en la tabla A, comprobarla en la tabla B, correr el anti_join() en los dos sentidos para ver qué no empareja, y solo entonces unir. Cuesta un minuto y ahorra tardes enteras.


20.11 Ejercicios del libro R4DS

Hacer los ejercicios de las siguientes secciones del libro en español:

  • sección 13.2.1 (los datos de vuelos),
  • sección 13.3.1 (claves),
  • sección 13.4.6 (uniones de transformación),
  • sección 13.5.1 (uniones de filtro).

20.12 Ejercicios del capítulo

Sobre los ejercicios de este capítulo. Cada ejercicio se entrega con tres cosas: el script, el resultado (que el bloque corra) y una explicación en palabras. Los ejercicios usan millas y paises del paquete datos, más una tabla de consulta que vas a escribir tú, no vuelos ni sus tablas acompañantes, que son las del capítulo. La hoja completa para entregar está en Ejercicios/Ejercicios_Capitulo_20_datos-relacionados.Rmd.

20.12.1 Ejercicio 1. Encontrar la clave

Datos: paises. Averigua con count() y filter(n > 1) si pais por sí solo es una clave, y si la combinación de pais y anio lo es.

Verificación: la primera prueba devuelve 142 filas repetidas (una por país, con n igual a 12); la segunda devuelve una tabla vacía.

En palabras: una tabla vacía es la prueba superada. Explica por qué, y di cuál es entonces la clave primaria de paises.

# Escribe tu código aquí

20.12.2 Ejercicio 2. Tu propia tabla de consulta

Averigua cuántos fabricantes distintos hay en millas con n_distinct(). Después escribe con tribble() una tabla de consulta llamada origen_fabricante con dos columnas, fabricante y pais_origen, pero solo para la mitad de los fabricantes. Únela a millas con left_join().

Verificación: el resultado tiene las mismas 234 filas que millas, y una columna nueva con NA en las filas de los fabricantes que no pusiste.

En palabras: los NA no son un error aquí. ¿Qué te están diciendo? ¿Por qué left_join() es la unión correcta para añadir una tabla de consulta?

# Escribe tu código aquí

20.12.3 Ejercicio 3. left_join() frente a inner_join()

Repite la unión del ejercicio anterior con inner_join() y compara nrow() de los dos resultados.

Verificación: el inner_join() devuelve menos de 234 filas; el left_join() devuelve exactamente 234.

En palabras: el inner_join() no dio ningún error ni ninguna advertencia. Explica por qué eso lo hace más peligroso que un error, y cuál costumbre te protege.

# Escribe tu código aquí

20.12.4 Ejercicio 4. anti_join() para diagnosticar

Usa anti_join() para averiguar cuáles fabricantes de millas no están en tu tabla de consulta, y cuántos vehículos representan.

Verificación: la lista tiene que coincidir exactamente con los fabricantes que dejaste fuera a propósito, y el número de vehículos tiene que ser igual a la diferencia entre las dos uniones del Ejercicio 3.

En palabras: anti_join() no añade ninguna columna. ¿Para qué sirve entonces? Describe una situación de tu propio trabajo donde lo usarías.

# Escribe tu código aquí

20.12.5 Ejercicio 5. La clave duplicada

Añade a tu tabla de consulta una segunda fila para un fabricante que ya estaba, con otro país. Vuelve a hacer el left_join().

Verificación: el resultado tiene más de 234 filas, y los vehículos de ese fabricante aparecen duplicados.

En palabras: ¿cuántas filas de más salieron, y por qué exactamente esa cantidad? Si después de esta unión calcularas el consumo promedio por clase, ¿estaría mal? Explica en qué sentido.

# Escribe tu código aquí

20.12.6 Ejercicio 6. Cuando las columnas se llaman distinto

Cambia el nombre de la columna de tu tabla de consulta, de fabricante a marca. Intenta el left_join() sin tocar nada más, en un bloque con error=TRUE, y después arréglalo con la forma by = c("a" = "b").

Verificación: el intento sin arreglar da error; la versión arreglada da otra vez 234 filas.

En palabras: copia el mensaje de error y explica qué te estaba diciendo. En by = c("fabricante" = "marca"), ¿cuál de los dos nombres pertenece a cuál tabla?

# Escribe tu código aquí

20.12.7 Ejercicio 7 (BONO). semi_join() frente a %in%

Quédate con los vehículos de millas cuyo fabricante está en tu tabla de consulta, de dos maneras: con semi_join() y con filter(fabricante %in% ...).

Verificación: las dos dan exactamente la misma tabla, con el mismo número de filas y las mismas 11 columnas.

En palabras: si las dos dan lo mismo, ¿para qué existe semi_join()? Piensa en qué pasaría si la condición dependiera de dos columnas a la vez, y en qué pasaría si usaras inner_join() en lugar de semi_join().

# Escribe tu código aquí