《R数据科学》学习笔记|Note8:使用dplyr处理关系数据

使用dplyr处理关系数据

往期文章

《R数据科学》学习笔记|Note1:绪论
《R数据科学》学习笔记|Note2:使用ggplot2进行数据可视化(上)
《R数据科学》学习笔记|Note3:使用ggplot2进行数据可视化(下)
《R数据科学》学习笔记|Note4:使用dplyr进行数据转换(上)
《R数据科学》学习笔记|Note5:使用dplyr进行数据转换(下)
《R数据科学》学习笔记|Note6:使用tibble实现简单数据框
《R数据科学》学习笔记|Note7:使用readr进行数据导入
零基础"机器学习"自学笔记|Note6:正规方程及其推导(内附详细推导过程)

[TOC]

8.1 简介

只涉及一张数据表的数据分析是非常罕见的。存在于多个表中的这种数据统称为关系数据,因为重要的是数据间的关系,而不是单个数据集。 关系总是定义于两张表之间。其他所有关系都是建立在这种简单思想之上:三张或更多表之间的关系总是可以用每两个表之间关系表示出来。 要想处理关系数据,你需要能够在两张表之间进行的操作。

  • 合并连接:向数据框中加入新变量,新变量的值是另一个数据框中的匹配观测。
  • 筛选连接:根据是否匹配另一个数据框中的观测,筛选数据框中的观测。
  • 集合操作:将观测作为集合元素来处理。

关系数据最常见于关系数据库管理系统(relational database management system,RDBMS),该系统几乎囊括了所有的现代数据库。如果之前使用过数据库,那你肯定使用过 SQL。一般来说,dplyr 要比 SQL 更容易使用,因为前者是专门用于进行数据分析的。 在进行常用的数据分析操作时,dplyr 非常得心应手,反之,它并不擅长数据分析中不常用的那些操作。

8.2 nycflights13

我们将使用 nycflights13 包来学习关系数据。nycflights13 中包含了与 flights 相关的 4 个 tibble,我们已经在之前中使用过 flights 表了。

a'a'a'a
  • airports:给出了每个机场的信息,通过 faa 机场编码进行标识。
> airports
# A tibble: 1,458 x 8
   faa   name                     lat    lon   alt    tz dst   tzone        
   <chr> <chr>                  <dbl>  <dbl> <dbl> <dbl> <chr> <chr>        
 1 04G   Lansdowne Airport       41.1  -80.6  1044    -5 A     America/New_~
 2 06A   Moton Field Municipal~  32.5  -85.7   264    -6 A     America/Chic~
 3 06C   Schaumburg Regional     42.0  -88.1   801    -6 A     America/Chic~
 4 06N   Randall Airport         41.4  -74.4   523    -5 A     America/New_~
 5 09J   Jekyll Island Airport   31.1  -81.4    11    -5 A     America/New_~
 6 0A9   Elizabethton Municipa~  36.4  -82.2  1593    -5 A     America/New_~
 7 0G6   Williams County Airpo~  41.5  -84.5   730    -5 A     America/New_~
 8 0G7   Finger Lakes Regional~  42.9  -76.8   492    -5 A     America/New_~
 9 0P2   Shoestring Aviation A~  39.8  -76.6  1000    -5 U     America/New_~
10 0S9   Jefferson County Intl   48.1 -123.    108    -8 A     America/Los_~
# ... with 1,448 more rows
  • planes:给出了每架飞机的信息,通过 tailnum 进行标识。
> planes
# A tibble: 3,322 x 9
   tailnum  year type       manufacturer   model  engines seats speed engine
   <chr>   <int> <chr>      <chr>          <chr>    <int> <int> <int> <chr> 
 1 N10156   2004 Fixed win~ EMBRAER        EMB-1~       2    55    NA Turbo~
 2 N102UW   1998 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
 3 N103US   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
 4 N104UW   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
 5 N10575   2002 Fixed win~ EMBRAER        EMB-1~       2    55    NA Turbo~
 6 N105UW   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
 7 N107US   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
 8 N108UW   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
 9 N109UW   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
