《R for Data Science (2e)》免费中文翻译 (第19章) --- Joins(1)

写在前面

本系列推文为《R for Data Science (2)》的中文翻译版本。所有内容都通过开源免费的方式上传至Github,欢迎大家参与贡献,详细信息见:
Books-zh-cn 项目介绍:
Books-zh-cn:开源免费的中文书籍社区
r4ds-zh-cn Github 地址:
https://github.com/Books-zh-cn/r4ds-zh-cn
r4ds-zh-cn 网站地址:
https://books-zh-cn.github.io/r4ds-zh-cn/


目录

  • 19.1 介绍

  • 19.2 键

  • 19.3 基础连接

19.1 介绍

数据分析很少只涉及单个数据框。 通常你会拥有多个数据框,并且必须通过连接(join)将它们合并起来,以回答你感兴趣的问题。 本章将向你介绍两种重要的连接类型:

  • Mutating joins, 从另一个数据框的匹配观测中添加新变量到一个数据框。

  • Filtering joins, 根据观测是否与另一个数据框中的观测匹配,从一个数据框中筛选观测。

我们将首先讨论键------用于在连接中关联一对数据框的变量。 然后通过检查 nycflights13 包中数据集的键来巩固理论,并利用这些知识开始连接数据框。 接着我们将讨论连接的工作原理,重点关注它们对行的操作。 最后,我们将讨论非等值连接------一类提供比默认相等关系更灵活的键匹配方式的连接。

19.1.1 先决条件

在本章中,我们将使用 dplyr 的连接函数来探索 nycflights13 中的五个相关数据集。

复制代码
library(tidyverse)
library(nycflights13)

19.2 键

要理解连接,首先需要了解两个表如何通过各自内部的键对进行关联。 在本节中,你将学习两种类型的键,并查看 nycflights13 包数据集中两种键的示例。 你还将学习如何检查键是否有效,以及如果表缺少键该怎么办。

19.2.1 主键和外键

每次连接都涉及一对键:主键和外键。 主键是唯一标识每个观测的一个变量或一组变量。 当需要多个变量时,该键称为复合键。例 如,在 nycflights13 中:

  • airlines 记录了每家航空公司的两条信息:其承运人代码和全称。 你可以用其两个字母的承运人代码来识别航空公司,因此 carrier 是主键。

    复制代码
    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 记录了每个机场的数据。 你可以用其三个字母的机场代码来识别每个机场,因此 faa 是主键。

    复制代码
    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 记录了每架飞机的数据。 你可以用其尾号来识别飞机,因此 tailnum 是主键。

    复制代码
    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 记录了起飞机场的天气数据。 你可以通过地点和时间的组合来识别每个观测,因此 origin 和 time_hour 是复合主键。

    复制代码
    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>, ...

外键是另一个表中与主键对应的变量(或一组变量)。 例如:

  • flights$tailnum 是一个外键,对应主键 planes$tailnum.

  • flights$carrier 是一个外键,对应主键 airlines$carrier.

  • flights$origin 是一个外键,对应主键 airports$faa.

  • flights$dest 是一个外键,对应主键 airports$faa.

  • flights$origin-flights$time_hour 是一个复合外键,对应复合主键 weather$origin-weather$time_hour.

这些关系在 Figure 19.1 中进行了可视化总结。

Figure 19.1: nycflights13 包中所有五个数据帧之间的连接。构成主键的变量为灰色,并用箭头连接到相应的外键。

你会注意到这些键设计中的一个优点:主键和外键几乎总是具有相同的名称,这(正如你稍后将看到的)将使连接操作更加简便。 同样值得注意的是相反的关系:在多个表中使用的变量名几乎在每个地方都有相同的含义。 只有一个例外:year 在 flights 中指出发年份,在 planes 中指制造年份。 当我们真正开始连接表时,这一点将变得非常重要。

19.2.2 检查主键

现在我们已经识别出每个表中的主键,最好验证它们是否确实能唯一标识每个观测。 一种方法是使用 count() 对主键进行计数,并查找 n 大于 1 的记录。 这表明 planes 和 weather 的主键情况良好:

