如何使用管道操作符優雅的書寫R語言代碼

本文將跟大家分享如果在R語言中使用管道操作符優化代碼,以及管道函數調用及傳參的注意事項。

使用R語言處理數據或者分析,很多時候免不了要寫連續輸入輸出的代碼,按照傳統書寫方式或者習慣,初學者往往會引入一大堆中介變數,或者使用函數嵌套進行一次性輸出。

以上兩種方法雖然從結果上來看,同樣可以達到我們預期的效果,但是無論是代碼效率還是內存佔用上都存在巨大劣勢。

1、使用中介變數會使得內存開銷成倍增長,特別是你的原始數據量非常大而內存又有限,在一個處理過程中引入太多中介對象,不僅代碼冗餘,內存也會迅速透支。

2、使用函數嵌套則避免了內存佔用的問題,但是嵌套太多層函數,會造成代碼難以理解,閱讀困難,甚至給今後的項目復用造成很大的困擾。

而R語言大佬們很早就已經意識到這個問題,開始在R語言中引入管道操作符函數,進行連續傳參,實現了內存節省、代碼優化的需求。

通常我們使用最多的管道函數來自於magrittr包,該包中管道操作函數寫作%>%,這是一個在R語言中使用非常頻繁的函數,很多比較成熟的項目擴展包都已經實現了管道操作函數的內置。(比如dplyr、rvest、leaflet等都實現了默認調用)。

在大多數並沒有默認載入magrittr包的擴展包函數中使用管道操作符,需要先載入該包之後才能使用該函數。

這裡僅以一個小案例來開始今天的講解:

library(「rvest」)library(「stringr」)url<-「http://www.zyzw.com/twzs010.htm「

在不久前的一篇關於中國世界文遺產儀錶盤的案例中,我在目標網站上抓取了52個中國世界自然文遺產的名稱。

按照傳統的引入中間變數的寫法,代碼應該是這樣的:

web<-read_html(url,encoding="GBK") web1<-html_nodes(web,"b")content1<-html_text(web1,trim = FALSE)content2<-gsub("(\n\t|,|\d|、)","",content1)content3<-grep("\S",content2,value=T)content4<-str_trim(content3,side="both")content5<-content4[1:54]content6<-content5[setdiff(1:54,c(35,39))];content6

一共寫了8行代碼,引入了7個中介變數,浪費了大量內存,整個代碼看著也很辣眼睛。

加入函數嵌套的寫法,以上代碼可以寫成下面的模式:

content<-str_trim(grep("\S",gsub("(\n\t|,|\d|、)","",html_text(html_nodes(read_html(url,encoding="GBK") ,"b"),trim = FALSE)),value=T),side="both")content<-content[1:54]content<-content[setdiff(1:54,c(35,39))];content

函數嵌套確實省去了不少代碼(其實並沒有節省多少,充其量是節省了幾個中介變數的名稱而已,大量的代碼全都嵌套在首句裡面了),但是這樣風格的代碼如何保障一眼就看清楚內部的邏輯。

Name<-read_html(url,encoding="GBK") %>% #讀取url所在的目標網頁 html_nodes("b") %>% #選擇b節點內容 html_text(trim = FALSE) %>% #獲取b節點內的文本(清除空格) gsub("(\n\t|,|\d|、)","",.) %>% #替換掉文本內的所有製表符、標點符號等 grep("\S",.,value=T) %>% #篩選出非空文本 str_trim(side="both") %>% #清除掉文本兩側的空格 .[1:54]%>% #保留字元串向量的1:54個觀測值 .[setdiff(1:54,c(35,39))] #剔除掉其中的第35、39個觀測值

以上代碼使用管道操作函數依次將左側獨享作為參數傳入右側函數內部,層層傳遞,不創建任何中間變數,因而這一段代碼自url輸入起始,到setdiff篩選完畢之後輸出NAME終止,沒有生成任何中間變數(也就意味著沒有浪費任何多餘內存)。從代碼的簡介與優雅程度來看,它也完勝前兩者,因為每一句功能都可以通過%>%看到明顯的輸入輸出,當你回看或者修改時,僅需定位到對應代碼塊調試即可。

以上就是%>%的用法,用一個圖示來表示,可以將其表示如下:

實際上在參數傳遞的細節上,還有很多需要注意的地方:

1、當函數僅需一個必要參數時,則此時函數寫法容忍度非常高,相對自由:

sample(letters[1:5],size=20,replace=TRUE) %>% table() sample(letters[1:5],size=20,replace=TRUE) %>% tablesample(letters[1:5],size=20,replace=TRUE) %>% table(.)a b c d 5 7 1 7

以上三種寫法都可以輸出正確的結果,第一種寫法保留了括弧,第二種寫法省略了括弧,第三種同時保留了括弧和佔位符「.」。

因為table只接受一個位置參數(你也可以理解為必備參數,該參數是一個因子或者類別型變數),從左側由管道操作符傳入的參數就會被作為table的必備參數。

前兩種寫法等價,第三種寫法在括弧內加入了佔位符,這種用法接下來會講到。

2、當函數有一個以上的必備參數(位置參數)時,而且管道函數傳入的參數位於第一個時,可以寫成如下模式:

url %>% read_html(encoding="GBK")url %>% read_html(.,encoding="GBK")

read_html函數中僅有一個位置參數x(必備參數),encoding是一個默認參數,options也是一個默認可選參數。因而x是必備參數,且無需聲明參數名稱。url被%>%傳入read_html之後,默認就被作為x參數的對象。以上兩種寫法等價,第二種寫法聲明了x參數在read_html()函數內的位置。(相當於x參數的佔位符),但是在此種情況下並不必要。(因為x作為第一個位置參數,可以被默認識別出來)

