什么是DataFrame
引用 r-tutor上的定義:
DataFrame 是一個表格或者類似二維數組的結構,它的各行表示一個實例,各列表示一個變量。
沒錯,DataFrame就是類似於Excel表格和MySQL數據庫一樣是一個結構化的數據體。而這種結構化的數據體是當代數據流編程中的中流砥柱,幾乎所有先進算法的載體都是DataFrame,比如現在我們耳熟能詳的邏輯回歸算法、貝葉斯算法、支持向量機算法、XGBoost算法等等都建立在這個數據流編程的基礎之上,我們可以在R、Python、Scala等函數式編程中找到他們的身影。
R中的DataFrame數據流編程
參考前文 [[原]基於RStudio Webinars的統計報告Web化與工程化實踐總結](https://segmentfault.com/a/11...我們將數據流編程分為數據讀取、數據清洗、數據處理、數據可視化以及數據建模五個模塊。
數據讀取 readr/httr/DBI
readr
readr簡化了我們讀取多種格式表格型數據的方法,包括分割文件withread_delim()
,read_csv()
、read_tsv()
、read_csv2()
、固定寬度文件讀取的read_fwf()
、read_table()
以及read_log()
來讀取Web日志文件。在參數配置方面是和原生的read.xxx()
函數族是看齊的。
readr是利用C++和RCpp編寫的,所以執行的速度是相當快的,不過相對於直接用C語言寫的data.table::fread()
就稍微慢大概1.2-2倍左右。在實際使用中,data.talbe::fread()
的讀取速度可以比原生的read.csv
有3-10倍的提升速度。
httr
httr是一個高級的網絡請求庫,類似於Python中的Tornado和Requests,除了提供基本的Restful接口設計功能,比如GET()
, HEAD()
,PATCH()
, PUT()
, DELETE()
和 POST()
,還提供了OAuth的調用,比如oauth1.0_token()
和oauth2.0_token()
。而且httr
還提供了諸如session、cookie、SSL、header、proxy、timeoutd等更過高級管理功能。當然你可以用它來做簡單的爬蟲應用,如果需要更高級的爬蟲,我們需要投入rvest的懷抱來支持諸如xpath
等高級爬蟲特性。
DBI
DBI是一個為R與數據庫通訊的數據庫接口。相當於Java里面的DAO,Python里的Torndb和Tornlite,方便多種關系型數據庫的SQL請求。其中最亮眼的是,R中的DataFrame和數據庫之前可以以整個數據框插入的形式插入數據而不需要再拼接SQL語句。
以下是一個官方文檔的示例:
library(DBI)
# 創建一個臨時內存的 RSQLite 數據庫 con <- dbConnect(RSQLite::SQLite(), dbname = ":memory:") dbListTables(con) # 直接插入整個數據框到數據庫中 dbWriteTable(con, "mtcars", mtcars) dbListTables(con) dbListFields(con, "mtcars") dbReadTable(con, "mtcars") # 你可以獲取所有結果: res <- dbSendQuery(con, "SELECT * FROM mtcars WHERE cyl = 4") dbFetch(res) dbClearResult(res) # 或者一次取一塊 res <- dbSendQuery(con, "SELECT * FROM mtcars WHERE cyl = 4") while(!dbHasCompleted(res)){ chunk <- dbFetch(res, n = 5) print(nrow(chunk)) } dbClearResult(res) dbDisconnect(con)
數據清洗 tidyr/jsonlite
tidyr
tidyr
是一個數據清洗的新包,正在取代reshape2
、spreadsheets
等包。清潔的數據在數據處理的后續流程中十分重要,比如數據變化(dplyr),可視化(ggplot2/ggvis)以及數據建模等。tidyr
主要提供了一個類似Excel中數據透視表(pivot table)的功能,提供gather
和spread
函數將數據在長格式和寬格式之間相互轉化,應用在比如稀疏矩陣和稠密矩陣之間的轉化。此外,separate
和union
方法提供了數據分組拆分、合並的功能,應用在nominal數據的轉化上。
jsonlite
類似於Python中的json庫,參考前文 [[原]數據流編程教程:R語言與非結構化數據共舞](https://segmentfault.com/a/11...,我們可以知道jsonlite是一個標准的json轉化庫,依賴於jsonlite我們可以自由地在JSON和DataFrame之間相互轉化。
數據處理 dplyr/rlist/purrr
dplyr
dplyr包是現在數據流編程的核心,同時支持主流的管道操作 %>%
,主要的數據處理方法包括:
-
高級查詢操作:
select()
: 按列變量選擇filter()
: 按行名稱分片slice()
: 按行索引分片mutate()
: 在原數據集最后一列追加一些數據集summarise()
: 每組聚合為一個小數量的匯總統計,通常結合gruop_by()
使用arrange()
: 按行排序
-
關聯表查詢
inner_join(x, y)
: 匹配 x + yleft_join(x, y)
: 所有 x + 匹配 ysemi_join(x, y)
: 所有 x 在 y 中匹配的部分anti_join(x, y)
: 所有 x 在 y 中不匹配的部分
-
集合操作
intersect(x, y)
: x 和 y 的交集(按行)union(x, y)
: x 和 y 的並集(按行)setdiff(x, y)
: x 和 y 的補集 (在x中不在y中)
更多詳細操作可以參考由SupStats翻譯的 數據再加工速查表,比Python的老鼠書直觀很多。
rlist
參考前文 數據流編程教程:R語言與非結構化數據共舞,我們知道,區別於dplyr
包,rlist
包是針對非結構化數據處理而生的,也對以list為核心的數據結構提供了類似DataFrame的高級查詢、管道操作等等方法。
purrr
purrr
向Scala這樣的具有高級類型系統的函數式編程語言學習,為data frame的操作提供更多的函數式編程方法,比如map、lambda表達式。此外,purrr
引入了靜態類型,來解決原生的apply
函數族類型系統不穩定的情況。
我遇到過一個非常頭疼的apply
函數的問題:apply內的表達式計算結果不一致。
# 原來表達式是這樣的,但是返回的計算結果不對: # x1,x2,x3都是一個含有NA值的一個10x10的矩陣 apply(x1*x2-x1*x3,1,sum,na.rm=T)
於是改成分步計算才能得到正確答案。
t1 <- apply(x1 * x2,1,sum,na.rm=T) t2 <- apply(x1 * x3,1,sum,na.rm=T) t3 <- t1 - t2
如果使用purrr
包就可以很好的解決這一問題。參考 Wisdom's Quintessence: Purrr package for R is good for performance 的例子:
library(purrr)
mtcars %>% split(.$cyl) %>% map(~ lm(mpg ~ wt, data = .)) %>% map(summary) %>% map_dbl("r.squared")
具體使用可以參考Rstudio Blog:purrr 0.2.0。
數據可視化 ggplot2/ggvis
ggplot2
ggplot2 是一個增強的數據可視化R包,幫助我們輕松創建令人驚嘆的多層圖形。它的設計理念類似於PhotoShop,具體參數包含設計對象、藝術渲染、統計量、尺寸調整、坐標系統、分片顯示、位置調整、動畫效果等等。
更多操作可以查看ggplot2與數據可視化速查表 和 官方文檔
實戰可以參考R Graphics Cookbook一書。
ggvis
ggvis
是吸收了ggplot2、vega以及d3的精華,目標旨在配合shiny打造動態可交互的可視化組件。ggvis
最明顯的區別就是在作圖時直接支持%>%
的管道操作,比如:
diamonds %>% ggvis(~carat, ~price, fill=~clarity) %>% layer_points(opacity:=1/2)
ggplot2與ggvis的關系類似於plyr與dplyr的關系,都是一種演化過程。
數據建模 broom
broom
在機器學習的本質其實就是各種姿勢的回歸,而在R中的各種回歸分析往往不會返回一個整齊的data frame 結果。比如:
lmfit <- lm(mpg ~ wt, mtcars) lmfit
## ## Call: ## lm(formula = mpg ~ wt, data = mtcars) ## ## Coefficients: ## (Intercept) wt ## 37.285 -5.344
這時候broom
包就派上用場了,直接將統計結果轉化為data frame格式:
library(broom)
tidy(lmfit)
## term estimate std.error statistic p.value ## 1 (Intercept) 37.285126 1.877627 19.857575 8.241799e-19 ## 2 wt -5.344472 0.559101 -9.559044 1.293959e-10
augment()
函數返回data frame格式的s其所有他參數結果
head(augment(lmfit))
## .rownames mpg wt .fitted .se.fit .resid .hat .sigma .cooksd .std.resid ## 1 Mazda RX4 21.0 2.620 23.28261 0.6335798 -2.2826106 0.04326896 3.067494 1.327407e-02 -0.76616765 ## 2 Mazda RX4 Wag 21.0 2.875 21.91977 0.5714319 -0.9197704 0.03519677 3.093068 1.723963e-03 -0.30743051 ## 3 Datsun 710 22.8 2.320 24.88595 0.7359177 -2.0859521 0.05837573 3.072127 1.543937e-02 -0.70575249 ## 4 Hornet 4 Drive 21.4 3.215 20.10265 0.5384424 1.2973499 0.03125017 3.088268 3.020558e-03 0.43275114 ## 5 Hornet Sportabout 18.7 3.440 18.90014 0.5526562 -0.2001440 0.03292182 3.097722 7.599578e-05 -0.06681879 ## 6 Valiant 18.1 3.460 18.79325 0.5552829 -0.6932545 0.03323551 3.095184 9.210650e-04 -0.23148309
glance()
函數,返回data frame格式的部分參數結果
glance(lmfit)
## r.squared adj.r.squared sigma statistic p.value df logLik ## 1 0.7528328 0.7445939 3.045882 91.37533 1.293959e-10 2 -80.01471 ## AIC BIC deviance df.residual ## 1 166.0294 170.4266 278.3219 30
DataFrame優化
data.table
眾所周知,data.frame的幾個缺點有:
-
大數據集打印緩慢
-
內部搜索緩慢
-
語法復雜
-
缺乏內部的聚合操作
針對這幾個問題,data.table應運而生。data.table完美兼容data.frame,這意味着之前對data.frame的操作我們可以完全保留,並且支持更多方便的數據操作方法。
data.table還參考了NoSQL中流行的Key-Value形式,引入了setkey()
函數,為數據框設置關鍵字索引。
值得一提的是data.table引入了全新的索引形式,大大簡化了data frame的分片形式,提供接近於原生矩陣的操作方式並直接利用C語言構造底層,保證操作的速度。
對比操作
對比data.table 和 dplyr 的操作:
操作 | data.table | dplyr |
---|---|---|
按行分片 | DT[1:2,] | DF[1:2,] |
按列分片 | DT[,1:2,with=False] | DF[,1:2] |
分組summarise | DT[, sum(y), by=z] | DF %>% group_by(z) %>% summarise(sum(y)) |
分組mutate | DT[, y := cumsum(y), by=z] | ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y)) |
篩選后分組匯總 | DT[x > 2, sum(y), by=z] | DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y)) |
篩選后分組更新 | DT[x > 2, y := cumsum(y), by=z] | ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x>2), cumsum(y))) |
分組后按條件匯總 | DT[, if(any(x > 5L)){y[1L]-y[2L]}else{y[2L], by=z]} | DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L]-y[2L] else y[2L]) |
apply函數族
操作 | data.table | dplyr |
---|---|---|
分組擴展各list | DT[, (cols) := lapply(.SD, sum), by=z] | ans <- DF %>% group_by(z) %>% mutate_each(funs(sum)) |
分組匯總各list | DT[, lapply(.SD, sum), by=z] | DF %>% group_by(z) %>% summarise_each(funs(sum)) |
分組匯總各list | DT[, c(lapply(.SD, sum),lapply(.SD, mean)), by=z] | DF %>% group_by(z) %>% summarise_each(funs(sum, mean)) |
分組匯總各list | DT[, c(.N, lapply(.SD, sum)), by=z] | DF %>% group_by(z) %>% summarise_each(funs(n(), mean)) |
join 操作
setkey(DT1, x, y)
操作 | data.table | dplyr |
---|---|---|
一般join | DT1[DT2] | left_join(DT2, DT1) |
擇列join | DT1[DT2, .(z, i.mul)] | left_join(select(DT2, x,y,mul), select(DT1, x,y,z)) |
聚合join | DT1[DT2, .(sum(z)*i.mul), by=.EACHI] | DF1 %>% group_by(x, y) %>% summarise(z=sum(z)) %>% inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul) |
更新join | DT1[DT2, z := cumsum(z)*i.mul, by=.EACHI] | join and group by + mutate |
滾動join | DT1[DT2, roll = -Inf] | / |
其他變量控制輸出 | DT1[DT2, mult = "first"] | / |
拼接操作
操作 | data.table | dplyr |
---|---|---|
分組再分list聚合 | DT[, list(x[1], y[1]), by=z] | DF %>% group_by(z) %>% summarise(x[1], y[1]) |
分組再分list拼接 | DT[, list(x[1:2], y[1]), by=z] | DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1])) |
分組取分位數聚合 | DT[, quantile(x, 0.25), by=z] | DF %>% group_by(z) %>% summarise(quantile(x, 0.25)) |
分組取分位數拼接 | DT[, quantile(x, c(0.25, 0.75)), by=z] | DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75)))) |
分組分list聚合拼接 | DT[, as.list(summary(x)), by=z] | DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x)))) |
更多操作詳情可查看data.table速查表。
DataFrame可視化
DT
DT
包是謝溢輝老師的大作,為data frame數據提供了非常好的可視化功能,並且提供了篩選、分頁、排序、搜索等數據查詢操作。
library(DT)
datatable(iris)
此外,DT
包還提供了大量的UI定制的功能,對html、css和js進行深度定制。比如:
m = matrix(c(
'<b>Bold</b>', '<em>Emphasize</em>', '<a href="http://rstudio.com">RStudio</a>', '<a href="#" onclick="alert(\'Hello World\');">Hello</a>' ), 2) colnames(m) = c('<span style="color:red">Column 1</span>', '<em>Column 2</em>') datatable(m) # 默認 escape = TRUE
datatable(m, escape = FALSE)
raw_matrix %>%
DT::datatable(options = list(pageLength = 30, dom = 'tip')) %>% DT::formatStyle(columns = c("A","B") background = styleColorBar(c(0, max(raw_matrix,na.rm = TRUE)), 'steelblue'), backgroundSize = '100% 50%', backgroundRepeat = 'no-repeat', backgroundPosition = 'center')
分布式DataFrame
DDF
DDF的全稱是 Distributed Data Frame, 也就是分布式數據框。DDF用一個統一的跨引擎API簡化了多數據源的分析操作,進一步將data frame底層的分布式傻瓜化。
在R中使用DDF,我們不需要修改之前任何的代碼,並且繞過Hadoop的絕對限制,就可以讓data frame格式的數據,自動獲得分布式處理的能力!
# 創建 DDF 管理器來運行Spark引擎 dm <- DDFManager("spark") # 從表格中創建 DDF ddf <- sql2ddf(dm, "select * from mtcars") /*Basic Stats*/ # 返回行/列的值 ncol(ddf) nrow(ddf) # 在 DDF 上進行運行標准匯總 summary(ddf)
更多具體操作可以參考官方指南
DataFrame在R、Python和Spark三者中的聯系
操作 | R | Python | Spark |
---|---|---|---|
庫 | base | Pandas | spark SQL |
讀取csv | read.csv() | read_csv() | spark-csv |
計數 | nrow() | pandasDF.count() | sparkDF.count() |
分片 | head(data,5) | pandasDF.head(5) | sparkDF.show(5) |
推斷類型 | 自動推斷 | 自動推斷 | 默認為string類型 |
標准差計算中的NaN處理 | 視為NA | 自動排除 | 視為NaN |
特征工程 | dplyr::mutate() | pandasDF['new'] | sparkDF.withColumn() |
DataFrame 之我見
-
處理數據的第一語言還是 SQL語句,因為SQL是DSL,這樣就對使用者沒有Python或者R的要求,也方便與DBA的維護。在R中可以使用
sqldf
通過SQL直接操作DataFrame,在Python中可以使用pysqldf
。 -
處理數據的第二語言則是
tidyverse
或者pandas
,使用這樣的鏈式調用方法可以提升數據流的處理效率,規避一些原生SQL在不同數據庫中執行情況不同或者可讀性較差的問題。 -
處理數據的第三語言則是
data.table
或者scala
,使用這樣高性能的方法可以在關鍵步驟提升數據處理效率到極致,不過會犧牲一部分維護性。