10 N110UW   1999 Fixed win~ AIRBUS INDUST~ A320-~       2   182    NA Turbo~
# ... with 3,312 more rows
  • weather:给出了纽约机场每小时的天气状况。
> weather
# A tibble: 26,115 x 15
   origin  year month   day  hour  temp  dewp humid wind_dir wind_speed
   <chr>  <int> <int> <int> <int> <dbl> <dbl> <dbl>    <dbl>      <dbl>
 1 EWR     2013     1     1     1  39.0  26.1  59.4      270      10.4 
 2 EWR     2013     1     1     2  39.0  27.0  61.6      250       8.06
 3 EWR     2013     1     1     3  39.0  28.0  64.4      240      11.5 
 4 EWR     2013     1     1     4  39.9  28.0  62.2      250      12.7 
 5 EWR     2013     1     1     5  39.0  28.0  64.4      260      12.7 
 6 EWR     2013     1     1     6  37.9  28.0  67.2      240      11.5 
 7 EWR     2013     1     1     7  39.0  28.0  64.4      240      15.0 
 8 EWR     2013     1     1     8  39.9  28.0  62.2      250      10.4 
 9 EWR     2013     1     1     9  39.9  28.0  62.2      260      15.0 
10 EWR     2013     1     1    10  41    28.0  59.6      260      13.8 
# ... with 26,105 more rows, and 5 more variables: wind_gust <dbl>,
#   precip <dbl>, pressure <dbl>, visib <dbl>, time_hour <dttm>

展示不同数据表之间关系的一种方法是绘制图形。

1

这个图有点让人眼花缭乱,但跟实际工作中的一些图比起来,可以说是相当简洁了。理解这种图的关键是,记住每种关系只与两张表有关。不需要弄清楚所有的事情,只要明白你所关心的表格间的关系即可。 对于 nycflights13 包中的表来说:

  • flightsplanes 通过单变量 tailnum 相连;
  • flightsairlines 通过变量 carrier 相连;
  • flightsairports 通过两种方式相连(变量 origindest);
  • flightsweather 通过变量 origin(位置)以及 yearmonthdayhour(时间)相连。

8.3 键

用于连接每对数据表的变量称为键。键是能唯一标识观测的变量(或变量集合)。简单情况下,单个变量就足以标识一个观测。例如,每架飞机都可以由 tailnum 唯一标识。其 他情况可能需要多个变量。例如,要想标识 weather 中的观测,你需要 5 个变量:yearmonthdayhourorigin

键的类型有两种。

  • 主键:唯一标识其所在数据表中的观测。例如,planes$tailnum 是一个主键,因为其可以唯一标识 planes 表中的每架飞机。
  • 外键:唯一标识另一个数据表中的观测。例如,flights$tailnum 是一个外键,因为其出现在 flights 表中,并可以将每次航班与唯一一架飞机匹配。

一个变量既可以是主键,也可以是外键。例如,originweather 表主键的一部分,同时也是 airports 表的外键。 一旦识别出表的主键,最好验证一下,看看它们能否真正唯一标识每个观测。一种验证方法是对主键进行 count() 操作,然后查看是否有 n 大于 1 的记录:

planes %>%
  count(tailnum) %>% #计算每个tialum的飞机个数
  filter(n > 1) #筛选出个数多于 1 的
> planes %>%
+   count(tailnum) %>%
+   filter(n > 1)
# A tibble: 0 x 2
# ... with 2 variables: tailnum <chr>, n <int>
weather %>%
 count(year, month, day, hour, origin) %>%
 filter(n > 1)
> weather %>%
+   count(year, month, day, hour, origin) %>%
+   filter(n > 1)
# A tibble: 3 x 6
   year month   day  hour origin     n
  <int> <int> <int> <int> <chr>  <int>
1  2013    11     3     1 EWR        2
2  2013    11     3     1 JFK        2
3  2013    11     3     1 LGA        2

