R語言學習筆記之——數據處理神器data.table
數據處理在數據分析流程中的地位相信大家都有目共睹,也是每一個數據從業者面臨的最為繁重的工作任務。
在實際應用場景下,雖然SQL(SQL類專業的etl語言)是數據處理的首選明星語言,性能佳、效率高、容易培養數據思維,但是SQL沒法處理構建全流程的數據任務,之後仍然需要藉助其他數據分析工具來對接更為深入的分析任務。
R語言作為專業的統計計算語言,數據處理是其一大特色功能,事實上每一個處理任務在R語言中都有著不止一套解決方案(這通常也是初學者在入門R語言時,感覺內容太多無從下手的原因),當然這些不同方案確實存在著性能和效率的絕大差異。
合理選擇一套自己的數據處理工具組合算是挺艱難的選擇,因為這個涉及到使用習慣和遷移成本的問題,比如你先熟知了R語言的基礎繪圖系統,在沒有強大的驅動力的情況下,你可能不太願意畫大把時間去研究ggplot2,你用會寫for/while循環,就不太願意去掌握apply組函數,甚至那些性能逆天的並行算運算包;剛開始會用基礎字元串處理,看到stringr包就面臨著技能工具更新的問題……
太多的選擇,讓人眼花繚亂,我自己也遇到過這種困惑,為了避免注意力分散,我的做法是先做可能性羅列——羅列一個可以實現同類功能的所有工具清單並做一套功能卡(也算是初步了解)。然後根據自己掌握的現狀選擇最熟練的一套,隨著時間的推移慢慢發現現有工具組合的不足,開始嘗試往更加高效、簡介的工具遷移,這樣以需求為推動力的技能升級和遷移更為徹底和明確。
最典型的幾個技能組合遷移如下:
基礎字元串處理函數——stringr繪圖系統:plot——ggplot2代碼風格:函數嵌套——管道函數(`%>%`)列表處理:list(自建循環)——rlistjson處理:Rjson+RJSONIO——jsonlite數據抓取:RCurl+XML——httr+xml2循環任務:for/while——apply——plyr::a_ply——並行運算(foreach、parallel)切片索引:subset——dplyr::select+filter聚合運算:aggregate——plyr::ddply+mutate——dplyr::group_by+summarize數據聯結:merge——plyr::join——dplyr::left/right/inner/outer_join數據塑型:plyr::melt/dcast——tidyr::gather/spread
……
其實還有很多類型的同類功能組合技能升級的路徑,不一給出,雖然工具遷移確實面臨著很高昂的代價,特別是時間成本、學習成本,但是遷移之後獲得的高效、代碼簡潔的體驗還是很爽的,以上特別是管道函數的遷移感觸最深,再也不存在自己寫完的東西間歇性懵逼的場景了。
說了這麼多,繞了這麼大的彎子想幹啥呢,沒錯今天又要給自己升級新技能啦,這次的主角兒是
data.table
一個R語言高性能數據處理包,一個包可以涵蓋以上所說的數據處理的大部分內容,而且操作高度抽象化話(抽象化就意味著代碼量少的可怕)。
其實很早就接觸過data.table,之所以一直沒有深入應用,因為它的理念與其他數據處理包偏離太遠,可以說遷移成本很高,幾乎就是技能重構而非遷移。
不過隨著視野的開闊,發現確實有必要深入了解這個高性能包,儘管有點兒顛覆R的傳統風格,但是性能和效率的提升可以彌補這一點。
data.table
1、I/O性能:
data.table的被推崇的重要原因就是他的IO吞吐性能在R語言諸多包中首屈一指,這裡以一個1.6G多的2015年紐約自行車出行數據集為例來檢驗其性能到底如何,希望我的小米本能扛得住折騰~_~
#清空內存rm(list=ls())gc()#使用傳統的I/O函數read_csv2進行導入:setwd("D:/Python/citibike-tripdata/")system.time( mydata1 <- read.csv("2015-citibike-tripdata.csv",stringsAsFactors = FALSE,check.names = FALSE) ) 用戶 系統 流逝 197.34 2.56 200.75 object.size(mydata1)1914019808 bytes數據量還是很大的,將近1.6G,900多萬記錄,16個欄位。
可憐的機器呀,內存和磁碟要撐爆了~
使用data.table內的I/O函數進行導入:
rm(list=ls())gc()library("data.table")system.time( mydata1 <- fread("2015-citibike-tripdata.csv") )Read 9937969 rows and 16 (of 16) columns from 1.606 GB file in 00:00:45 用戶 系統 流逝 43.44 0.48 44.43
五倍效率,45秒鐘900萬1.606G的數據,還是很有說服力的(雖然沒有傳說中的十倍性能)。
rm(list=ls())gc()
2、索引切片聚合
data.table中提供了將行索引、列切片、分組功能於一體的數據處理模型。
DT[i,j,by]
如果這個過程是SQL中是由select …… from …… where …… groupby …… having 來完成的,在R的其他基礎包中起碼也是分批次完成的。
dplyr::fliter() %>% select() %>% group_by() %>% summarize()
雖然可以藉助管道函數進行代碼優化,但是仍然無法與data.table的簡潔想抗衡。
mydata <- fread("https://raw.githubusercontent.com/wiki/arunsrinivasan/flights/NYCflights14/flights14.csv")
這裡使用一個在線數據集,包含2014年紐約機場發出的所有航班信息。
class(mydata)[1] "data.table" "data.frame"
使用fread函數導入之後便會自動轉化為data.table對象,這是data.table所特有的高性能數據對象,同時繼承了data.frame傳統數據框類,也意味著他能囊括很多數據框的方法和函數調用。
str(mydata)一共253316條記錄,17個欄位。
「year」 航班日期——年
「month」 航班日期——月「day」 航班日期——天「dep_time」 航班起飛時間「dep_delay」 航班延誤時長「arr_time」 航班到達時間「arr_delay」 航班到達延誤時間「cancelled」 航班是否取消「carrier」「tailnum」「flight」
「origin」 起飛地「dest」 目的地「air_time」「distance」 距離「hour」「min」data.table行索引
carrier <- unique(mydata$carrier)[1] "AA" "AS" "B6" "DL" "EV" "F9" "FL" "HA" "MQ" "VX" "WN" "UA" "US" "OO"tailnum <- sample(unique(mydata$tailnum),5)[1] "N332AA" "N813MQ" "N3742C" "N926EV" "N607SW"origin <- unique(mydata$origin)[1] "JFK" "LGA" "EWR"dest <- sample(unique(mydata$dest),5)[1] "BWI" "OAK" "DAL" "ATL" "ALB"``mydata[carrier == "AA" ]#等價於mydata[carrier == "AA",]#行索引可以直接引用列表,無需加表明前綴,這一點兒數據框做不到,而且i,j,by三個參數對應的條件支持模糊識別,無論加「,」與否都可以返回正確結果。mydata[carrier %in% c("AA","AS"),]支持在行索引位置使用%in% 函數。
data.table列索引
列索引與數據框相比操作體驗差異比較大,data.table的列索引摒棄了data.frame時代的向量化參數,而使用list參數進行列索引。
mydata[,list(carrier,tailnum)]
為了操作體驗更佳,這裡的list可以簡化為一個英文句點符號。即:
mydata[,.(carrier,tailnum)]#但心裡要清楚列索引接受的條件是含有列表的列表,而且這裡的列表作為變數給出,而非data.frame時代的字元串向量。
行列同時索引毫無壓力。
mydata[carrier %in% c("AA","AS"),.(carrier,tailnum)]
列索引的位置不僅支持列名索引,可以直接支持內建函數操作。
mydata[,.(flight/1000,carrier,tailnum)]
支持直接在列索引位置新建列,賦值符號為:=。
mydata[,delay_all := dep_delay+arr_delay]#銷毀某一列:mydata[,delay_all := NULL]
批量新建列:
mydata[,c("delay_all","delay_dif") := .((dep_delay+arr_delay),(dep_delay-arr_delay))]等價於寫法2:mydata[,`:=`(delay_all = dep_delay+arr_delay,delay_dif =dep_delay-arr_delay )]#銷毀新建列:mydata[,c("delay_all","delay_dif") := NULL]
注意以上新建列時,如果只有一列,列名比較自由,寫成字元串或者變數都可以,但是新建多列,必須嚴格按照左側列名為字元串向量,右側為列表的模式,當然你也可以使用第二種寫法。
DT[,`:=`(varname1 = statement1 ,varname1 = statement2)]
可以直接使用data.table內建的函數。
mydata[carrier %in% c("AA","AS"),.N][1] 26876.N是一個計數函數,相當於plyr中的count,或者基礎函數中的length。
基本的統計函數都可以直接支持。
mydata[carrier %in% c("AA","AS"),.(sum(dep_delay),mean(arr_delay))] V1 V21: 228913 5.263841mydata[carrier %in% c("AA","AS"),.(dep_delay,mean(arr_delay))]mydata[carrier %in% c("AA","AS") & dep_delay %between% c(500,1000),.(dep_delay,arr_delay)]
當整列和聚合的單值同時輸出時,可以支持自動補齊操作。
當聚合函數與data.table中的分組參數一起使用時,data.table的真正威力才逐漸顯露。
mydata[,.(sum(dep_delay),mean(arr_delay)),by = carrier]
多分組聚合。
mydata[,.(sum(dep_delay),mean(arr_delay)),by = .(carrier,origin)]
多分組計數。
mydata[,.N,by = .(carrier,origin)]
自定義名稱:
mydata[,.(dep_delay_sum = sum(dep_delay),arr_delay_mean = mean(arr_delay)),by = carrier]mydata[,.(dep_delay_sum = sum(dep_delay),arr_delay_mean = mean(arr_delay)),by = .(carrier,origin)]mydata[,.(carrier_n = .N),by = .(carrier,origin)]
數據排序:
排序行:
setorder(mydata,carrier,-arr_delay)
setorder函數作用於mydata本身,運行無輸出。如果想要運行的同時進行輸出則可以在結尾加上[]
setorder(mydata,carrier,-arr_delay)[]
這個功能有點兒類似於基礎函數中,在語句外部加上圓括弧。(a <- 1+1)
排序列:
sample(names(mydata),length(names(mydata))) [1] "arr_time" "air_time" "distance" "dep_time" "dest" "arr_delay" "month" "min" "tailnum" "origin" [11] "year" "hour" "cancelled" "flight" "day" "carrier" "dep_delay"setcolorder(mydata,sample(names(mydata),length(names(mydata))))mydata[carrier == "AA", lapply(.SD, mean), by=.(carrier,origin,dest), .SDcols=c("arr_delay","dep_delay") ]
以上語法加入了新的參數.SDcols和.SD,咋一看摸不著頭腦,其實是在按照carrier,origin,dest三個維度分組的基礎上,對每個子塊特定列進行均值運算。
這裡的執行邏輯是這樣的:
by=.(carrier,origin,dest) 先按照三個維度進行全部的分組;.SDcols=c("arr_delay","dep_delay")則分別在篩選每一個子數據塊兒上的特定列;lapply(.SD, mean)則將各個子塊的對應列應用於均值運算,並返回最終的列表。
數據合併:
data.table的數據合併方式非常簡潔;
DT <- data.table(x=rep(letters[1:5],each=3), y=runif(15))DX <- data.table(z=letters[1:3], c=runif(3))
設置各自的主鍵:
setkey(DT,x)setkey(DX,z)DT[DX]
就是如此簡單,連接的執行邏輯是,內側是左表,外側是右表,所以是DX left join DT
如果沒有設置主鍵,需要顯式聲明內部的on參數,指定連接主鍵,單主鍵必須在左右表中名稱一致。
當然你要是特別不習慣這種用法,還是習慣使用merge的話,data.table仍然是支持的,因為他本來就繼承了數據框,支持所有針對數據框的函數調用。
左手用R右手Python系列——數據合併與追加
長寬轉換:
長寬轉換仍然支持plyr中的melt/dcast函數以及tidyr中的gather/spread函數。
本篇僅對data.table的基礎常用函數做一個整理,如果想要學習期更為靈活高階的用法,還請非同步官方文檔。
左手用R右手Python系列——數據塑型與長寬轉換
推薦閱讀: