以下內容主要參照 Introducing dplyr 和 dplyr 包自帶的簡介 (Introduction to dplyr), 復制了原文對應代碼, 並夾雜了個人理解和觀點 (多附於括號內).
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 感受一下不再被刷屏的感覺.
1 基本操作
把常用的數據操作行為歸納為以下五種:
1.1 篩選: filter()
按給定的邏輯判斷篩選出符合要求的子數據集, 類似於 base::subset() 函數
例如:
filter(hflights_df, Month == 1, DayofMonth == 1)
用R自帶函數實現:
hflights[hflightsMonth == 1 & hflightsDayofMonth == 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(hflightsDayofMonth,hflightsMonth, hflightsYear),]hflights[order(desc(hflightsArrDelay)), ]
1.3 選擇: select()
用列名作參數來選擇子數據集:
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))1.5 匯總: summarise()
對數據框調用其它函數進行匯總操作, 返回一維的結果:
summarise(hflights_df, delay = mean(DepDelay, na.rm = TRUE))
等同於 plyr::summarise(), 原文說該函數功能尚不是非常有用, 大概以后的更新會加強吧.
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自帶函數實現的:
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 里的 + 連接號一樣, 發揮出種種奇妙的功能呢? 還是在實際使用中多體驗感受吧.
感想
可以看到, 用 dplyr 所含函數實現的代碼都要簡潔易讀得多, 說到底, R語言只是一個工具, 作為工具, 就是要拿來用的, 越稱手越便利越簡潔越好, 可是, 正如 Hadley Wickham 在2013年的訪談中提到的那樣:
如果你用了8小時進行數據清理和數據整理,而只用了2小時進行建模,那么很明顯,你希望了解如何將數據清理和整理的時間盡可能縮短。
反思之下, 本人也是將大把的時間花在了對數據的反復調整上, 或許是手生, 當然R語言在這方面也確實有一定不足, 大神又說了:
數據分析有兩個瓶頸,一是我們的目標是什么,二是我們如何用計算機去實現。我現有的很多作品,如 ggplot2,plyr 和 reshape2,更關注的是如何更簡單地表達你的目標,而不是如何讓計算機算得更快。
這種內在的理念正是要將工具工具化, 把無謂的時間減少, 讓精力用在真正需要考慮的地方. 正如 Vim 一樣, 在投入一定的學習成本后, 繼續用繼續學, 不知不覺地就能心手如一, 想做什么, 就已經按下去了, 從而更多地思考要編輯什么, 而不必糾結於光標移動選擇等細節. 這其中的巧妙之處在於: 實現過程要以人腦的思維運作方式為標准, 讓工具來適應人, 以實現目的為導向, ggplot2 的圖形圖層語法也是如此. 不管是軟件也好, 編程語言也好, 高效的方法都是相通的, 這也正是許多人努力的方向, 另外平素語出驚人的王垠最近也表達了類似觀點.
順便肖凱老師在網易雲課堂新開的R語言初級教程里提到了十大必學R包的說法, 並把 plyr 列為之一, 有趣的是居然還有人在問答平台上求詳情, 好奇之下放狗一搜, 原來出處在此 (脫水版), 其中 ggplot2 和 reshape2 是平時都有在用的, 還有實用的 knitr 和 Slidify , 其它就沒什么發言權了.
深入學習
暫時沒有太多的相關資料, 如欲進一步學習, 可參閱:
- dplyr 包自帶的60頁詳細文檔
- 其余幾個vignettes (網頁) 或 vignette(package = "dplyr") , 包含了數據庫相關, 混合編程, 運算性能比較, 以及新的 window-functions 等內容.
簡單看了下vignette("window-functions", package = "dplyr"), 提供了一系列函數, 擴展了原來只能返回一個數值的聚焦類函數(如sum(), mean())至返回等長度的值, 變成 cumsum()和 cummean(), 以及 n(), lead() 和 lag()等便捷功能. - plyr 包的相關文檔: 主頁
- 還有 data.table 包也是很強大的哦, 空下來可以學一學
常見的數據處理包
dplyr——package
1.數據對象:tbl對象
使用dplyr包預處理時建議使用tbl_df()或tbl_cube()或tbl_sql()函數將原數據轉換為tbl對象
2.觀測篩選
將指定條件的觀測篩選出來:filter()函數
filter(.data,…)
.data為tbl對象
…為觀測篩選條件,類似於subset()函數,但不同的是filter()函數不能篩選某些關心的變量變量
library(dplyr)
df <- data.frame(x = c("a","b","c","a","b","e","d","f"),y = c(1,2,3,4,5,6,7,8)) dftbl <- tbl_df(df) filter(dftbl,x %in% c("a","b"))
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
3.變量選取
select()函數可以篩選指定的變量,而且選擇變量時也可以重新命名變量。如果要剔除某些變量,只需要在變量前加上負號”-“。
select()函數,傳遞的參數:
starts_with(x,ignor.case = TRUE) # 選擇以字符x開頭的變量
ends_with(x,ignore.case = TRUE) # 選擇以字符x結尾的變量
contains(x,ignore.case = TRUE) #選擇所有包含x的變量
matches(x,ignore.case = TRUE) #選擇匹配正則表達式的變量
num_range(“x”,1:5,width = 2) #選擇從x01到x05的數值型變量
one_of(“x”,”y”,”z”) #選擇包含在聲明變量中的變量
everything() #選擇所有變量,一般調整數據集中變量順序時使用
示例:
# 將dftbl數據集中的y變量放到x變量之前 select(dftbl,everything()) #篩選變量的同時,重新命名變量 select(dftbl,x1 = 1,y1 = y)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
4.重命名變量
rename(tbl,newname = oldname,…)
rename(dftbl,x1 = x,y1 = y)
- 1
- 1
5.數據排序
數據預處理的過程中往往也需要按某些變量進行排序:arrange()函數實現語法:
arrange(.data,…)
arrange()函數默認以某個變量進行升序,如需降序則desc(var_name)
arrange(dftbl,y) # y變量升序 arrange(dftbl,desc(y)) #降序操作
- 1
- 2
- 1
- 2
6.數據擴展
通過mutae()函數可以在原始數據集的基礎上擴展新變量,並保留原始變量
mutate(.data,…)
mutate(dftbl,z = y^2+y-10)
- 1
- 1
同樣可以進行數據擴展的函數transmute(),與mutate()函數不同的是,該函數擴展新變量的同時將刪除所有原始變量,結果中只有擴展變量。
transmute(dftbl,z =y^2)
- 1
- 1
7.數據聚合
在數據庫操作中,往往需要進行聚合函數的應用,這里可以使用summarize()函數實現數據集聚合操作.個人理解的聚合函數就是對數據集中的變量進行統計計算。
語法如下:
summarize(.data,…)
可以用來聚合的函數有:
min(),max(),mean()…等統計量,以及IQR() #返回四分位極差
n() # 返回觀測個數
n_distinct() #返回不同的觀測個數
first() # 返回第一個觀測
last() #返回最后一個觀測
nth() #返回n個觀測
summarize(dftbl,max(y)) summarize(dftbl,n())
- 1
- 2
- 1
- 2
還可以用group_by()函數實現分組聚合
group_by()語法如下:
group_by(.data,add = FALSE)
summarize(group_by(dftbl,x),sum(y))
- 1
- 1
8.數據關聯
數據框中經常需要將多個表進行連接操作,如左連接、右連接、內連接等,這里dplyr包也提供了數據集的連接操作。如下:
inner_join #內連接
left_join #左連接
right_join #右連接
full_join #全連
semi_join # 返回能夠與y表匹配的x表所有記錄
anti_join # 返回無法與y表匹配的x表的所有記錄
df2 <- data.frame(x = c("a","b","c"),z = c("A","B","C")) df2tbl <- tbl_df(df2) inner_join(x = dftbl,y = df2tbl,by = "x") semi_join(x = dftbl,y = df2tbl,by = "x") anti_join(x = dftbl,y = df2tbl,by = "x")
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
9.數據合並
R基礎包cbind()和rbind()函數實現按列的方向進行數據合並和按行的方向進行合並。
dplyr包中也添加了類似功能的函數,分別是bind_cols()函數和bind_rows()函數
mydf1 <- data.frame(x = c(1,2,3,4),y = c(10,20,30,40)) mydf2 <- data.frame(x = c(5,6),y = c(50,60)) mydf3 <- data.frame(z = c(100,200,300,400)) bind_rows(mydf1,mydf2) #行變長 bind_cols(mydf1,mydf3)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
10.管道函數
即通過%>%將上一個函數的輸出作為下一個函數的輸入
# 根據數據集dftbl和df2tbl,取出z變量對應的最大y值
inner_join(x = dftbl,y = df2tbl,by = "x") %>% group_by(z) %>% summarise(max(y))
- 1
- 2
- 1
- 2
11.連接數據庫數據
如果需要獲取MySQL數據庫中的數據時,可以直接使用dplyr包中的src_mysql()函數。
src_mySQL 函數語法如下:
src_mysql(dbname,host = NULL,port = 0L,user = “root”,password = “”,…)
通過以上方式連接MySQL數據庫后,使用tbl()函數獲取數據集,tbl()函數語法如下:
tbl(src,from =”“)
src為src_mysql()函數對象
from為SQL語句
src <- src_mysql("test",host = "localhost",user = "root",password = "snake") src
- 1
- 2
- 1
- 2
plyr-package
可以非常方便的實現數據結構之間的轉換
其中的函數名有一定的規律,跟輸入輸出的數據結構相關。
1.函數介紹
a*ply函數形式
aaply(.data = ,.margins = ,.fun = ,...,.progress = "none",.inform = FALSE) adply(.data = ,.margins = ,.fun = ,...,.progress = "none",.inform = FALSE) alply(.data = ,.margins = ,.fun = ,...,.progress = "none",.inform = FALSE) a_ply(.data = ,.margins = ,.fun = ,.progress = "none",.inform = FALSE) #輸入結構:array,無輸出結果
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
.data可以是數組也可以是矩陣;
.margins指定要分析的數組或矩陣的維度,即行維(margins = 1),列維(margins = 2)
.fun為行或列維指定需要處理的函數,可以是R自帶的函數,如sum(),mean()等,也可以是自定義函數;
…為指定函數的其他參數;.progress指定以什么樣的方式展示程序運行的進度,默認為不顯示進度,還可以選擇text(文本進度條)、tk(tk進度條)和win(windows系統自帶的進度條)
.inform是否指定報錯信息,默認不指定,因為設為TRUE,將會降低程序的執行效率,但該參數對bug的處理是有幫助的
示例;
library(plyr)
a <- array(data = 1:500000,dim = c(100000,5)) # 對每一行求均值,不顯示進度條 test1 <- aaply(.data = a,.margins = 1,.fun = mean,.progress = "none") head(test1) # 對每一行求標准差,以文本的形式顯示進度條 test2 <- adply(.data = a,.margins = 1,.fun = sd,.progress = "text") head(test2) # 對每一列求和,以tk形式顯示進度條 a2 <- array(rnorm(100000),dim = c(100,1000)) test3 <- alply(.data = a2,.margins = 2,.fun = sum,.progress = "tk") head(test3) # 對每一列求最大值,以windows自帶進度條顯示進度 a3 <- array(rnorm(100000),dim = c(100,1000)) test4 <- a_ply(.data = a3,.margins = 2,.fun = max,.progress = "win")
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
# d*ply函數格式
daply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE)
ddply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE)
dlply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE)
d_ply(.data = ,.variables = ,.fun = ,…,.progress = “none”,.inform = FALSE)
.data 指定為數據框結構;
.variables指定數據框中的分組變量,需要用點號.引起來;
.fun 基於分組變量,可對數據框中的其余變量指定某種函數,可以是R自帶的函數,如sum(),mean()等,也可以自定義函數,類似於聚合分析;
.progress和.inform與a*plyr函數參數一致。
示例:
# 構建自定義函數
fun <- function(data) apply(data,2,mean) daply(.data = iris[,1:4],.variables = .(iris$Species),.fun = fun) ddply(.data = iris[,1:4],.variables = .(iris$Species),.fun = fun) dlply(.data = iris[,1:4],.variables = .(iris$Species),.fun = fun)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
# l*ply函數格式
laply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE) ldply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE) llply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE) l_ply(.data = ,.fun = ,...,.progress = "none",.inform = FALSE)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
.data可以指定為列表數據.
其余參數與a*ply()函數和d*ply()函數參數一致。
示例:
x1 <- 1:100 x2 <- seq(from = 100,to = 1000,by = 2) x3 <- runif(150,min = 10,max = 100) # 列表由向量構成 l1 <- list(x1 = x1,x2 = x2,x3 = x3) laply(.data = l1,.fun = mean) ldply(.data = l1,.fun = summary) llply(.data = l1,.fun = quantile) l_ply(.data = l1,.fun = summary) # 構建數據框dll y11 <- rnorm(n = 100,mean = 10,sd = 5) y12 <- rt(n = 100,df = 3) y13 <- rf(n = 100,df1 = 2,df2 = 3) y14 <- factor(x = c("low","potential","high"),ordered = T) y15 <- sample(y14,size = 100,replace = TRUE) d11 <- data.frame(y1 = y11,y2 = y12,y3 = y13,y5 = y15) head(dll) # 構建數據框d21 y21 <- 1:100 y22 <- seq(from = 1,to = 2,length = 100) y23 <- rchisq(n = 100,df = 8) y24 <- factor(x = c("A","B","C","D"),order = T) y25 <- sample(y24,size = 100,replace = TRUE) d21 <- data.frame(y21 = y21,y22 = y22,y23 = y23,y25 = y25) head(d21) # 列表由數據框組成 l2 <- list(first = d11,second = d21) library(psych) fun <- function(data) describeBy(data[,1:3],group = data[,4]) llply(.data = l2,.fun = fun,.progress = "none") llply(.data = l2,.fun = fun,.progress = "text")