有时数据表没有明确的主键:每行都是一个观测,但没有一个变量组合能够明确地标识它。例如,flights 表中的主键是什么?你可能认为是日期加航班号或者是日期加机尾编 号,但这两种组合都不是唯一标识。

如果一张表没有主键,有时就需要使用 mutate() 函数和 row_number() 函数为表加上一个主键。这样一来, 如果你完成了一些筛选工作,并想要使用原始数据检查的话,就可以更容易地匹配观测。 这种主键称为代理键

主键与另一张表中与之对应的外键可以构成关系。关系通常是一对多的。例如,每个航班 只有一架飞机,但每架飞机可以飞多个航班。在另一些数据中,你有时还会遇到一对一的 关系。你可以将这种关系看作一对多关系的特殊情况。你可以使用多对一关系加上一对多关系来构造多对多关系。例如,在这份数据中,航空公司与机场之间存在着多对多关系:每个航空公司可以使用多个机场,每个机场可以服务多个航空公司。

8.4 合并连接

本节将介绍用于<u>组合两个表格</u>的第一种工具,即合并连接。合并连接可以将两个表格中的变量组合起来,它先通过两个表格的键匹配观测,然后将一个表格中的变量复制到另一个 表格中。

mutate() 函数一样,连接函数也会将变量添加在表格的右侧,因此如果表格中已经有了很多变量,那么新变量就不会显示出来。为了解决这个问题,我们建立一个简化的数据集,以便更易看到示例数据集中发生的变化:

flights2 <- flights %>%
  select(year:day, hour, origin, dest, tailnum, carrier)
#如果报错则用下面这个代码
flights2 <- flights %>%
  dplyr::select(year:day, hour, origin, dest, tailnum, carrier)
'''
dplyr::select这是因为不同的包之间关于select方法有冲突。
library(conflicted) 可以清楚的告诉你是哪些包之间有冲突
你可以用包的名字::方法名字来解决这个问题如:dplyr::select
'''
flights2
> flights2
# A tibble: 336,776 x 8
    year month   day  hour origin dest  tailnum carrier
   <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>  
 1  2013     1     1     5 EWR    IAH   N14228  UA     
 2  2013     1     1     5 LGA    IAH   N24211  UA     
 3  2013     1     1     5 JFK    MIA   N619AA  AA     
 4  2013     1     1     5 JFK    BQN   N804JB  B6     
 5  2013     1     1     6 LGA    ATL   N668DN  DL     
 6  2013     1     1     5 EWR    ORD   N39463  UA     
 7  2013     1     1     6 EWR    FLL   N516JB  B6     
 8  2013     1     1     6 LGA    IAD   N829AS  EV     
 9  2013     1     1     6 JFK    MCO   N593JB  B6     
10  2013     1     1     6 LGA    ORD   N3ALAA  AA     
# ... with 336,766 more rows

假设想要将航空公司的全名加入 flights2 数据集,可以通过 left_join() 函数组合 airlinesflights2 数据框:

flights2 <- flights %>%
  dplyr::select(year:day, hour, origin, dest, tailnum, carrier)
> flights2 %>%
+   dplyr::select(-origin, -dest) %>%
+   left_join(airlines, by = "carrier")#通过carrier匹配
# A tibble: 336,776 x 7
    year month   day  hour tailnum carrier name                    
   <int> <int> <int> <dbl> <chr>   <chr>   <chr>                   
 1  2013     1     1     5 N14228  UA      United Air Lines Inc.   
 2  2013     1     1     5 N24211  UA      United Air Lines Inc.   
 3  2013     1     1     5 N619AA  AA      American Airlines Inc.  
 4  2013     1     1     5 N804JB  B6      JetBlue Airways         
 5  2013     1     1     6 N668DN  DL      Delta Air Lines Inc.    
 6  2013     1     1     5 N39463  UA      United Air Lines Inc.   
 7  2013     1     1     6 N516JB  B6      JetBlue Airways         
 8  2013     1     1     6 N829AS  EV      ExpressJet Airlines Inc.
 9  2013     1     1     6 N593JB  B6      JetBlue Airways         
