R語言dplyr包
前言
2014年剛到, 就在 Feedly 訂閱里看到 RStudio Blog 介紹 dplyr 包已發布 (Introducing dplyr), 此包將原本 plyr 包中的 ddply() 等函數進一步分離強化, 專注接受dataframe對象, 大幅提高了速度, 並且提供了更穩健的與其它數據庫對象間的接口。 既然是 Hadley Wickham 的新作, 並自稱 a grammar of data manipulation,當然要先學為快了, 正好新申了域名, 就把原本記在 Rmd 里的筆記組織一下,放在這里,算是個簡短的教程吧,僅供入門。
正文: 學習筆記
以下內容主要參照 Introducing dplyr 和 dplyr 包自帶的簡介 ( Introduction to dplyr ), 復制了原文對應代碼, 並夾雜了個人理解和觀點 (多附於括號內)。
目錄
0. 初始化
1. 基本操作
2. 分組動作 group_by()
3. 連接符 %.%
4. 實用代碼匯總
5. 感想
6. 深入學習
參考資料
0. 初始化
0.1 安裝
install.packages("dplyr")
0.2 示范數據
library(Lahman): Lahman 包里的棒球比賽數據集 Batting
library(hflights): hflights 包里的飛機航班數據
0.3 數據集類型
將過長過大的數據集轉換為顯示更友好的 tbl_df 類型:
hflights _df <- tbl _df(hflights)
可以 hflights_df 感受一下不再被刷屏的感覺。
### code
install.packages("dplyr")
install.packages("Lahman")
install.packages("hflights")
library(Lahman)
library(hflights)
library(dplyr)
hflights_df <- tbl_df(hflights)
head(hflights)
class(hflights)
head(hflights_df)
class(hflights_df)
1. 基本操作
把常用的數據操作行為歸納為以下五種(Five basic verbs): filter, select, arrange, mutate, summarise (plus group_by)
窗口函數(Window functions): min _ rank, top_n, lag
實用函數(Convenience functions): sample _ n, sample_frac, glimpse, Count
連接數據庫操作(Connecting to databases)
-
1.1 篩選: filter()
-
按給定的邏輯判斷篩選出符合要求的子數據集, 類似於 base::subset() 函數,例如:
filter(hflights_df, Month == 1, DayofMonth == 1)
-
用R自帶函數實現:
hflights[hflights$Month == 1 & hflights$DayofMonth == 1, ]
-
除了代碼簡潔外, 還支持對同一對象的任意個條件組合, 如:
filter(hflights_df, Month == 1 | Month == 2) # 或 filter(hflights_df, Month %in% c(1, 2))
-
注意:這里需要提醒的是,對於多條件的選擇,需要完整條件的,然后使用集合運算符將條件拼接起來。集合運算符有 !、 |、 &、 xor(交補)。(表示 AND 時要使用 & 而避免 &&)條件的判斷符有>(=)、 <(=)、 ==、 !=、 %in% (判斷元素是否在集合或者列表內,返回邏輯值)。
# 錯誤寫法 filter(hflights_df, Month == 1 | 2)
-
-
1.2 排列:arrange()
-
按給定的列名依次對行進行排序,例如:
arrange(hflights_df, DayofMonth, Month, Year)
-
對列名加 desc() 進行倒序:
arrange(hflights_df, desc(ArrDelay))
-
這個函數和 plyr::arrange() 是一樣的,類似於order()。 用R自帶函數實現:
hflights[order(hflights$DayofMonth, hflights$Month, hflights$Year), ] hflights[order(desc(hflights$ArrDelay)), ]
-
-
1.3 選擇:select() || plus contains, starts_with, ends_with, matches
-
用列名作參數來選擇子數據集:
select(hflights_df, Year, Month, DayOfWeek)
-
還可以用 : 來連接列名, 沒錯,就是把列名當作數字一樣使用:
select(hflights_df, Year:DayOfWeek)
-
用 - 來排除列名:
select(hflights_df, -(Year:DayOfWeek))
-
同樣類似於R自帶的 subset() 函數 (但不用再寫一長串的 c("colname1", "colname2") 或者 which(colname(data) == "colname3"),甚至還要去查找列號)
-
-
1.4 變形:mutate()
-
對已有列進行數據運算並添加為新列:
mutate(hflights_df, gain = ArrDelay - DepDelay, speed = Distance / AirTime * 60 )
-
作用與 plyr::mutate() 相同,與 base::transform() 相似,優勢在於可以在同一語句中對剛增加的列進行操作:
mutate(hflights_df, gain = ArrDelay - DepDelay, gain_per_hour = gain / (AirTime / 60) )
-
而同樣操作用R自帶函數 transform() 的話就會報錯:
# 錯誤寫法 transform(hflights, gain = ArrDelay - DepDelay, gain_per_hour = gain / (AirTime / 60) ) # 正確寫法(原因:gain是中間變量) transform(hflights, gain = ArrDelay - DepDelay, gain_per_hour = (ArrDelay - DepDelay) / (AirTime / 60) )
-
-
1.5 匯總:summarise() || plus group_by, summarise_each, n, n_distinct, tally
-
對數據框調用其它函數進行匯總操作, 返回一維的結果:
summarise(hflights_df, delay = mean(DepDelay, na.rm = TRUE))
-
等同於 plyr::summarise(),原文說該函數功能尚不是非常有用, 大概以后的更新會加強吧。
-
-
1.6 抽樣:sample _ n(), sample_frac()
抽樣函數表示從表中隨機抽取任意行數據。第一行代碼:按照個數隨機從數據集中選擇十行,第二行代碼:按照比例隨機從數據集中選擇10%行(15 = 153 * 10%)。
# library(datasets)
# library(dplyr)
# head(airquality)
# dim(airquality)
# 按個數抽個
sample_n(airquality, size = 10)
# 按比例抽取
sample_frac(airquality, size = 0.1)
- 1.7 計數: Count()
count
The count function tallies observations based on a group. It is slightly similar to the table function in the base package. For example:
計數函數應用場景適用於分組觀測值。它稍微類似於基本函數包(base包)中的table函數。例如:
count(airquality, Month)
Month n
1 5 31
2 6 30
3 7 31
4 8 31
5 9 30
2. 分組動作 group_by()
以上5個動詞函數已經很方便了, 但是當它們跟分組操作這個概念結合起來時,那才叫真正的強大! 當對數據集通過 group_by() 添加了分組信息后,mutate()、 arrange() 和 summarise() 函數會自動對這些 tbl 類數據 執行分組操作 (R語言泛型函數的優勢)。
例如: 對飛機航班數據按飛機編號 (TailNum) 進行分組, 計算該飛機航班的次數 (count = n()), 平均飛行距離 (dist = mean(Distance, na.rm = TRUE)) 和 延時 (delay = mean(ArrDelay, na.rm = TRUE)) 。
planes <- group_by(hflights_df, TailNum)
delay <- summarise(planes,
count = n(),
dist = mean(Distance, na.rm = TRUE),
delay = mean(ArrDelay, na.rm = TRUE))
delay <- filter(delay, count > 20, dist < 2000)
用 ggplot2 包作個圖觀察一下, 發現飛機延時不延時跟飛行距離沒太大相關性:
ggplot(delay, aes(dist, delay)) +
geom_point(aes(size = count), alpha = 1/2) +
geom_smooth() +
scale_size_area()
更多例子見 vignette("introduction", package = "dplyr")
另: 一些匯總時的小函數
n(): 計算個數。
n_distinct(): 計算 x 中唯一值的個數。 (原文為 count_distinct(x), 測試無用)
first(x), last(x) 和 nth(x, n): 返回對應秩的值, 類似於自帶函數 x[1], x[length(x)], 和 x[n]
注意: 分組計算得到的統計量要清楚樣本已經發生了變化, 此時的中位數是不可靠的
3. 連接符 %.% (Using chaining syntax for more readable code)
包里還新引進了一個操作符, 使用時把數據名作為開頭, 然后依次對此數據進行多步操作。比如:
Batting %.%
group_by(playerID) %.%
summarise(total = sum(G)) %.%
arrange(desc(total)) %.%
head(5)
# 結果
playerID total
1 henderi01 2295
2 cobbty01 2246
3 bondsba01 2227
4 ruthba01 2174
5 aaronha01 2174
這樣可以按進行數據處理時的思路寫代碼, 一步步深入, 既易寫又易讀,接近於從左到右的自然語言順序,對比一下用R自帶函數實現的:
head(arrange(summarise(group_by(Batting, playerID), total = sum(G)) , desc(total)), 5)
或者像這篇文章所用的方法:
totals <- aggregate(. ~ playerID, data=Batting[,c("playerID","R")], sum)
ranks <- sort.list(-totals$R)
totals[ranks[1:5],]
文章里還表示: 用他的 MacBook Air 跑 %.% 那段代碼用了 0.036 秒, 跑上面這段代碼則用了 0.266 秒,運算速度提升了近7倍。 (當然這只是一例,還有其它更大的數字。)
更多請 ?"%.%", 至於這個新鮮的概念會不會和 ggplot2 里的 + 連接號一樣,發揮出種種奇妙的功能呢? 還是在實際使用中多體驗感受吧。
Pipe(類似於管道操作)
The pipe operator in R, represented by %>% can be used to chain code together. It is very useful when you are performing several operations on data, and don’t want to save the output at each intermediate step.
For example, let’s say we want to remove all the data corresponding to Month = 5, group the data by month, and then find the mean of the temperature each month. The conventional way to write the code for this would be:
# library(datasets)
# library(dplyr)
# head(airquality)
# dim(airquality)
filteredData <- filter(airquality, Month != 5)
groupedData <- group_by(filteredData, Month)
summarise(groupedData, mean(Temp, na.rm = TRUE))
With piping, the above code can be rewritten as:
airquality %>%
filter(Month != 5) %>%
group_by(Month) %>%
summarise(mean(Temp, na.rm = TRUE))
4. 實用代碼匯總
library(dplyr)
#將數據整理成的tbl_df數據(處理速度快)
iris <- tbl_df(iris)
##變量篩選select 對應select 刪除-
select(iris,Sepal.Length,Sepal.Width)
select(iris,-Species)
##對數據運算並添加為新列mutate() 對應 count(a) as t1
mutate(iris,t1=Sepal.Length*2)
##計算
n(): 計算個數
n_distinct() #: 計算 x 中唯一值的個數
first(x), last(x) 和 nth(x, n)#: 返回對應秩的值, 類似於自帶函數 x[1], x[length(x)], 和 x[n]
##過濾filter 對應 where
filter(iris,Sepal.Length>5,Sepal.Width<4)
filter(iris,Sepal.Length>5 & Sepal.Width<4 & (Species == "setosa" | Species == "versicolor"))
##數據排序arrange 對應 order by
arrange(iris,Sepal.Length)
arrange(iris,desc(Sepal.Length))
##匯總group_by() 分組-匯總
group_by(iris, Species)
group_by(iris,Species,Petal.Width) %>% summarise(c1=n(),c2=n_distinct(Species))
##計算summarise()
summarise(iris,c1=n(),c2=mean(Sepal.Length))
##多步操作連接符%>%
filter(iris,Sepal.Length>5,Sepal.Width<4) %>% summarise(c1=n(),c2=mean(Sepal.Length))
##抽樣sample_n sample_frac
sample_n(iris,20)
##左連接 ab交集 差集
left_join(a, b, by="x1")
right_join(a, b, by="x1")
inner_join(a, b, by="x1")##保留匹配的數據
outer_join(a, b, by="x1")##保留所有數據
semi_join(a, b, by="x1") # 數據集a中能與數據集b匹配的記錄
anti_join(a, b, by="x1") # 數據集a中雨數據集b不匹配的記錄
intersect(x, y): x 和 y 的交集(按行)
union(x, y): x 和 y 的並集(按行)
setdiff(x, y): x 和 y 的補集 (在x中不在y中)
##列合並
bind_cols(y, z)
##行合並
bind_rows(y, z)
5. 感想
可以看到,用 dplyr 所含函數實現的代碼都要簡潔易讀得多,說到底, R語言只是一個工具, 作為工具, 就是要拿來用的, 越稱手越便利越簡潔越好, 可是, 正如 Hadley Wickham 在2013年的訪談中提到的那樣:
如果你用了8小時進行數據清理和數據整理,而只用了2小時進行建模,那么很明顯,你希望了解如何將數據清理和整理的時間盡可能縮短。
反思之下,本人也是將大把的時間花在了對數據的反復調整上,或許是手生,當然R語言在這方面也確實有一定不足,大神又說了:
數據分析有兩個瓶頸,一是我們的目標是什么,二是我們如何用計算機去實現。我現有的很多作品,如 ggplot2,plyr 和 reshape2,更關注的是如何更簡單地表達你的目標,而不是如何讓計算機算得更快。
這種內在的理念正是要將工具工具化,把無謂的時間減少,讓精力用在真正需要考慮的地方。 正如 Vim 一樣,在投入一定的學習成本后,繼續用繼續學,不知不覺地就能心手如一,想做什么,就已經按下去了,從而更多地思考要編輯什么,而不必糾結於光標移動選擇等細節。 這其中的巧妙之處在於:實現過程要以人腦的思維運作方式為標准,讓工具來適應人,以實現目的為導向,ggplot2 的圖形圖層語法也是如此。不管是軟件也好,編程語言也好,高效的方法都是相通的,這也正是許多人努力的方向。
6. 深入學習
如欲進一步學習,可參閱:
- dplyr 包自帶的60頁詳細文檔
- 其余幾個 vignettes (網頁) 或 vignette(package = "dplyr") , 包含了數據庫相關, 混合編程,運算性能比較,以及新的 window-functions 等內容。
- 簡單看了下 vignette("window-functions", package = "dplyr") ,提供了一系列函數,擴展了原來只能返回一個數值的聚焦類函數(如sum(), mean())至返回等長度的值,變成 cumsum()和 cummean(),以及 n(),lead() 和 lag()等便捷功能。
- plyr 包的相關文檔: 主頁
- 還有 data.table 包也是很強大的哦,空下來可以學一學。
If this small example has whet your interest, you can learn more from the built-in vignettes. First install dplyr with install.packages("dplyr"), then run:
vignette("introduction",package = "dplyr") to learn how the main verbs of dplyr work with data frames.
vignette("databases", package = "dplyr") to learn how to work with databases from dplyr.
You can track development progress at http://github.com/hadley/dplyr, report bugs at http://github.com/hadley/dplyr/issues and get help with data manipulation challenges at https://groups.google.com/group/manipulatr. If you ask a question specifically about dplyr on StackOverflow, please tag it with dplyr and I’ll make sure to read it.