【R資料科學讀書筆記】R語言中的管道操作

weixin_34253539發表於2018-08-26

R語言中的管道操作

這是R資料科學的讀書筆記之一,《R資料科學》是一本教你如何用R語言進行資料分析的書。即便我使用R語言快2年多了,但是讀這本書還是受益頗多。

這一篇學習筆記對應第13章:使用magrittr進行管道操作。關於管道這個概念,我最早在Linux系統中接觸,它是Unix系統設計哲學的體現,“組合小功能完成大任務”,比如說BWA比對後排序用管道的寫法就是

bwa mem ref 1.fq 2.fq | samtools sort > align.bam

在R語言接觸管道符號"%>%"是在學習dplyr包時候,那個時候我以為這個符號是 Hadley Wickham 創造出來的,其實是來源於Stefan Milton Bache開發的magrittr中。

基礎部分

在沒有管道符號之前,如果我要對一個變數做一系列的分析的話,那麼寫法是下面這個樣子

# 先建立100個隨機數
nums <- rnorm(100) 
# 分成兩列
nums_matrix <- matrix(nums, ncol = 2)
# 分別求兩列的均值
nums_mean <- Matrix::colMeans(nums_matrix)

這裡面我寫了很多中間變數,要多敲很多字,而且如果我要修改輸入的話的100個隨機數的話,我需要修改兩處。當然可以進行函式巢狀.

Matrix::colMeans(matrix(rnorm(100), ncol=2))

但是這種寫法不利於人的閱讀,當我讀到這個函式的時候,我需要先連續往大腦裡塞進去兩個函式後,才能抵達核心,然後再從裡往外解析。

但是有了管道符號之後一切就不一樣了,寫法就是

rnorm(100) %>% matrix(ncol=2) %>% Matrix::colMeans()

你會發現從左往右閱讀,程式碼讀起來非常的流暢。

雖然管道看起來很美好,但是在如下的場景中就不太適合了,

  • 操作步驟特別的多,比如說10個,那麼你就需要用一些有意義的中間變數來存放中間結果,方便除錯
  • 多輸入多輸出。比如說A和B輸入,輸出C和D
  • 操作步驟構成了一張複雜關係的有向圖,比如說D結果依賴於B和C,而B和C依賴於A。

簡單點說,就是類似於A > B > C > D 這種場景用管道比較好。

除了%>%這個好用的符號外,magrittr還提供了其他三個比較好用的符號,%$%%<>%%T>%

高階部分

上面都是常規操作,作為有一定基礎的R語言使用者,更希望探索點這個符號的本質。

首先明確一點,在R語言中一切符號本質上都是函式,比如說"+"也是一個函式,常規用法都是1 + 2, 但是我們可以用函式的方式來寫哦

`+`(4,5)
# 9

因此rnorm(100) %>% matrix(ncol=2)其實應該理解成

`%>%`(rnorm(100), matrix(ncol=2))

那麼我們就可以看看管道符號的原始碼了

?magrittr::`%>%`
function (lhs, rhs) 
{
    parent <- parent.frame()
    env <- new.env(parent = parent)
    chain_parts <- split_chain(match.call(), env = env)
    pipes <- chain_parts[["pipes"]]
    rhss <- chain_parts[["rhss"]]
    lhs <- chain_parts[["lhs"]]
    env[["_function_list"]] <- lapply(1:length(rhss), function(i) wrap_function(rhss[[i]], 
        pipes[[i]], parent))
    env[["_fseq"]] <- `class<-`(eval(quote(function(value) freduce(value, 
        `_function_list`)), env, env), c("fseq", "function"))
    env[["freduce"]] <- freduce
    if (is_placeholder(lhs)) {
        env[["_fseq"]]
    }
    else {
        env[["_lhs"]] <- eval(lhs, parent, parent)
        result <- withVisible(eval(quote(`_fseq`(`_lhs`)), env, 
            env))
        if (is_compound_pipe(pipes[[1L]])) {
            eval(call("<-", lhs, result[["value"]]), parent, 
                parent)
        }
        else {
            if (result[["visible"]]) 
                result[["value"]]
            else invisible(result[["value"]])
        }
    }
}

這個程式碼的核心在於如下兩行

env[["_function_list"]] <- lapply(1:length(rhss), function(i) wrap_function(rhss[[i]], 
        pipes[[i]], parent))
env[["_fseq"]] <- `class<-`(eval(quote(function(value) freduce(value, 
        `_function_list`)), env, env), c("fseq", "function"))

這兩行乾的活其實是進行詞法轉換,也就是把我們之前的管道串聯起來的部分轉換成

my_pipe <- function(.){
    . <- rnorm(.) 
    . <- matrix(., ncol = 2)
    . <- Matrix::colMeans(.)
}
my_pipe(100)

多說兩句

考慮到在管道里面用"+"."-"這些函式時用到`+`或許會有點詭異,於是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<-`

相關文章