10  2013     1     1     6 N3ALAA  AA      American Airlines Inc.  
# ... with 336,766 more rows

将航空公司数据连接到 flights2 的结果产生了一个新变量:name。这就是我们将这种连接称为合并连接的原因。对于这个示例,我们可以通过 mutate() 函数和 R 的取子集操作达到同样的效果:

flights2 %>%
  dplyr::select(-origin, -dest) %>%
  mutate(name = airlines$name[match(carrier, airlines$carrier)])

但这种方式很难推广到需要匹配多个变量的情况,而且需要仔细阅读代码才能搞清楚操作目的。

8.4.1 理解连接

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

<img src="https://gitee.com/mzbj/blog-image/raw/master/img/2.png" alt="2" style="zoom:150%;" />

有颜色的列表示作为“键”的变量:它们用于在表间匹配行。灰色列表示“值”列,是与键对应的值。在以下的示例中,虽然键和值都是一个变量,但非常容易推广到多个键变量和多个值变量的情况。

连接是将 x 中每行连接到 y 中 0 行、一行或多行的一种方法。下图表示出了所有可能的匹配,匹配就是两行之间的交集。

匹配在实际的连接操作中是用圆点表示的。圆点的数量 = 匹配的数量 = 结果中行的数量

3

8.4.2 内连接

内连接是最简单的一种连接。只要两个观测的键是相等的,内连接就可以匹配它们。

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

8.4.3 外连接

内连接保留同时存在于两个表中的观测,外连接则保留至少存在于一个表中的观测。外连 接有 3 种类型。

  • 左连接:保留 x 中的所有观测。
  • 右连接:保留 y 中的所有观测 。
  • 全连接:保留 x 和 y 中的所有观测。
4
inner_join(x, y) #内连接
left_join(x, y) #左连接
right_join(x, y) #右连接
full_join(x, y) #全连接

8.4.4 重复键

至今为止,所有图都假设键具有唯一性。但情况并非总是如此。本节说明了当键不唯一时将会发生的两种情况。

  • 一张表中具有重复键。通常来说,当存在一对多关系时,如果你想要向表中添加额外信息,就会出现这种情况。
    5
  • 两张表中都有重复键。这通常意味着出现了错误,因为键在任意一张表中都不能唯一标识观测。当连接这样的重复键时,你会得到所有可能的组合,即笛卡儿积:

6

8.4.5 定义键列

迄今为止,两张表都是通过一个单变量来连接的,而且这个变量在两张表中具有同样的名 称。这种限制条件是通过 by = "key" 来实现的。你还可以对 by 设置其他值,以另外的方 式来连接表。

  • 默认值 by = NULL。这会使用存在于两个表中的所有变量,这种方式称为自然连接。例如, 匹配航班表和天气表时使用的就是其公共变量:yearmonthdayhourorigin
> flights2 %>%
+   left_join(weather)
Joining, by = c("year", "month", "day", "hour", "origin")
# A tibble: 336,776 x 18
    year month   day  hour origin dest  tailnum carrier  temp  dewp humid
   <int> <int> <int> <dbl> <chr>  <chr> <chr>   <chr>   <dbl> <dbl> <dbl>
 1  2013     1     1     5 EWR    IAH   N14228  UA       39.0  28.0  64.4
 2  2013     1     1     5 LGA    IAH   N24211  UA       39.9  25.0  54.8
 3  2013     1     1     5 JFK    MIA   N619AA  AA       39.0  27.0  61.6
 4  2013     1     1     5 JFK    BQN   N804JB  B6       39.0  27.0  61.6
 5  2013     1     1     6 LGA    ATL   N668DN  DL       39.9  25.0  54.8
 6  2013     1     1     5 EWR    ORD   N39463  UA       39.0  28.0  64.4
 7  2013     1     1     6 EWR    FLL   N516JB  B6       37.9  28.0  67.2
 8  2013     1     1     6 LGA    IAD   N829AS  EV       39.9  25.0  54.8
 9  2013     1     1     6 JFK    MCO   N593JB  B6       37.9  27.0  64.3
