R語言中管道操作 %>%, %T>%, %$% 和 %%
前言
使用R語言進行資料處理是非常方便的,幾行程式碼就可以完成很複雜的操作。但是,對於資料的連續處理,還是有人覺得程式碼不好看,要麼是長長的函式巢狀呼叫,有點像Lisp感覺,括號包一切;要麼就是每次操作賦值一個臨時變數,囉嗦。為什麼就不能像Linux的管道一樣優雅呢?
magrittr包在這樣場景中被開發出來,通過管道的方式讓連續複雜資料的處理操作,程式碼更短,更容易讀,甚至一行程式碼可以搞定原來10行程式碼的事情。
目錄
- magrittr介紹
- magrittr安裝
- magrittr包的基本使用
- magrittr包的擴充套件功能
1. magrittr介紹
magrittr包被定義為一個高效的管道操作工具包,通過管道的連線方式,讓資料或表示式的傳遞更高效,使用操作符%>%,可以直接把資料傳遞給下一個函式呼叫或表示式。magrittr包的主要目標有2個,第一是減少程式碼開發時間,提高程式碼的可讀性和維護性;第二是讓你的程式碼更短,再短,短短短…
magrittr包,主要定義了4個管道操作符,分另是%>%, %T>%, %$% 和 %<>%。其中,操作符%>%是最常用的,其他3個操作符,與%>%類似,在特殊的使用場景會起到更好的作用。當正確掌握這幾個操作符後,你一定會愛不釋手的,快去把所有的程式碼都重構吧,砍掉原來大段冗長的程式碼是一件多麼令人激動的事情啊。
2. magrittr安裝
本文所使用的系統環境
- Win10 64bit
- R: 3.2.3 x86_64-w64-mingw32/x64 b4bit
magrittr是在CRAN釋出的標準庫,安裝起來非常簡單,2條命令就可以了。
~ R > install.packages('magrittr') > library(magrittr)
3. 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 %>% sum
Error 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 z
6 6 -0.8204684 f
7 7 0.4874291 g
8 8 0.7383247 h
9 9 0.5757814 i
10 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 z
6 6 -0.8204684 f
7 7 0.4874291 g
8 8 0.7383247 h
9 9 0.5757814 i
10 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
4. magrittr包的擴充套件功能
我們已經瞭解了magrittr包的4個操作符的使用,除了操作符,我們再看一下magrittr還有哪些功能。
- 符號操作符定義
- %>%對程式碼塊的傳遞
-
%>%對函式的傳遞
4.1 符號操作符定義
為了讓鏈條傳遞看起來更友好,magrittr對於常見的計算符號操作符進行的重新定義,讓每個操作都對應用一個函式,這樣所有的傳遞呼叫程式碼都是風格統一的。比如,add()函式和`+`是等價的。
下面列出對應的列表:
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 Species 1 5.1 3.5 1.4 0.2 setosa 150 5.9 3.0 5.1 1.8 virginica
這裡x就是iris資料集,作為了函式的顯示引數,被應用於後續的資料處理過程。
通過對magrittr的學習,我們掌握了一些特殊的R語言程式碼的程式設計技巧,用magrittr包寫出的R語言程式,與傳統的R語言程式碼是有區別,可以你的程式很簡單、很高效。