2014年剛到, 就在 Feedly 訂閱裏看到 RStudio Blog 介紹 dplyr 包已發佈 (Introducing dplyr), 此包將原本 plyr 包中的 ddply() 等函數進一步分離強化, 專注接受dataframe對象, 大幅提高了速度, 並且提供了更穩健的與其它數據庫對象間的接口。
0.1 安裝
install.packages("dplyr")
0.2 示範數據- library(Lahman) : Lahman 包裏的棒球比賽數據集 Batting
- library(hflights) : hflights 包裏的飛機航班數據
## 將過長過大的數據集轉換爲顯示更友好的 tbl_df 類型:
hflights_df <- tbl_df(hflights)
## 可以 hflights_df 感受一下不再被刷屏的感覺.
1 基本操作把常用的數據操作行爲歸納爲以下五種:
1.1 篩選: filter()按給定的邏輯判斷篩選出符合要求的子數據集, 類似於base::subset() 函數
例如:
filter(hflights_df, Month == 1, DayofMonth == 1)
## 用R自帶函數實現:
hflights[hflights$DayofMonth == 1, ] ;
除了代碼簡潔外, 還支持對同一對象的任意個條件組合, 如:
filter(hflights_df, Month == 1 | Month == 2)
## 注意: 表示 AND 時要使用& 而避免 &&
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)), ] ;
用列名作參數來選擇子數據集:
select(hflights_df, Year, Month, DayOfWeek)
還可以用 :來連接列名, 沒錯, 就是把列名當作數字一樣使用
select(hflights_df, Year : DayOfWeek)
## 選擇從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))1.5 彙總: summarise()對數據框調用其它函數進行彙總操作, 返回一維的結果:
summarise(hflights_df, delay = mean(DepDelay, na.rm = TRUE))## 該函數功能尚不是非常有用
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連接符 %.%包裏還新引進了一個操作符, 使用時把數據名作爲開頭, 然後依次對此數據進行多步操作.
Batting %.% group_by(playerID) %.% summarise(total = sum(G)) %.% arrange(desc(total)) %.% head(5)
## 在R中試了一下上面的代碼,顯示報錯如下,沒有找到原因,在help裏也搜索不到%.%函數,不曉得是不是dplyr包中這個函數已經取消了,如有問題,歡迎批評指正:
Batting %.%
+ group_by(playerID) %.%
+ summarise(total = sum(G)) %.%
+ arrange(desc(total)) %.%
+ head(5)
Error in Batting %.% group_by(playerID) %.% summarise(total = sum(G)) %.% :
could not find function "%.%"
? "%.%"
No documentation for ‘%.%’ in specified packages and libraries:
you could try ‘??%.%’
## 這樣可以按進行數據處理時的思路寫代碼, 一步步深入, 既易寫又易讀, 接近於從左到右的自然語言順序, 對比一下用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倍. (當然這只是一例, 還有其它更大的數字.)
**************** 感想 ****************************************************************************可以看到, 用 dplyr 所含函數實現的代碼都要簡潔易讀得多, 說到底, R語言只是一個工具, 作爲工具, 就是要拿來用的, 越稱手越便利越簡潔越好, 反思之下, 本人也是將大把的時間花在了對數據的反覆調整上, 或許是手生, 當然R語言在這方面也確實有一定不足, 大神又說了:
數據分析有兩個瓶頸,一是我們的目標是什麼,二是我們如何用計算機去實現。我現有的很多作品,如 ggplot2,plyr 和 reshape2,更關注的是如何更簡單地表達你的目標,而不是如何讓計算機算得更快。
這種內在的理念正是要將工具工具化, 把無謂的時間減少, 讓精力用在真正需要考慮的地方. 正如 Vim 一樣, 在投入一定的學習成本後, 繼續用繼續學, 不知不覺地就能心手如一, 想做什麼, 就已經按下去了, 從而更多地思考要編輯什麼, 而不必糾結於光標移動選擇等細節. 這其中的巧妙之處在於: 實現過程要以人腦的思維運作方式爲標準, 讓工具來適應人, 以實現目的爲導向, ggplot2 的圖形圖層語法也是如此. 不管是軟件也好, 編程語言也好, 高效的方法都是相通的, 這也正是許多人努力的方向, 另外平素語出驚人的王垠最近也表達了類似觀點.
暫時沒有太多的相關資料, 如欲進一步學習, 可參閱:
- dplyr 包自帶的60頁詳細文檔
- 其餘幾個vignettes (網頁) 或 vignette(package = "dplyr") , 包含了數據庫相關,
混合編程, 運算性能比較, 以及新的 window-functions 等內容.
簡單看了下vignette("window-functions", package = "dplyr"), 提供了一系列函數, 擴展了原來只能返回一個數值的聚焦類函數(如sum(), mean())至返回等長度的值, 變成 cumsum()和 cummean(), 以及 n(), lead() 和 lag()等便捷功能. - plyr 包的相關文檔
- 還有 data.table 包也是很強大的哦, 空下來可以學一學