10  2013     1     1     6 LGA    ORD   N3ALAA  AA       39.9  25.0  54.8
# ... with 336,766 more rows, and 7 more variables: wind_dir <dbl>,
#   wind_speed <dbl>, wind_gust <dbl>, precip <dbl>, pressure <dbl>,
#   visib <dbl>, time_hour <dttm>
  • 字符向量 by = "x"。这种方式与自然连接很相似,但只使用某些公共变量。例如, flightsplanes 表中都有 year 变量,但是它们的意义不同,因此我们只通过 tailnum 进行连接:
flights2 %>%
 left_join(planes, by = "tailnum")
#注意,结果中的 year 变量(同时存在于两个输入数据框中,但并不要求相等)添加了一个后缀,以消除歧义。
  • 命名字符向量 by = c("a" = "b")。这种方式会匹配 x 表中的 a 变量和 y 表中的 b 变量。 输出结果中使用的是 x 表中的变量。

    例如,如果想要画出一幅地图,那么我们就需要在航班数据中加入机场数据,后者包含了每个机场的位置(latlon)。因为每次航班都有起点机场和终点机场,所以需要指定使用哪个机场进行连接:

flights2 %>%
 left_join(airports, c("dest" = "faa"))
 
flights2 %>%
 left_join(airports, c("origin" = "faa"))

8.4.6 其他实现方式

base::merge() 函数可以实现所有 4 种合并连接操作。

7

8.5 筛选连接

筛选连接匹配观测的方式与合并连接相同,但前者影响的是观测,而不是变量。筛选连接有两种类型。

  • semi_join(x, y)(半连接):保留 x 表中与 y 表中的观测相匹配的所有观测。
  • anti_join(x, y)(反连接):丢弃 x 表中与 y 表中的观测相匹配的所有观测。

半连接的图形表示如下所示。

8

半连接的逆操作是反连接。反连接保留 x 表中那些没有匹配 y 表的行。
9

反连接可以用于诊断连接中的不匹配。例如,在连接 flightsplanes 时,你可能想知道 flights 中是否有很多行在 planes 中没有匹配记录:

flights %>%
 anti_join(planes, by = "tailnum") %>%
 count(tailnum, sort = TRUE)

8.6 连接中的问题

为了在使用自己的数据时可以顺畅地进行各种连接,你需 要注意以下几点。

  • 首先,需要找出每个表中可以作为主键的变量。
  • 确保主键中的每个变量都没有缺失值。
  • 检查外键是否与另一张表的主键相匹配。

8.7 集合操作

两表之间的最后一种操作就是集合操作。通常很少使用这种操作,但如果要将一个复杂的筛选操作分解为多个简单部分时,它们还是有些用处的。所有集合操作都是作用于整行 的,比较的是每个变量的值。集合操作需要 x 和 y 具有相同的变量,并将观测按照集合来处理。

  • intersect(x, y) 返回既在 x 表,又在 y 表中的观测。
  • union(x, y) 返回 x 表或 y 表中的唯一观测
  • setdiff(x, y) 返回在 x 表,但不在 y 表中的观测。
df1 <- tribble(
 ~x, ~y,
 1, 1,
 2, 1
)
df2 <- tribble(
 ~x, ~y,
 1, 1,
 1, 2
)
# 4种可能的集合操作为:
intersect(df1, df2)
> intersect(df1, df2)
# A tibble: 1 x 2
      x     y
  <dbl> <dbl>
1     1     1

union(df1, df2)
> union(df1, df2)
# A tibble: 3 x 2
      x     y
  <dbl> <dbl>
1     1     1
2     2     1
3     1     2

setdiff(df1, df2)
> setdiff(df1, df2)
# A tibble: 1 x 2
      x     y
  <dbl> <dbl>
1     2     1

setdiff(df2, df1)
> setdiff(df2, df1)
# A tibble: 1 x 2
      x     y
  <dbl> <dbl>
1     1     2
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容