1. 程式人生 > >R語言中管道操作 %>%, %T>%, %$% 和 %%

R語言中管道操作 %>%, %T>%, %$% 和 %%

前言

使用R語言進行資料處理是非常方便的,幾行程式碼就可以完成很複雜的操作。但是,對於資料的連續處理,還是有人覺得程式碼不好看,要麼是長長的函式巢狀呼叫,有點像Lisp感覺,括號包一切;要麼就是每次操作賦值一個臨時變數,囉嗦。為什麼就不能像Linux的管道一樣優雅呢?

magrittr包在這樣場景中被開發出來,通過管道的方式讓連續複雜資料的處理操作,程式碼更短,更容易讀,甚至一行程式碼可以搞定原來10行程式碼的事情。

目錄

  1. magrittr介紹
  2. magrittr安裝
  3. magrittr包的基本使用
  4. 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函式,最後完成資料計算。

mag1

比如,我們要做下面的事情。(這是一個YY的需求。)

  1. 取10000個隨機數符合,符合正態分佈。
  2. 求這個10000個數的絕對值,同時乘以50。
  3. 把結果組成一個100*100列的方陣。
  4. 計算方陣中每行的均值,並四捨五入保留到整數。
  5. 把結果除以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步

輸出的直方圖:

01

上面的程式碼寫法是,每一行實現一個條件,但中間多了不少的臨時變數。再看另外一種的寫法,括號包一切。


# 設定隨機種子
> set.seed(1)
> hist(round(rowMeans(matrix(abs(rnorm(10000))*50,ncol=100)))%%7)

輸出的直方圖:

02

我分別用兩種常見的程式碼風格,實現了我們的需求。再看看%>%的方式,有多麼的不一樣。


# 設定隨機種子
> set.seed(1)

# 開始
> rnorm(10000) %>%
+   abs %>% `*` (50)  %>%
+   matrix(ncol=100)  %>%
+   rowMeans %>% round %>% 
+   `%%`(7) %>% hist

輸出的直方圖:

03

一行程式碼,不僅搞定所有的事情,而且結構清楚,可讀性非常強。這就是管道程式碼風格,帶來的優雅和簡約。

3.2 %T>% 向左操作符(tee operator)

%T>%向左操作符,其實功能和 %>% 基本是一樣的,只不過它是把左邊的值做為傳遞的值,而不是右邊的值。這種情況的使用場景也是很多的,比如,你在資料處理的中間過程,需要列印輸出或圖片輸出,這時整個過程就會被中斷,用向左操作符,就可以解決這樣的問題。

現實原理如下圖所示,使用%T>%把左側的程式的資料集A傳遞右側程式的B函式,,B函式的結果資料集不再向右側傳遞,而是把B左側的A資料集再次向右傳遞給C函式,最後完成資料計算。

mag2

我們把上面的需求稍微進行調整,在最後增加一個要求,就會用到向左操作符。

  1. 取10000個隨機數符合,符合正態分佈。
  2. 求這個10000個數的絕對值,同時乘以50。
  3. 把結果組成一個100*100列的方陣。
  4. 計算方陣中每行的均值,並四捨五入保留到整數。
  5. 把結果除以7求餘數,並話出餘數的直方圖。
  6. 對餘數求和

由於輸出直方圖後,返回值為空,那麼再繼續管道,就會把空值向右進行傳遞,這樣計算最後一步時就會出錯。這時我們需求的是,把除以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資料集進行處理,最後完成資料計算。

mag3

下面定義一個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,完成整個過程。

mag4

定義一個符合正態分佈的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語言程式碼是有區別,可以你的程式很簡單、很高效。