關(guān)于數(shù)據(jù)操作的另一個(gè)流行的包是dplyr腮敌,它發(fā)明了一種數(shù)據(jù)操作語(yǔ)法黔牵。dplyr擴(kuò)展包并沒(méi)有使用構(gòu)建子集函數(shù)([ ])米者,而是定義了一系列基礎(chǔ)的變形函數(shù)作為數(shù)據(jù)操作模塊德召,并且引入了一個(gè)管道操作符辑舷,利用管道操作符將這些變形函數(shù)串聯(lián)起來(lái)喻犁,進(jìn)而完成復(fù)雜的多步任務(wù)。
如果還沒(méi)有安裝dplyr何缓,請(qǐng)運(yùn)行以下代碼以從CRAN中安裝:
install.packages("dplyr")
首先肢础,我們重新加載產(chǎn)品表格,將它們重置為原始形式:
library(readr)
product_info <- read_csv("data/product-info.csv")
product_stats <- read_csv("data/product-stats.csv")
product_tests <- read_csv("data/product-tests.csv")
toy_tests <- read_csv("data/product-toy-tests.csv")
然后碌廓,載入dplyr包:
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:data.table':
#
## between, last
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
以上輸出信息說(shuō)明dplyr泛化了很多內(nèi)置函數(shù)传轰。加載這個(gè)包之后,這些內(nèi)置函數(shù)便被屏蔽了谷婆。
現(xiàn)在慨蛙,我們可以使用dplyr包提供的變形函數(shù)了辽聊。先使用select( ) 函數(shù)從數(shù)據(jù)框中提取列,并將這些列存儲(chǔ)在新創(chuàng)建的表中:
select(product_info, id, name, type, class)
## Source:local data frame[6x4]
##
## id name type class
## <chr> <chr> <chr> <chr>
## 1 T01 SupCar toy vehicle
## 2 T02 SupPlane toy vehicle
## 3 M01 JeepX model vehicle
## 4 M02 AircraftX model vehicle
## 5 M03 Runner model people
## 6 M04 Dancer model people
打印出來(lái)的表格與data.frame和data.table都不太一樣期贫。它不僅顯示了表格本身跟匆,也包括一個(gè)表頭,用于說(shuō)明數(shù)據(jù)框的大小和每一列的數(shù)據(jù)類(lèi)型通砍。
顯然玛臂,select( ) 使用了非標(biāo)準(zhǔn)計(jì)算,所以我們可以直接將數(shù)據(jù)框的列名作為參數(shù)封孙。它和subset( )迹冤、transform( ) 以及with( ) 的工作方式類(lèi)似。
其次敛瓷,我們可以使用filter( ) 函數(shù)叁巨,通過(guò)邏輯條件篩選數(shù)據(jù)框。同樣地呐籽,這個(gè)函數(shù)也是在數(shù)據(jù)框的語(yǔ)義中被計(jì)算:
filter(product_info, released == "yes")
## Source:local data frame[4x5]
##
## id name type class released
## <chr> <chr> <chr> <chr> <chr>
## 1 T01 SupCar toy vehicle yes
## 2 M01 JeepX model vehicle yes
## 3 M02 AircraftX model vehicle yes
## 4 M03 Runner model people yes
如果想要根據(jù)多個(gè)條件篩選記錄锋勺,只需要把每個(gè)條件都作為filter() 的參數(shù):
filter(product_info,
released == "yes", type == "model")
## Source:local data frame[3x5]
##
## id name type class released
## <chr> <chr> <chr> <chr> <chr>
## 1 M01 JeepX model vehicle yes
## 2 M02 AircraftX model vehicle yes
## 3 M03 Runner model people yes
mutate( )函數(shù)可以創(chuàng)建一個(gè)新的數(shù)據(jù)框,這個(gè)數(shù)據(jù)框包含新列狡蝶,或者替換原數(shù)據(jù)框的列庶橱。它與transform( )類(lèi)似,不同的是贪惹,如果數(shù)據(jù)是data.table苏章,它也能支持原地賦值:=:
mutate(product_stats, density = size / weight)
## Source: local data frame [6 x 5]
##
## id material size weight density
## <chr> <chr> <int> <dbl> <dbl>
## 1 T01 Metal 120 10.0 12.000000
## 2 T02 Metal 350 45.0 7.777778
## 3 M01 Plastics 50 NA NA
## 4 M02 Plastics 85 3.0 28.333333
## 5 M03 Wood 15 NA NA
## 6 M04 Wood 16 0.6 26.666667
arrange( ) 函數(shù)也是用于創(chuàng)建一個(gè)新的數(shù)據(jù)框,這個(gè)數(shù)據(jù)框是按一個(gè)或多個(gè)列排序后的奏瞬。desc( ) 函數(shù)表示降序排列:
arrange(product_stats, material, desc(size), desc(weight))
## Source: local data frame [6 x 4]
##
## id material size weight
## <chr> <chr> <int> <dbl>
## 1 T02 Metal 350 45.0
## 2 T01 Metal 120 10.0
## 3 M02 Plastics 85 3.0
## 4 M01 Plastics 50 NA
## 5 M04 Wood 16 0.6
## 6 M03 Wood 15 NA
dplyr包提供了豐富的連接函數(shù)枫绅,包括inner_join( )、left_join( )硼端、right_join( )并淋、full_join( )、semi_join( ) 和anti_join( )珍昨。如果要連接的兩個(gè)表存在無(wú)法匹配的記錄县耽,這些連接操作的行為會(huì)有很大差別。對(duì)于product_info和product_tests镣典,它們的記錄可以完全匹配兔毙,所以left_join( ) 的返回結(jié)果和merge( ) 相同:
product_info_tests <- left_join(product_info, product_tests, by = "id")
product_info_tests
## Source: local data frame [6 x 8]
##
## id name type class released quality durability
## <chr> <chr> <chr> <chr> <chr> <int> <int>
## 1 T01 SupCar toy vehicle yes NA 10
## 2 T02 SupPlane toy vehicle no 10 9
## 3 M01 JeepX model vehicle yes 6 4
## 4 M02 AircraftX model vehicle yes 6 5
## 5 M03 Runner model people yes 5 NA
## 6 M04 Dancer model people no 6 6
## Variables not shown: waterproof (chr)
運(yùn)行?dplyr::join了解這些連接操作的更多差異兄春。
為了對(duì)數(shù)據(jù)進(jìn)行分組匯總澎剥,我們需要先利用group_by( ) 創(chuàng)建一個(gè)分組后的表格。然后使用summarize( ) 匯總數(shù)據(jù)赶舆。例如哑姚,我們想把product_info_tests按照type和class分割開(kāi)趾唱,然后對(duì)每一組計(jì)算quality和durability的平均值:
summarize(group_by(product_info_tests, type, class),
mean_quality = mean(quality, na.rm = TRUE),
mean_durability = mean(durability, na.rm = TRUE))
## Source: local data frame [3 x 4]
## Groups: type [? ]
##
## type class mean_quality mean_durability
## <chr> <chr> <dbl> <dbl>
## 1 model people 5.5 6.0
## 2 model vehicle 6.0 4.5
## 3 toy vehicle 10.0 9.5
通過(guò)前面的代碼示例,我們掌握了這些變形函數(shù): select( )蜻懦、filter( )甜癞、mutate( )、arrange( )宛乃、group_by( ) 和summarize( )悠咱。這些函數(shù)的設(shè)計(jì)初衷都是對(duì)數(shù)據(jù)進(jìn)行一個(gè)小操作,但是將它們合理地組合到一起征炼,就可以完成復(fù)雜的數(shù)據(jù)處理操作析既。除了這些函數(shù),dplyr包還從magrittr包中引入了管道操作符 %>%谆奥,利用 %>% 將函數(shù)連接起來(lái)眼坏,組合使用。
假設(shè)現(xiàn)在有product_info和product_tests酸些。我們需要對(duì)已發(fā)布的產(chǎn)品進(jìn)行分析宰译,對(duì)于每種類(lèi)型和類(lèi)對(duì)應(yīng)的組,計(jì)算該組產(chǎn)品的質(zhì)量和耐久性的平均值魄懂,并將結(jié)果數(shù)據(jù)按照質(zhì)量均值降序排列沿侈。通過(guò)使用管道操作符將dplyr變形函數(shù)連接起來(lái),可以漂亮地完成這個(gè)任務(wù):
product_info %>% filter(released == "yes") %>%
inner_join(product_tests, by = "id") %>%
group_by(type, class) %>%
summarize(
mean_quality = mean(quality, na.rm = TRUE),
mean_durability = mean(durability, na.rm = TRUE)) %>%
arrange(desc(mean_quality))
## Source: local data frame [3 x 4]
## Groups: type [2]
##
## type class mean_quality mean_durability
## <chr> <chr> <dbl> <dbl>
## 1 model vehicle 6 4.5
## 2 model people 5 NaN
## 3 toy vehicle NaN 10.0
但是 %>% 是如何工作的呢市栗?其實(shí)缀拭,管道操作符基本上只負(fù)責(zé)一件事情:把符號(hào)左側(cè)返回的結(jié)果,作為符號(hào)右側(cè)調(diào)用函數(shù)的第1個(gè)參數(shù)填帽。也就是說(shuō)蛛淋,x %>% f(...) 等價(jià)于f(x, ...)。因?yàn)?%>% 是一個(gè)由包定義的二元操作符篡腌,所以允許我們將函數(shù)調(diào)用連接起來(lái)褐荷,一方面避免存儲(chǔ)多余的中間值,另一方面將嵌套調(diào)用分解哀蘑,使每一步操作流程清晰地展現(xiàn)出來(lái)诚卸。
假設(shè)將d0轉(zhuǎn)化為d3需要3個(gè)步驟葵第。在每一步的函數(shù)調(diào)用中绘迁,需要將前面一步的結(jié)果作為參數(shù)。如果像這樣操作數(shù)據(jù)卒密,可能會(huì)有很多中間結(jié)果缀台,當(dāng)數(shù)據(jù)量很大的時(shí)候,會(huì)消耗很多內(nèi)存:
d1 <- f1(d0, arg1)
d2 <- f2(d1, arg2)
d3 <- f3(d2, arg3)
想要避免中間結(jié)果哮奇,就不得不寫(xiě)嵌套調(diào)用膛腐。這個(gè)任務(wù)看起來(lái)一點(diǎn)都不友好睛约,特別是在每個(gè)函數(shù)調(diào)用都有多個(gè)參數(shù)的時(shí)候:
f3(f2(f1(d0, arg1), arg2), arg3)
使用管道操作符,工作流便可以像下面這樣重新組織:
d0 %>% f1(arg1) %>% f2(arg2) %>% f3(arg3)
這樣的代碼看起來(lái)更加簡(jiǎn)潔和直觀哲身。整個(gè)表達(dá)式不止看起來(lái)像一個(gè)管道辩涝,其工作方式也像一個(gè)管道。d0 %>% f1(arg1) 等價(jià)于f1(d0, arg1)勘天,并會(huì)被送往f2(., arg2)怔揩,緊接著又會(huì)被送往f3(., arg3)。每一步的輸出結(jié)果都會(huì)成為下一步的輸入脯丝。
而且商膊,管道操作符不止在dplyr的函數(shù)中生效,對(duì)其他所有的函數(shù)也都是適用的宠进。假設(shè)我們想要對(duì)鉆石價(jià)格畫(huà)一個(gè)密度圖晕拆,如圖所示。
data(diamonds, package = "ggplot2")
plot(density(diamonds$price, from = 0),
main = "Density plot of diamond prices")
使用管道操作符材蹬,我們可以像這樣重寫(xiě)代碼:
diamonds$price %>%
density(from = 0) %>%
plot(main = "Density plot of diamonds prices")
與data.table類(lèi)似实幕,dplyr也提供了do( ) 函數(shù)來(lái)對(duì)每組數(shù)據(jù)進(jìn)行任意操作。例如堤器,將diamonds按cut分組茬缩,每組都按log(price) ~ carat擬合一個(gè)線性模型。和data.table不同的是吼旧,我們需要為操作指定一個(gè)名稱(chēng)凰锡,以便將結(jié)果儲(chǔ)存到列中。而且圈暗,do( ) 中的表達(dá)式不能直接在分組數(shù)據(jù)的語(yǔ)義下計(jì)算掂为,我們需要使用.來(lái)表示數(shù)據(jù):
models <- diamonds %>%
group_by(cut) %>%
do(lmod = lm(log(price) ~ carat, data = .))
models
## Source: local data frame [5 x 2]
## Groups: <by row>
##
## cut lmod
## <fctr> <chr>
## 1 Fair <S3: lm>
## 2 Good <S3: lm>
## 3 Very Good <S3: lm>
## 4 Premium <S3: lm>
## 5 Ideal <S3: lm>
注意到一個(gè)新列l(wèi)mod被創(chuàng)建了。這不是一個(gè)典型的原子向量列员串,而是一個(gè)包含了線性回歸對(duì)象的列表勇哗,也就是說(shuō),每一個(gè)cut的值對(duì)應(yīng)的模型會(huì)以列表的形式儲(chǔ)存在lmod列的對(duì)應(yīng)位置中寸齐。我們可以使用索引來(lái)獲得每個(gè)模型:
models$lmod[[1]]
##
## Call:
## lm(formula = log(price) ~ carat, data = .)
##
## Coefficients:
## (Intercept) carat
## 6.785 1.251
在需要完成高度定制的操作時(shí)欲诺,do( ) 函數(shù)的優(yōu)勢(shì)就更加明顯了。舉個(gè)例子渺鹦,假如我們需要分析toy_tests數(shù)據(jù)扰法,要對(duì)每種產(chǎn)品的質(zhì)量和耐久性進(jìn)行匯總。如果只需要樣本數(shù)最多的3個(gè)測(cè)試記錄毅厚,并且每個(gè)產(chǎn)品的質(zhì)量和耐久性是經(jīng)樣本數(shù)加權(quán)的平均數(shù)塞颁,考慮下我們應(yīng)該做什么。
使用dplyr包的函數(shù)和管道操作符,上述任務(wù)可以通過(guò)以下代碼輕松完成:
toy_tests %>%
group_by(id) %>%
arrange(desc(sample)) %>%
do(head(., 3)) %>%
summarize(
quality = sum(quality * sample) / sum(sample),
durability = sum(durability * sample) / sum(sample))
## Source:local data frame[2x3]
##
## id quality durability
## <chr> <dbl> <dbl>
## 1 T01 9.319149 9.382979
## 2 T02 9.040000 8.340000
注意到祠锣,當(dāng)數(shù)據(jù)分組后酷窥,所有的后續(xù)操作都是按組進(jìn)行的。為了查看中間結(jié)果伴网,我們可以運(yùn)行do(head(., 3)) 之前的代碼蓬推,如下所示:
toy_tests %>%
group_by(id) %>%
arrange(desc(sample))
## Source: local data frame [8 x 5]
## Groups: id [2]
##
## id date sample quality durability
## <chr> <int> <int> <int> <int>
## 1 T0120160405 180 9 10
## 2 T0120160302 150 10 9
## 3 T0120160502 140 9 9
## 4 T0120160201 100 9 9
## 5 T0220160403 90 9 8
## 6 T0220160502 85 10 9
## 7 T0220160303 75 8 8
## 8 T0220160201 70 7 9
這樣我們就得到了按樣本數(shù)降序排列的所有記錄。然后澡腾,do(head(., 3)) 將會(huì)對(duì)每一個(gè)組計(jì)算head(. 3)拳氢,其中,.表示每組數(shù)據(jù):
toy_tests %>%
group_by(id) %>%
arrange(desc(sample)) %>%
do(head(., 3))
## Source: local data frame [6 x 5]
## Groups: id [2]
##
## id date sample quality durability
## <chr> <int> <int> <int> <int>
## 1 T0120160405 180 9 10
## 2 T0120160302 150 10 9
## 3 T0120160502 140 9 9
## 4 T0220160403 90 9 8
## 5 T0220160502 85 10 9
## 6 T0220160303 75 8 8
現(xiàn)在蛋铆,我們得到了每一組的樣本數(shù)最多的3條記錄馋评,如此匯總數(shù)據(jù)是很方便的。
dplyr函數(shù)定義了一種非常直觀的數(shù)據(jù)操作語(yǔ)法刺啦,并且提供了便于使用管道操作符的高性能變形函數(shù)留特。更多內(nèi)容,請(qǐng)閱讀包的指南(https://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html)玛瘸,并且訪問(wèn)DataCamp上的交互式教程(https://www.datacamp.com/courses/dplyr-data-manipulation-r-tutorial)蜕青。