复制代码
planes |> 
  count(tailnum) |> 
  filter(n > 1)
#> # A tibble: 0 × 2
#> # ℹ 2 variables: tailnum <chr>, n <int>

weather |> 
  count(time_hour, origin) |> 
  filter(n > 1)
#> # A tibble: 0 × 3
#> # ℹ 3 variables: time_hour <dttm>, origin <chr>, n <int>

你还应检查主键中是否存在缺失值------如果某个值是缺失的,它就无法标识一个观测!

复制代码
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 代理键

到目前为止,我们还没有讨论 flights 的主键。 这里它并不是特别重要,因为没有其他数据框将其用作外键,但考虑它仍然有用,因为如果我们有某种方式向他人描述观测,处理它们会更容易。

经过一些思考和试验,我们确定有三个变量共同唯一标识每个航班:

复制代码
flights |> 
  count(time_hour, carrier, flight) |> 
  filter(n > 1)
#> # A tibble: 0 × 4
#> # ℹ 4 variables: time_hour <dttm>, carrier <chr>, flight <int>, n <int>

没有重复记录是否自动使 time_hour-carrier-flight 成为主键? 这当然是个好的起点,但并不能保证。 例如,海拔和纬度是 airports 的良好主键吗?

复制代码
airports |>
  count(alt, lat) |> 
  filter(n > 1)
#> # A tibble: 1 × 3
#>     alt   lat     n
#>   <dbl> <dbl> <int>
#> 1    13  40.6     2

显然,通过海拔和纬度来标识机场是个糟糕的主意,而且通常仅从数据本身无法判断一组变量能否构成一个好的主键。 但对于航班来说,time_hour, carrier 和 flight 的组合似乎是合理的,因为如果同一时间空中有多个相同航班号的航班,航空公司和乘客都会非常困惑。

也就是说,使用行号引入一个简单的数值代理键可能更好:

复制代码
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>, ...

代理键在与他人沟通时特别有用:告诉某人查看航班 2001,比说查看 2013年1月3日上午9点起飞的 UA430 航班要容易得多。

19.2.4 练习

  1. 我们在 Figure 19.1 中忘记了绘制 weather 和 airports 之间的关系。 它们的关系是什么?在 图中应该如何表示?

  2. weather 只包含纽约三个起飞机场的信息。 如果它包含美国所有机场的天气记录,那么它会与 flights 建立什么额外的连接?

  3. year, month, day, hour 和 origin 这些变量几乎构成了 weather 的复合键,但有一个小时存在重复观测。 你能找出那个小时有什么特殊之处吗?

  4. 我们知道一年中有些特殊日子(例如平安夜和圣诞节)的乘客数量比平时少。 你如何将这种数据表示为一个数据框? 主键会是什么? 它将如何与现有的数据框连接?

  5. 绘制一张图说明 Lahman 包中 Batting、People 和 Salaries 数据框之间的连接关系。 再绘制另一张图展示 People, Managers, AwardsManagers 之间的关系。 你如何描述 Batting, Pitching 和 Fielding 数据框之间的关系?

19.3 基础连接

现在你已经理解了数据框如何通过键连接,我们可以开始使用连接来更好地理解 flights 数据集。 dplyr 提供了六个连接函数:left_join(), inner_join(), right_join(), full_join(), semi_join() 和 anti_join()。它 们都有相同的接口:接收一对数据框(x 和 y)并返回一个数据框。 输出中的行和列顺序主要由 x 决定。

在本节中,你将学习如何使用一个变异连接 left_join() 以及两个筛选连接 semi_join() 和 anti_join()。 下一节你将详细了解这些函数的工作原理,以及剩余的 inner_join(), right_join() 和 full_join()。

19.3.1 变异连接

变异连接(mutating join )允许你合并两个数据框的变量:它首先通过键匹配观测,然后将变量从一个数据框复制到另一个。 与 mutate() 类似,连接函数会将变量添加到右侧,因此如果你的数据集有很多变量,你可能看不到新增的变量。 为了示例清晰,我们将创建一个仅包含六个变量的简化数据集:

