R數(shù)據(jù)科學(xué)chapter9

#使用dplyr處理關(guān)系數(shù)據(jù)

#要想處理關(guān)系數(shù)據(jù),你需要能夠在兩張表之間進(jìn)行的操作。我們設(shè)計(jì)了三類操作來處理關(guān)系數(shù)據(jù)。

#? 合并連接:向數(shù)據(jù)框中加入新變量,新變量的值是另一個數(shù)據(jù)框中的匹配觀測。

#? 篩選連接:根據(jù)是否匹配另一個數(shù)據(jù)框中的觀測,篩選數(shù)據(jù)框中的觀測。

#? 集合操作:將觀測作為集合元素來處理。

#關(guān)系數(shù)據(jù)最常見于關(guān)系數(shù)據(jù)庫管理系統(tǒng)(relational database management system,RDBMS), 該系統(tǒng)幾乎囊括了所有的現(xiàn)代數(shù)據(jù)庫。

library(tidyverse)

library(nycflights13)

#nycflights13 中包含了與 flights 相關(guān)的 4 個tibble

#? airlines:可以根據(jù)航空公司的縮寫碼查到公司全名。

airlines

#? airports:給出了每個機(jī)場的信息,通過 faa 機(jī)場編碼進(jìn)行標(biāo)識。

airports

#? planes:給出了每架飛機(jī)的信息,通過 tailnum 進(jìn)行標(biāo)識。

planes

#? weather:給出了紐約機(jī)場每小時的天氣狀況。

weather

#展示不同數(shù)據(jù)表之間關(guān)系的一種方法是繪制圖形。

#記住每種關(guān)系只與兩張表有關(guān)。

#對于 nycflights13 包中的表來說:

#? flights 與 planes 通過單變量 tailnum 相連;

#? flights 與 airlines 通過變量 carrier 相連;

#? flights 與 airports 通過兩種方式相連(變量 origin 和 dest);

#? flights 與 weather 通過變量 origin(位置)以及 year、month、day 和 hour(時間)相連。

#鍵

#用于連接每對數(shù)據(jù)表的變量稱為鍵。

#鍵是能唯一標(biāo)識觀測的變量(或變量集合)。

#簡單情況下,單個變量就足以標(biāo)識一個觀測。

#例如,每架飛機(jī)都可以由 tailnum 唯一標(biāo)識。

#其他情況可能需要多個變量。

#例如,要想標(biāo)識 weather 中的觀測,你需要 5 個變量:year、 month、day、hour 和 origin。

#鍵的類型有兩種。

#? 主鍵:唯一標(biāo)識其所在數(shù)據(jù)表中的觀測。

#例如,planes$tailnum 是一個主鍵,因?yàn)槠淇梢晕ㄒ粯?biāo)識 planes 表中的每架飛機(jī)。

#? 外鍵:唯一標(biāo)識另一個數(shù)據(jù)表中的觀測。

#例如,flights$tailnum 是一個外鍵,因?yàn)槠涑霈F(xiàn)在 flights 表中,并可以將每次航班與唯一一架飛機(jī)匹配。

#一個變量既可以是主鍵,也可以是外鍵。

#例如,origin 是 weather 表主鍵的一部分,同時也是 airports 表的外鍵。

#一旦識別出表的主鍵,最好驗(yàn)證一下,看看它們能否真正唯一標(biāo)識每個觀測。

#一種驗(yàn)證方法是對主鍵進(jìn)行 count() 操作,然后查看是否有 n 大于 1 的記錄

planes %>%

? count(tailnum) %>%

? filter(n > 1)

weather %>%

? count(year, month, day, hour, origin) %>%

? filter(n > 1)

#有時數(shù)據(jù)表沒有明確的主鍵:每行都是一個觀測,但沒有一個變量組合能夠明確地標(biāo)識它。

#例如,flights 表中的主鍵是什么?你可能認(rèn)為是日期加航班號或者是日期加機(jī)尾編號,但這兩種組合都不是唯一標(biāo)識:

flights %>%

? count(year, month, day, flight) %>%

? filter(n > 1)

flights %>%

? count(year, month, day, tailnum) %>%

? filter(n > 1)

#如果一張表沒有 主鍵,有時就需要使用 mutate() 函數(shù)和 row_number() 函數(shù)為表加上一個主鍵。

#如果你完成了一些篩選工作,并想要使用原始數(shù)據(jù)檢查的話,就可以更容易地匹配觀測。 這種主鍵稱為代理鍵。

#主鍵與另一張表中與之對應(yīng)的外鍵可以構(gòu)成關(guān)系。

#關(guān)系通常是一對多的。例如,每個航班只有一架飛機(jī),但每架飛機(jī)可以飛多個航班。

#在另一些數(shù)據(jù)中,你有時還會遇到一對一的關(guān)系。

#你可以將這種關(guān)系看作一對多關(guān)系的特殊情況。

#你可以使用多對一關(guān)系加上一對多關(guān)系來構(gòu)造多對多關(guān)系。

