《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:正規(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>

展示不同數據表之間關系的一種方法是繪制圖形。

1

這個圖有點讓人眼花繚亂,但跟實際工作中的一些圖比起來,可以說是相當簡潔了。理解這種圖的關鍵是,記住每種關系只與兩張表有關。不需要弄清楚所有的事情,只要明白你所關心的表格間的關系即可。 對于 nycflights13 包中的表來說:

  • flightsplanes 通過單變量 tailnum 相連;
  • flightsairlines 通過變量 carrier 相連;
  • flightsairports 通過兩種方式相連(變量 origindest);
  • flightsweather 通過變量 origin(位置)以及 year、monthdayhour(時間)相連。

8.3 鍵

用于連接每對數據表的變量稱為鍵。鍵是能唯一標識觀測的變量(或變量集合)。簡單情況下,單個變量就足以標識一個觀測。例如,每架飛機都可以由 tailnum 唯一標識。其 他情況可能需要多個變量。例如,要想標識 weather 中的觀測,你需要 5 個變量:yearmonth、dayhourorigin。

鍵的類型有兩種。

  • 主鍵:唯一標識其所在數據表中的觀測。例如,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 合并連接

本節(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() 函數組合 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 重復鍵

至今為止,所有圖都假設鍵具有唯一性。但情況并非總是如此。本節(jié)說明了當鍵不唯一時將會發(fā)生的兩種情況。

  • 一張表中具有重復鍵。通常來說,當存在一對多關系時,如果你想要向表中添加額外信息,就會出現這種情況。
    5
  • 兩張表中都有重復鍵。這通常意味著出現了錯誤,因為鍵在任意一張表中都不能唯一標識觀測。當連接這樣的重復鍵時,你會得到所有可能的組合,即笛卡兒積:

6

8.4.5 定義鍵列

迄今為止,兩張表都是通過一個單變量來連接的,而且這個變量在兩張表中具有同樣的名 稱。這種限制條件是通過 by = "key" 來實現的。你還可以對 by 設置其他值,以另外的方 式來連接表。

  • 默認值 by = NULL。這會使用存在于兩個表中的所有變量,這種方式稱為自然連接。例如, 匹配航班表和天氣表時使用的就是其公共變量:year、monthday、hourorigin。
> 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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容