复制代码
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

变异连接有四种类型,但有一种你几乎会一直使用:left_join()。 它的特殊之处在于输出始终与 x 保持相同的行数。left_join() 的主要用途是添加额外的元数据。 例如,我们可以使用 left_join() 将完整的航空公司名称添加到 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

或者我们可以找出每架飞机起飞时的温度和风速:

复制代码
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

或者飞机的大小;

复制代码
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>

当 left_join() 无法为 x 中的某行找到匹配项时,它会用缺失值填充新变量。 例如,没有关于尾号为 N3ALAA 的飞机的信息,因此 type, engines 和 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

本章后续部分我们会多次回到这个问题。

19.3.2 指定连接键

默认情况下,left_join() 会使用两个数据框中同时出现的所有变量作为连接键,即所谓的自然连接。 这是一种实用的启发式方法,但并不总是有效。 例如,如果我们尝试将 flights2 与完整的 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>, ...

由于我们的连接试图将 tailnum 和 year 作为复合键使用,导致大量匹配失败。flights 和 planes 都有 year 列,但含义不同:flights$year 是航班发生的年份,而 planes$year 是飞机的制造年份。 我们只想通过 tailnum 进行连接,因此需要使用 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>, ...

请注意,输出中的 year 变量通过后缀(year.x 和 year.y)进行了区分,这告诉你变量来自 x 还是 y 参数。 你可以使用 suffix 参数覆盖默认的后缀。

join_by(tailnum) 是 join_by(tailnum == tailnum) 的简写。 了解这种完整形式很重要,原因有二。 首先,它描述了两个表之间的关系:键必须相等。 这就是为什么这种连接类型通常被称为等值连接。 你将在 Section 19.5 中学习非等值连接。

其次,它允许你为每个表指定不同的连接键。 例如,连接 flight2 和 airports 表有两种方式:通过 dest 或 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>, ...

在旧代码中,你可能会看到另一种指定连接键的方式,即使用字符向量:

  • by = "x" 对应 join_by(x)。

  • by = c("a" = "x") 对应 join_by(a == x)。

既然现在有了 join_by(),我们更倾向于使用它,因为它提供了更清晰、更灵活的规范。

inner_join(), right_join(), full_join() 与 left_join() 具有相同的接口。 区别在于它们保留哪些行:左连接保留 x 中的所有行,右连接保留 y 中的所有行,全连接保留 x 或 y 中的所有行,而内连接只保留同时出现在x 和 y 中的行。 我们稍后会详细讨论这些。

19.3.3 过滤连接

正如你可能猜到的,筛选连接的主要功能是筛选行。 它有两种类型:半连接和反连接。 半连接(Semi-joins )保留 x 中所有在 y 中有匹配的行。 例如,我们可以使用半连接来筛选 airports 数据集,仅显示起飞机场:

复制代码
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

或仅显示目的地机场:

复制代码
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 )则相反:它们返回 x 中所有在 y 中没有匹配的行。 它们对于查找数据中隐式的缺失值非常有用,这是 Section 18.3 的主题。 隐式缺失值不会显示为 NAs,而仅表现为不存在。 例如,我们可以通过查找没有匹配目的机场的航班,来发现 airports 中缺失的行:

复制代码
flights2 |> 
  anti_join(airports, join_by(dest == faa)) |> 
  distinct(dest)
#> # A tibble: 4 × 1
#>   dest 
#>   <chr>
#> 1 BQN  
#> 2 SJU  
#> 3 STT  
#> 4 PSE

或者我们可以查找哪些 tailnums 在 planes 中缺失:

复制代码
flights2 |>
  anti_join(planes, join_by(tailnum)) |> 
  distinct(tailnum)
#> # A tibble: 722 × 1
#>   tailnum
#>   <chr>  
#> 1 N3ALAA 
#> 2 N3DUAA 
#> 3 N542MQ 
#> 4 N730MQ 
#> 5 N9EAMQ 
#> 6 N532UA 
#> # ℹ 716 more rows

