作者:張丹,R語言中文社區專欄特邀作者,《R的極客理想》系列圖書作者,民生銀行大數據中心數據分析師,前況客創始人兼CTO。
個人博客 http://fens.me, Alexa全球排名70k。
前言
使用R語言進行數據處理是非常方便的,幾行代碼就可以完成很複雜的操作。但是,對於數據的連續處理,還是有人覺得代碼不好看,要麼是長長的函數嵌套調用,有點像Lisp感覺,括號包一切;要麼就是每次操作賦值一個臨時變量,囉嗦。為什麼就不能像Linux的管道一樣優雅呢?
magrittr包在這樣場景中被開發出來,通過管道的方式讓連續複雜數據的處理操作,代碼更短,更容易讀,甚至一行代碼可以搞定原來10行代碼的事情。
目錄
magrittr介紹
magrittr安裝
magrittr包的基本使用
magrittr包的擴展功能
magrittr包被定義為一個高效的管道操作工具包,通過管道的連接方式,讓數據或表達式的傳遞更高效,使用操作符%>%,可以直接把數據傳遞給下一個函數調用或表達式。magrittr包的主要目標有2個,第一是減少代碼開發時間,提高代碼的可讀性和維護性;第二是讓你的代碼更短,再短,短短短…
magrittr包,主要定義了4個管道操作符,分另是%>%, %T>%, %$% 和 %<>%。其中,操作符%>%是最常用的,其他3個操作符,與%>%類似,在特殊的使用場景會起到更好的作用。當正確掌握這幾個操作符後,你一定會愛不釋手的,快去把所有的代碼都重構吧,砍掉原來大段冗長的代碼是一件多麼令人激動的事情啊。
magrittr的項目主頁:https://github.com/smbache/magrittr
本文所使用的系統環境
magrittr是在CRAN發布的標準庫,安裝起來非常簡單,2條命令就可以了。
~ R> install.packages('magrittr')> library(magrittr)
對於magrittr包的使用,其實就是掌握這4個操作符的用法,向右操作符%>%, 向左操作符%T>%, 解釋操作符%$% 和 複合賦值操作符%<>%。
3.1 %>% 向右操作符(forward-pipe operator)
%>%是最常用的一個操作符,就是把左側準備的數據或表達式,傳遞給右側的函數調用或表達式進行運行,可以連續操作就像一個鏈條一樣。
現實原理如下圖所示,使用%>%把左側的程序的數據集A傳遞右側程序的B函數,B函數的結果數據集再向右側傳遞給C函數,最後完成數據計算。
比如,我們要做下面的事情。(這是一個YY的需求。)
取10000個隨機數符合,符合正態分布。
求這個10000個數的絕對值,同時乘以50。
把結果組成一個100*100列的方陣。
計算方陣中每行的均值,並四捨五入保留到整數。
把結果除以7求餘數,並話出餘數的直方圖。
我們發現上面的5個過程是連續的,正常的代碼我要怎麼實現呢。
# 設置隨機種子> set.seed(1)# 開始 > n1<-rnorm(10000)# 第1步> n2<-abs(n1)*50 # 第2步> n3<-matrix(n2,ncol = 100) # 第3步> n4<-round(rowMeans(n3)) # 第4步> hist(n4%%7) # 第5步
輸出的直方圖:
上面的代碼寫法是,每一行實現一個條件,但中間多了不少的臨時變量。再看另外一種的寫法,括號包一切。
# 設置隨機種子> set.seed(1)> hist(round(rowMeans(matrix(abs(rnorm(10000))*50,ncol=100)))%%7)
輸出的直方圖:
我分別用兩種常見的代碼風格,實現了我們的需求。再看看%>%的方式,有多麼的不一樣。
# 設置隨機種子> set.seed(1)# 開始> rnorm(10000) %>%+ abs %>% `*` (50) %>%+ matrix(ncol=100) %>%+ rowMeans %>% round %>% + `%%`(7) %>% hist
輸出的直方圖:
一行代碼,不僅搞定所有的事情,而且結構清楚,可讀性非常強。這就是管道代碼風格,帶來的優雅和簡約。
3.2 %T>% 向左操作符(tee operator)
%T>%向左操作符,其實功能和 %>% 基本是一樣的,只不過它是把左邊的值做為傳遞的值,而不是右邊的值。這種情況的使用場景也是很多的,比如,你在數據處理的中間過程,需要列印輸出或圖片輸出,這時整個過程就會被中斷,用向左操作符,就可以解決這樣的問題。
現實原理如下圖所示,使用%T>%把左側的程序的數據集A傳遞右側程序的B函數,,B函數的結果數據集不再向右側傳遞,而是把B左側的A數據集再次向右傳遞給C函數,最後完成數據計算。
我們把上面的需求稍微進行調整,在最後增加一個要求,就會用到向左操作符。
取10000個隨機數符合,符合正態分布。
求這個10000個數的絕對值,同時乘以50。
把結果組成一個100*100列的方陣。
計算方陣中每行的均值,並四捨五入保留到整數。
把結果除以7求餘數,並話出餘數的直方圖。
對餘數求和
由於輸出直方圖後,返回值為空,那麼再繼續管道,就會把空值向右進行傳遞,這樣計算最後一步時就會出錯。這時我們需求的是,把除以7的餘數向右傳遞給最後一步求和,那麼就可以用到 %T>% 了
直接使用%>%向右傳值,出現異常。
> set.seed(1)> rnorm(10000) %>%+ abs %>% `*` (50) %>%+ matrix(ncol=100) %>%+ rowMeans %>% round %>% + `%%`(7) %>% hist %>% sumError in sum(.) : invalid 'type' (list) of argument
使用 %T>% 把左邊的值,再向右傳值,則結果正確。
> rnorm(10000) %>%+ abs %>% `*` (50) %>%+ matrix(ncol=100) %>%+ rowMeans %>% round %>% + `%%`(7) %T>% hist %>% sum[1] 328
3.3 %$% 解釋操作符(exposition pipe-operator)
%$% 的作用是把左側數據的屬性名傳給右側,讓右側的調用函數直接通過名字,就可以獲取左側的數據。比如,我們獲得一個data.frame類型的數據集,通過使用 %$%,在右側的函數中可以直接使用列名操作數據。
現實原理如下圖所示,使用%$%把左側的程序的數據集A傳遞右側程序的B函數,同時傳遞數據集A的屬性名,作為B函數的內部變量方便對A數據集進行處理,最後完成數據計算。
下面定義一個3列10行的data.frame,列名分別為x,y,z,或缺x列大於5的數據集。使用 %$% 把列名x直接傳到右側進行判斷。這裡.代表左側的完整數據對象。一行代碼就實現了需求,而且這裡不需要顯示的定義中間變量。
> set.seed(1)> data.frame(x=1:10,y=rnorm(10),z=letters[1:10]) %$% .[which(x>5),] x y z6 6 -0.8204684 f7 7 0.4874291 g8 8 0.7383247 h9 9 0.5757814 i10 10 -0.3053884 j
如果不使用%$%,我們通常的代碼寫法為:
> set.seed(1)> df<-data.frame(x=1:10,y=rnorm(10),z=letters[1:10])> df[which(df$x>5),] x y z6 6 -0.8204684 f7 7 0.4874291 g8 8 0.7383247 h9 9 0.5757814 i10 10 -0.3053884 j
從代碼中可以發現,通常的寫法是需要定義變量df的,df一共要被顯示的使用3次,就是這一點點的改進,會讓代碼看起來更乾淨。
3.4 %<>% 複合賦值操作符(compound assignment pipe-operator)
%<>%複合賦值操作符, 功能與 %>% 基本是一樣的,對了一項額外的操作,就是把結果寫到左側對象。比如,我們需要對一個數據集進行排序,那麼需要獲得排序的結果,用%<>%就是非常方便的。
現實原理如下圖所示,使用%<>%把左側的程序的數據集A傳遞右側程序的B函數,B函數的結果數據集再向右側傳遞給C函數,C函數結果的數據集再重新賦值給A,完成整個過程。
定義一個符合正態分布的100個隨機數,計算絕對值,並按從小到大的順序排序,獲得並取前10個數字賦值給x。
> set.seed(1)> x<-rnorm(100) %<>% abs %>% sort %>% head(10)> x [1] 0.001105352 0.016190263 0.028002159 0.039240003 0.044933609 0.053805041 0.056128740 [8] 0.059313397 0.074341324 0.074564983
是不是太方便了,一行就實現了一連串的操作。但是這裡同時有一個陷阱,需要注意一下 %<>% 必須要用在第一個管道的對象處,才能完成賦值的操作,如果不是左側第一個位置,那麼賦值將不起作用。
> set.seed(1)> x<-rnorm(100)# 左側第一個位置,賦值成功> x %<>% abs %>% sort %>% head(10)> x [1] 0.001105352 0.016190263 0.028002159 0.039240003 0.044933609 0.053805041 0.056128740 [8] 0.059313397 0.074341324 0.074564983# 左側第二個位置,結果被直接列印出來,但是x的值沒有變> x %>% abs %<>% sort %>% head(10) [1] 0.001105352 0.016190263 0.028002159 0.039240003 0.044933609 0.053805041 0.056128740 [8] 0.059313397 0.074341324 0.074564983> length(x)[1] 10# 左側第三個位置,結果被直接列印出來,但是x的值沒有變> x %>% abs %>% sort %<>% head(10) [1] 0.001105352 0.016190263 0.028002159 0.039240003 0.044933609 0.053805041 0.056128740 [8] 0.059313397 0.074341324 0.074564983> length(x)[1] 10
我們已經了解了magrittr包的4個操作符的使用,除了操作符,我們再看一下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 (`n'est pas`) `!`set_colnames `colnames<-`set_rownames `rownames<-`set_names `names<-`
我們來看一下使用的效果。對一個包括10個隨機數的向量的先*5再+5。
# 使用符號的寫法> set.seed(1)> rnorm(10) %>% `*`(5) %>% `+`(5) [1] 1.8677309 5.9182166 0.8218569 12.9764040 6.6475389 0.8976581 7.4371453 8.6916235 [9] 7.8789068 3.4730581# 使用函數的寫法> set.seed(1)> rnorm(10) %>% multiply_by(5) %>% add(5) [1] 1.8677309 5.9182166 0.8218569 12.9764040 6.6475389 0.8976581 7.4371453 8.6916235 [9] 7.8789068 3.4730581
上面計算結果是完全一樣的,用函數替換了符號。其實,這種轉換的操作在面向對象的封裝時是非常有用的,像hibernate封裝了所有的SQL,XFire封裝了WebServices協議等。
4.2 %>%傳遞到代碼塊
有些時候,我們對同一個數據塊的要進行次行的處理,一條語句是很難完成的,這些就需要一個代碼塊也進行處理。把數據集傳遞到{}代碼塊中,傳入的數據集以.來表示,通過一段代碼來完成操作,而不是一句話完成操作。
比如,對一個包括10個隨機數的向量的先*5再+5,求出向量的均值和標準差,並從小到大排序後返回前5條。
> set.seed(1)> rnorm(10) %>%+ multiply_by(5) %>%+ add(5) %>%+ { + cat("Mean:", mean(.), + "Var:", var(.), "\n")+ sort(.) %>% head+ }Mean: 5.661014 Var: 15.23286 [1] 0.8218569 0.8976581 1.8677309 3.4730581 5.9182166 6.6475389
通過{}包裝的代碼塊,就可以很方便的完成多少處理的複雜操作。
4.3 %>%傳遞到函數
傳遞到函數和傳遞到代碼塊設計是類似的,是把一個數據集傳給一個匿名函數,進行複雜的數據數據的操作。在這裡,我們會顯示的定義數據集的名字作為匿名函數的參數。
比如,對鳶尾花數據集進行處理,只保留第一行和最後一行作為結果。
> iris %>%+ (function(x) {+ if (nrow(x) > 2) + rbind(head(x, 1), tail(x, 1))+ else x+ }) Sepal.Length Sepal.Width Petal.Length Petal.Width Species1 5.1 3.5 1.4 0.2 setosa150 5.9 3.0 5.1 1.8 virginica
這裡x就是iris數據集,作為了函數的顯示參數,被應用於後續的數據處理過程。
通過對magrittr的學習,我們掌握了一些特殊的R語言代碼的編程技巧,用magrittr包寫出的R語言程序,與傳統的R語言代碼是有區別,可以你的程序很簡單、很高效。
天性「懶惰」的程式設計師總是會想各種辦法,來減少自己的代碼,讓代碼變得優雅,同時還能讓程序更可靠。什麼時候能把代碼寫得越來越少,那麼你就越來越接近高手!
R的極客理想:量化投資篇
金融投資學理論
R語言數據處理
量化投資策略
三個維度
教你如何將R語言技術應用於金融市場