#例如,在這份數(shù)據(jù)中,航空公司與機(jī)場之間存在著多對多關(guān)系:

#每個航空公司可以使用多個機(jī)場,每個機(jī)場可以服務(wù)多個航空公司。

#合并連接

#本節(jié)將介紹用于組合兩個表格的第一種工具,即合并連接。

#合并連接可以將兩個表格中的變量組合起來,它先通過兩個表格的鍵匹配觀測,然后將一個表格中的變量復(fù)制到另一個表格中。

#連接函數(shù)也會將變量添加在表格的右側(cè)

flights2 <- flights %>%

? select(year:day, hour, origin, dest, tailnum, carrier)

flights2

#假設(shè)想要將航空公司的全名加入 flights2 數(shù)據(jù)集,

#你可以通過 left_join() 函數(shù)組合airlines 和 flights2 數(shù)據(jù)框:

flights2 %>%

? select(-origin, -dest) %>%

? left_join(airlines, by = "carrier")

#將航空公司數(shù)據(jù)連接到 flights2 的結(jié)果產(chǎn)生了一個新變量:name。

#對于這個示例,我們可以通過 mutate() 函數(shù)和 R 的取子集操作達(dá)到同樣的效果:

flights2 %>%

? select(-origin, -dest) %>%

? mutate(name = airlines$name[match(carrier, airlines$carrier)])

#但這種方式很難推廣到需要匹配多個變量的情況

#理解連接

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 行、一行或多行的一種方法。

#匹配就是兩行之間的交集。

#內(nèi)連接

#內(nèi)連接是最簡單的一種連接。

#只要兩個觀測的鍵是相等的,內(nèi)連接就可以匹配它們。

#內(nèi)連接的結(jié)果是一個新數(shù)據(jù)框,其中包含鍵、x 值和 y 值。

#我們使用 by 參數(shù)告訴 dplyr 哪 個變量是鍵:

x %>%

? inner_join(y, by = "key")

#內(nèi)連接最重要的性質(zhì)是,沒有匹配的行不會包含在結(jié)果中。

#這意味著內(nèi)連接一般不適合在分析中使用,因?yàn)樘菀讈G失觀測了。

#外連接

#內(nèi)連接保留同時存在于兩個表中的觀測,

#外連接則保留至少存在于一個表中的觀測。

#外連接有 3 種類型。

#? 左連接:保留 x 中的所有觀測。left_join(x,y)

#? 右連接:保留 y 中的所有觀測.right_join(x,y)

#? 全連接:保留 x 和 y 中的所有觀測。full_join(x,y)

#這些連接會向每個表中添加額外的“虛擬”觀測,

#這個觀測擁有總是匹配的鍵(如果沒有其他鍵可匹配的話),

#其值則用 NA 來填充。

#最常用的連接是左連接:只要想從另一張表中添加數(shù)據(jù),就可以使用左連接,

#因?yàn)樗鼤A粼碇械乃杏^測,即使它沒有匹配。

#左連接應(yīng)該是你的默認(rèn)選擇,除非有足夠充分的 理由選擇其他的連接方式。

#重復(fù)鍵

#當(dāng)鍵不唯一時將會發(fā)生的兩種情況。

#? 一張表中具有重復(fù)鍵。

#通常來說,當(dāng)存在一對多關(guān)系時,如果你想要向表中添加額外信息,就會出現(xiàn)這種情況。

x <- tribble(

? ~key, ~val_x,

? 1, "x1",

? 2, "x2",

? 2, "x3",

? 1, "x4"

)

y <- tribble(

? ~key, ~val_y,

? 1, "y1",

? 2, "y2" )

left_join(x, y, by = "key")

#? 兩張表中都有重復(fù)鍵。

#這通常意味著出現(xiàn)了錯誤,因?yàn)殒I在任意一張表中都不能唯一標(biāo)識觀測。

#當(dāng)連接這樣的重復(fù)鍵時,你會得到所有可能的組合,即笛卡兒積

x <- tribble(

? ~key, ~val_x,

? 1, "x1",

? 2, "x2",

? 2, "x3",

? 3, "x4"

)

y <- tribble(

? ~key, ~val_y,

? 1, "y1",

? 2, "y2",

? 2, "y3",

? 3, "y4" )

left_join(x, y, by = "key")

#定義鍵列

#迄今為止,兩張表都是通過一個單變量來連接的,

#而且這個變量在兩張表中具有同樣的名稱。

#這種限制條件是通過by = "key"來實(shí)現(xiàn)的。

#? 默認(rèn)值by = NULL。這會使用存在于兩個表中的所有變量,這種方式稱為自然連接。

#匹配航班表和天氣表時使用的就是其公共變量:year、month、day、hour 和 origin。

flights2 %>%

? left_join(weather)

#? 字符向量by = "x"。這種方式與自然連接很相似,但只使用某些公共變量。

#例如, flights 和 planes 表中都有 year 變量,但是它們的意義不同,因此我們只通過 tailnum 進(jìn)行連接:

flights2 %>%

? left_join(planes, by = "tailnum")

#注意,結(jié)果中的 year 變量(同時存在于兩個輸入數(shù)據(jù)框中,但并不要求相等)添加了一個后綴,以消除歧義。

#? 命名字符向量by = c("a" = "b")。

#這種方式會匹配x表中的a變量和y表中的b變量。

#輸出結(jié)果中使用的是 x 表中的變量。

#因?yàn)槊看魏桨喽加衅瘘c(diǎn)機(jī)場和終點(diǎn)機(jī)場,所以需要指定使用哪個機(jī)場進(jìn)行連接:

flights2 %>%

? left_join(airports, c("dest" = "faa"))

flights2 %>%

? left_join(airports, c("origin" = "faa"))

#篩選連接

#篩選連接匹配觀測的方式與合并連接相同,但前者影響的是觀測,而不是變量。

#篩選連接有兩種類型。

#? semi_join(x, y):保留 x 表中與 y 表中的觀測相匹配的所有觀測。

#? anti_join(x, y):丟棄 x 表中與 y 表中的觀測相匹配的所有觀測。

#對數(shù)據(jù)表進(jìn)行篩選或摘要統(tǒng)計(jì)后,

#如果想要使用表中原來的行來匹配篩選或摘要結(jié)果,

#那么半連接是非常有用的。

#例如,假設(shè)你已經(jīng)找出了最受歡迎的前 10 個目的地:

top_dest <- flights %>% count(dest, sort = TRUE) %>% head(10)

top_dest

#現(xiàn)在想要找出飛往這些目的地的所有航班,你可以自己構(gòu)造一個篩選器:

flights %>%

? filter(dest %in% top_dest$dest)

#但這種方法很難擴(kuò)展到多個變量。例如,假設(shè)已經(jīng)找出了平均延誤時間最長的 10 天,那么你應(yīng)該如何使用 year、month 和 day 來構(gòu)造篩選語句,才能在 flights 中找出這 10 天的觀測?

#此時你應(yīng)該使用半連接,它可以像合并連接一樣連接兩個表,但不添加新列,而是保留 x 表中那些可以匹配 y 表的行:

flights %>%

? semi_join(top_dest)

#重要的是存在匹配,匹配了哪條觀測則無關(guān)緊要。

#這說明篩選連接不會像合并連接那樣造成重復(fù)的行。

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

#反連接可以用于診斷連接中的不匹配。例如,在連接 flights 和 planes 時,你可能想知道 flights 中是否有很多行在 planes 中沒有匹配記錄:

flights %>%

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

#連接中的問題

#(1) 首先,需要找出每個表中可以作為主鍵的變量。

#(2) 確保主鍵中的每個變量都沒有缺失值。如果有缺失值,那么這個變量就不能標(biāo)識觀測!

#(3) 檢查外鍵是否與另一張表的主鍵相匹配。

#最好的方法是使用 anti_join(),

#由于數(shù)據(jù)錄入錯誤,外鍵和主鍵不匹配的情況很常見。

#解決這種問題通常需要大量工作。

#如果鍵中確實(shí)有缺失值,那么你就要深思熟慮一下,是應(yīng)該使用內(nèi)連接還是外連接,

#此外,是否應(yīng)該丟棄那些沒有匹配記錄的行。

#注意,僅憑檢查連接前后的行數(shù)是不足以確保連接能夠順暢運(yùn)行的。

#集合操作

#兩表之間的最后一種操作就是集合操作。

#但如果你想要將一個復(fù)雜的篩選操作分解為多個簡單部分時,它們還是有些用處的。

#所有集合操作都是作用于整行的,比較的是每個變量的值。

#集合操作需要 x 和 y 具有相同的變量,并將觀測按照集合來處理。

#intersect(x, y)

#返回既在 x 表,又在 y 表中的觀測。

#union(x, y)

#返回 x 表或 y 表中的唯一觀測。

#setdiff(x, y)

#返回在 x 表,但不在 y 表中的觀測。

#給定以下簡單數(shù)據(jù):

? df1 <- tribble(

? ? ~x, ~y,

? ? 1, 1,

? ? 2, 1 )

df2 <- tribble(

? ~x, ~y,

? 1, 1,

? 1, 2 )

#4 種可能的集合操作為:

? intersect(df1, df2)

#> # A tibble: 1 × 2

#> x y

#> <dbl> <dbl>

#> 1 1 1

union(df1, df2)

#> # A tibble: 3 × 2

#> x y

#>? <dbl> <dbl>

#> 1? ? 1? ? 2

#> 2? ? 2? ? 1

#> 3? ? 1? ? 1

# 注意,我們得到了3行,而不是4行

setdiff(df1, df2)

#> # A tibble: 1 × 2 #> x y

#> <dbl> <dbl>

#> 1 2 1

setdiff(df2, df1)

#> # A tibble: 1 × 2 #> x y

#> <dbl> <dbl>

#> 1 1 2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容