19.3.4 练习

  1. 找出全年中最具延误的 48 小时。 与 weather 数据进行交叉比对。 你能发现任何规律吗?

  2. 假设你已使用以下代码找出前 10 个最受欢迎的目的地:

    复制代码
    top_dest <- flights2 |>
      count(dest, sort = TRUE) |>
      head(10)

    你如何查找所有飞往这些目的地的航班?

  3. 每个起飞的航班是否都有对应小时的天气数据?

  4. 在 planes 中没有匹配记录的尾号有何共同点? (提示:一个变量可解释 ~90% 的问题。)

  5. 在 planes 中添加一列,列出曾使用该飞机的每个 carrier(航空公司)。 你可能认为飞机与航空公司之间存在隐式关系,因为每架飞机只由一家航空公司运营。 请使用之前章节学到的工具验证或否定这一假设。

  6. 将起飞机场和目的地机场的经纬度添加到 flights 中。 更简便的做法是在连接前还是连接后重命名列?

  7. 计算目的地的平均延误时间,然后连接 airports 数据框,以便展示延误的空间分布。 以下是绘制美国地图的简单方法:

    复制代码
    airports |>
      semi_join(flights, join_by(faa == dest)) |>
      ggplot(aes(x = lon, y = lat)) +
        borders("state") +
        geom_point() +
        coord_quickmap()

    你可能希望用点的 size 或 color 来显示每个机场的平均延误。

  8. 2013 年 6 月 13 日发生了什么? 绘制延误分布地图,并使用谷歌搜索与天气数据进行交叉比对。

    worst <- filter(flights, !is.na(dep_time), month == 6, day == 13)
    worst |>
    group_by(dest) |>
    summarize(delay = mean(arr_delay), n = n()) |>
    filter(n > 5) |>
    inner_join(airports, by = c("dest" = "faa")) |>
    ggplot(aes(x = lon, y = lat)) +
    borders("state") +
    geom_point(aes(size = n, color = delay)) +
    coord_quickmap()


  1. 记住,在 RStudio 中,你也可以使用 View() 来避免这个问题。

  2. 这并不是百分百正确,但如果不是,你就会得到警告。

--------------- 未完待续 ---------------

本期翻译贡献:

  • @TigerZ生信宝库

注:本文已开启快捷转载,欢迎大家转载,只需标明文章出处即可。

相关推荐
梦想的初衷~5 小时前
MaxEnt物种分布建模完整教程|理论基础、数据获取、R清洗优化与论文制图全流程
r语言·生态遥感·物种分布模拟·maxent建模·r语言数据分析·arcgis实操·科研建模
skywalk81631 天前
光明语言入榜计划:GitHub Linguist 注册指南・光明语言模块参考
人工智能·编程·光明
白猫不黑2 天前
网络安全专业:从入门到进阶的完整学习路线
学习·安全·web安全·计算机·网络安全·信息安全·编程
codigger3 天前
从 "调模型" 到 "搭系统":Harness Engineering 凭什么成为 2026 年 AI 圈最热的新词
程序员·编程·#人工智能·#agent
青柠之夏cc3 天前
掌握自动化,高效工作新引擎
编程·脚本·语法
codigger4 天前
把 AI Agent 养在自己电脑上:从本地部署到远程接管的一份完整思路
ai·编程·#人工智能·#agent
Omics Pro4 天前
~30,000+引用!理论2005,R包2008,多组学集成AI增强
开发语言·数据库·人工智能·算法·机器学习·自然语言处理·r语言
codigger5 天前
Git 三区域模型:把 add、commit、reset、merge 一次讲透
git·github·编程·编程语言
天桥下的卖艺者6 天前
chrals.CTI函数升级版发布,增加了CTI衍生的脂肪指数提取
数据库·r语言
skywalk81636 天前
光明(Light)中文编程语言 · 宣传文档 版本:**v0.4.0-rc2*更新日期:2026-10-03
人工智能·编程·光明