3、當函數有不止一個位置參數(必備參數)時,且左側傳入的對象在右側函數中不是位置排在第一個的,那麼此種情況下必須顯式聲明該參數在右側函數中所處的位置,並且使用「.」作為佔位符佔位。

read_html(url,encoding="GBK") %>% html_nodes("b") %>% html_text(trim = FALSE) %>% gsub("(\n\t|,|\d|、)","" )read_html(url,encoding="GBK") %>% html_nodes("b") %>% html_text(trim = FALSE) %>% gsub("(\n\t|,|\d|、)","", )read_html(url,encoding="GBK") %>% html_nodes("b") %>% html_text(trim = FALSE) %>% gsub("(\n\t|,|\d|、)","",.)

以上代碼中,前兩個是錯誤的,最後一個成功了,原因是gsub函數一共有三個位置參數(必備參數),而我們從左側傳入的那個字元串對象,剛好處於第三個位置參數的位置。如果不做顯式聲明,告訴gsub函數%>%左側傳入對象在右側函數中的具體位置,則函數無法自動識別。

gsub(pattern, replacement, x, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)

以上三點是基於函數式編程中,位置參數傳參必須遵循的規則,其實也是符合現實邏輯的。通過以上三點,我們可以得出的結論:

1、右側函數僅有一個位置參數(必備參數時),可只寫函數名(不用帶括弧)、也可以寫作函數名帶雙括弧,也可以寫作函數名+(.)。以上刪照片那個方式都是合法的,但是後兩種不是必要的,函數可以根據邏輯自動識別。

2、當右側函數有多個位置參數時,需要視左側傳入的參數在右側位置參數中的次序而定,倘若剛好位於右側所有位置參數第一個,則寫法也相對靈活,可以直接忽略掉,只指定其他位置參數和默認參數,倘若位於第一個之後,則必須給出精確的顯式位置聲明,並使用佔位符「.」佔位。

除此之外,管道函數傳參時,也支持傳給數據框的切片索引操作。如下所示:

read_html(url,encoding="GBK") %>% html_nodes("b") %>% html_text(trim = FALSE) %>% gsub("(\n\t|,|\d|、)","",.) %>% grep("\S",.,value=T) %>% str_trim(side="both") %>% .[1:54]

最後一次傳參的時候,左側傳入了一個文本向量,可以像普通場景下的向量下標索引一樣對觀測值進行過濾,此時左側向量名稱可以不用寫出, 用一個佔位符替代即可(這裡的.必不可少)。

magrittr包為了保證管道函數傳參過程更為高效,提供了很多類似%>%的輔助函數:

函數名稱: 函數符號表達式:extract `[`extract2 `[[`inset `[<-`inset2 `[[<-`use_series `$`add `+`subtract `-`multiply_by `*`raise_to_power `^`multiply_by_matrix `%*%`divide_by `/`divide_by_int `%/%`mod `%%`is_in `%in%`and `&`or `|`equals `==`is_greater_than `>`is_weakly_greater_than `>=`is_less_than `<`is_weakly_less_than `<=`not (`nest pas`) `!`set_colnames `colnames<-`set_rownames `rownames<-`set_names `names<-`

以上函數中有我們經常用到的四則運算、邏輯判斷與比較函數、包含關係函數等,也有一些使用頻率不高的冷門函數。我僅取其中常見的幾個進行簡要介紹。

library(「magrittr」)

extract函數等價於 `[`,用於索引數據框中的列:

iris %>% extract(,1:3) %>% headiris %>% `[`(1:3) %>% headiris %>% .[,1:3] %>% head

以上三種方法索引iris前三列並預覽,結果是等價的。

extract2函數等價於`[[`,用於索引列表中的順序對象。

mydata<-list( mmm=1:5, nn=c("dd","fff","ttt"), dd=6:20 )mydata %>% extract2(1)mydata %>% `[[`(1)mydata[[1]]mydata$mmm1 2 3 4 5

以上四種索引方式等價。

good.times<-Sys.Date() %>% as.POSIXct %>% seq(by="15 mins",length.out=100) %>% data.frame(timestamp = .)good.times$quarter<-good.times %>% use_series(timestamp) %>% format("%M") %>% as.numeric %>% divide_by_int(15) %>% add(1)good.times$quarter<-good.times %>% .[,"timestamp"] %>% format("%M") %>% as.numeric %>% `%/%`(15) %>% `+`(1)[1] 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 [61] 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4

以上兩種方式輸出結果等價,特別注意最後的%>% `%/%`(15)函數,%>% `+`(1),前者代表左側傳入值除以15的商的整數部分,後者代表向量統一增加1.

add `+`subtract `-`multiply_by `*`raise_to_power `^`multiply_by_matrix `%*%`divide_by `/`divide_by_int `%/%`mod `%%`

以上是集中簡單幾何運算,均可以通過函數名稱或者符號表達式的方式結合%>%進行傳參,節省代碼,提高效率。

至於其他那些尚未講解到的特殊用法,感興趣可以參考源文檔。

本文參考文獻:

https://cosx.org/2014/04/use-pipeline-operators-in-rhttp://blog.fens.me/r-magrittr/https://cran.r-project.org/web/packages/magrittr/magrittr.pdf往期案例數據請移步本人GitHub:

github.com/ljtyduyu/Dat

在線課程請點擊文末原文鏈接:

edu.hellobi.com/course/

歡迎關注數據小魔方qq交流群


推薦閱讀:

R語言可視化——多邊形與數據地圖填充
第二章 創建數據集
記錄數據可視化的每一個瞬間
快訊| 2017年6月份精選R包
數據地圖多圖層對象的顏色標度重疊問題解決方案

TAG:R编程语言 | 数据分析 | 数据清洗 |