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

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

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>

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


image.png


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


flights 与 planes 通过单变量 tailnum 相连;

flights 与 airlines 通过变量 carrier 相连;

flights 与 airports 通过两种方式相连(变量 origin 和 dest);

flights 与 weather 通过变量 origin(位置)以及 year、month、day 和 hour(时间)相连。


8.3 键


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


键的类型有两种。


**主键:**唯一标识其所在数据表中的观测。例如,planes$tailnum 是一个主键,因为其可以唯一标识 planes 表中的每架飞机。

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

一个变量既可以是主键,也可以是外键。例如,origin 是 weather 表主键的一部分,同时也是 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 合并连接


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


和 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() 函数组合 airlines 和 flights2 数据框:

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"
)


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


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


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

image.png


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 中的所有观测。

image.png

inner_join(x, y) #内连接
left_join(x, y) #左连接
right_join(x, y) #右连接
full_join(x, y) #全连接


8.4.4 重复键


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


一张表中具有重复键。通常来说,当存在一对多关系时,如果你想要向表中添加额外信息,就会出现这种情况。

image.png

两张表中都有重复键。这通常意味着出现了错误,因为键在任意一张表中都不能唯一标识观测。当连接这样的重复键时,你会得到所有可能的组合,即笛卡儿积:


image.png


8.4.5 定义键列


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


默认值 by = NULL。这会使用存在于两个表中的所有变量,这种方式称为自然连接。例如, 匹配航班表和天气表时使用的就是其公共变量:year、month、day、hour 和 origin。

> 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"。这种方式与自然连接很相似,但只使用某些公共变量。例如, flights 和 planes 表中都有 year 变量,但是它们的意义不同,因此我们只通过 tailnum 进行连接:

flights2 %>%
 left_join(planes, by = "tailnum")
#注意,结果中的 year 变量(同时存在于两个输入数据框中,但并不要求相等)添加了一个后缀,以消除歧义。


命名字符向量 by = c("a" = "b")。这种方式会匹配 x 表中的 a 变量和 y 表中的 b 变量。 输出结果中使用的是 x 表中的变量。


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

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


8.4.6 其他实现方式


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


image.png


8.5 筛选连接


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


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

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

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


image.png


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

image.png

反连接可以用于诊断连接中的不匹配。例如,在连接 flights 和 planes 时,你可能想知道 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


相关文章
|
7月前
|
数据采集 数据挖掘 BI
R数据科学-1(dplyr)
R数据科学-1(dplyr)
46 1
|
机器学习/深度学习 算法 数据挖掘
数据挖掘(Data Mining)| 数据分析建模理论基础
数据挖掘(Data Mining)| 数据分析建模理论基础
343 0
数据挖掘(Data Mining)| 数据分析建模理论基础
《R数据科学》学习笔记|Note5:使用dplyr进行数据转换(下)
本系列为《R数据科学》(R for Data Science)的学习笔记。相较于其他R语言教程来说,本书一个很大的优势就是直接从实用的R包出发,来熟悉R及数据科学。更新过程中,读者朋友如发现错误,欢迎指正。如果有疑问,也可以在评论区留言或后台私信。希望各位读者朋友能学有所得!
175 0
《R数据科学》学习笔记|Note5:使用dplyr进行数据转换(下)
|
大数据 数据处理
《R数据科学》学习笔记|Note6:使用tibble实现简单数据框
本系列为《R数据科学》(R for Data Science)的学习笔记。相较于其他R语言教程来说,本书一个很大的优势就是直接从实用的R包出发,来熟悉R及数据科学。更新过程中,读者朋友如发现错误,欢迎指正。如果有疑问,也可以在评论区留言或后台私信。希望各位读者朋友能学有所得!
559 0
《R数据科学》学习笔记|Note6:使用tibble实现简单数据框
|
数据可视化
《R数据科学》学习笔记|Note14:向量
《R数据科学》学习笔记|Note14:向量
278 0
《R数据科学》学习笔记|Note14:向量
|
资源调度 索引
《R数据科学》学习笔记|Note16:使用purrr实现迭代(下)
《R数据科学》学习笔记|Note16:使用purrr实现迭代(下)
131 0
《R数据科学》学习笔记|Note16:使用purrr实现迭代(下)
《R数据科学》学习笔记|Note9:使用stringr处理字符串(上)
本周内容较多且杂,故分成上下两篇。正则表达式是从左到右来匹配一个字符串的。“Regular Expression”这个词太长了,我们通常使用它的缩写“regex”或者“regexp”。 正则表达式可以被用来替换字符串中的文本、验证表单、基于模式匹配从一个字符串中提取字符串等等。
175 0
《R数据科学》学习笔记|Note9:使用stringr处理字符串(上)
《R数据科学》学习笔记|Note10:使用stringr处理字符串(下)
《R数据科学》学习笔记|Note10:使用stringr处理字符串(下)
238 0
《R数据科学》学习笔记|Note10:使用stringr处理字符串(下)
《R数据科学》学习笔记|Note11:使用forcats处理因子
《R数据科学》学习笔记|Note11:使用forcats处理因子
158 0
《R数据科学》学习笔记|Note11:使用forcats处理因子
《R数据科学》学习笔记|Note12:使用magrittr进行管道操作
《R数据科学》学习笔记|Note12:使用magrittr进行管道操作
179 0
《R数据科学》学习笔记|Note12:使用magrittr进行管道操作