使用dplyr處理關系數據
往期文章
《R數據科學》學習筆記|Note1:緒論
《R數據科學》學習筆記|Note2:使用ggplot2進行數據可視化(上)
《R數據科學》學習筆記|Note3:使用ggplot2進行數據可視化(下)
《R數據科學》學習筆記|Note4:使用dplyr進行數據轉換(上)
《R數據科學》學習筆記|Note5:使用dplyr進行數據轉換(下)
《R數據科學》學習筆記|Note6:使用tibble實現簡單數據框
《R數據科學》學習筆記|Note7:使用readr進行數據導入
零基礎"機器學習"自學筆記|Note6:正規(guī)方程及其推導(內附詳細推導過程)
[TOC]
8.1 簡介
只涉及一張數據表的數據分析是非常罕見的。存在于多個表中的這種數據統(tǒng)稱為關系數據,因為重要的是數據間的關系,而不是單個數據集。 關系總是定義于兩張表之間。其他所有關系都是建立在這種簡單思想之上:三張或更多表之間的關系總是可以用每兩個表之間關系表示出來。 要想處理關系數據,你需要能夠在兩張表之間進行的操作。
- 合并連接:向數據框中加入新變量,新變量的值是另一個數據框中的匹配觀測。
- 篩選連接:根據是否匹配另一個數據框中的觀測,篩選數據框中的觀測。
- 集合操作:將觀測作為集合元素來處理。
關系數據最常見于關系數據庫管理系統(tǒng)(relational database management system,RDBMS),該系統(tǒng)幾乎囊括了所有的現代數據庫。如果之前使用過數據庫,那你肯定使用過 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>
展示不同數據表之間關系的一種方法是繪制圖形。

這個圖有點讓人眼花繚亂,但跟實際工作中的一些圖比起來,可以說是相當簡潔了。理解這種圖的關鍵是,記住每種關系只與兩張表有關。不需要弄清楚所有的事情,只要明白你所關心的表格間的關系即可。 對于 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 合并連接
本節(jié)將介紹用于<u>組合兩個表格</u>的第一種工具,即合并連接。合并連接可以將兩個表格中的變量組合起來,它先通過兩個表格的鍵匹配觀測,然后將一個表格中的變量復制到另一個 表格中。
和 mutate() 函數一樣,連接函數也會將變量添加在表格的右側,因此如果表格中已經有了很多變量,那么新變量就不會顯示出來。為了解決這個問題,我們建立一個簡化的數據集,以便更易看到示例數據集中發(fā)生的變化:
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"
)
<img src="https://gitee.com/mzbj/blog-image/raw/master/img/2.png" alt="2" style="zoom:150%;" />
有顏色的列表示作為“鍵”的變量:它們用于在表間匹配行。灰色列表示“值”列,是與鍵對應的值。在以下的示例中,雖然鍵和值都是一個變量,但非常容易推廣到多個鍵變量和多個值變量的情況。
連接是將 x 中每行連接到 y 中 0 行、一行或多行的一種方法。下圖表示出了所有可能的匹配,匹配就是兩行之間的交集。
匹配在實際的連接操作中是用圓點表示的。圓點的數量 = 匹配的數量 = 結果中行的數量。

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 中的所有觀測。

inner_join(x, y) #內連接
left_join(x, y) #左連接
right_join(x, y) #右連接
full_join(x, y) #全連接
8.4.4 重復鍵
至今為止,所有圖都假設鍵具有唯一性。但情況并非總是如此。本節(jié)說明了當鍵不唯一時將會發(fā)生的兩種情況。
-
一張表中具有重復鍵。通常來說,當存在一對多關系時,如果你想要向表中添加額外信息,就會出現這種情況。5
兩張表中都有重復鍵。這通常意味著出現了錯誤,因為鍵在任意一張表中都不能唯一標識觀測。當連接這樣的重復鍵時,你會得到所有可能的組合,即笛卡兒積:

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 種合并連接操作。

8.5 篩選連接
篩選連接匹配觀測的方式與合并連接相同,但前者影響的是觀測,而不是變量。篩選連接有兩種類型。
-
semi_join(x, y)(半連接):保留 x 表中與 y 表中的觀測相匹配的所有觀測。 -
anti_join(x, y)(反連接):丟棄 x 表中與 y 表中的觀測相匹配的所有觀測。
半連接的圖形表示如下所示。


反連接可以用于診斷連接中的不匹配。例如,在連接 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
