purrr 0.2.0

purrr 0.2.0

Hadley Wickham

2016-01-06

Categories: Packages tidyversegit

原文地址github

我很高興的發佈了purrr 0.2.0。Purrr填補了R的函數式編程工具中的缺失部分,讓你的函數更加純粹、類型穩定。編程

我仍然在研究purrr應該作什麼,以及它如何與基礎R、dplyr, tidyr的現有功能進行比較。一個主要的觀點影響了當前的版本那就是:爲編程設計的函數應該是類型穩定的。類型穩定性是一個來自Julia的概念,並引發了個人注意。儘管R和Julia中的函數能夠返回不一樣類型的輸出,但總的來講,您應該努力讓函數老是返回相同類型數據結構。這使得函數對變化的輸入更加穩健,而且使得它們合理。(可是並非每一個函數都能是類型穩定的)安全

Purrr 0.2.0 爲map,flattens和try()添加了類型穩定的替代方法,以下所述。還有不少其餘的小改進,錯誤修復和一些棄用。請參閱發佈說明以獲取完整的更改列表。數據結構

類型穩定的映射

map是一個讓函數做用在每一個向量上的函數。在基礎R中的map函數有*apply族:lapply(),sapply(),vapply等。lapply()是一個類型穩定的函數:不管輸入什麼,都將返回一個列表。sapply不是一個類型穩定函數,它會根據輸入返回不一樣的類型的輸出。下面的代碼會向你展現一個簡單的sapply的例子,它會根據他的輸入返回一個向量,矩陣或者列表。app

df <- data.frame(
  a = 1L,
  b = 1.5,
  y = Sys.time(),
  z = ordered(1)
)

df[1:4] %>% sapply(class) %>% str()
#> List of 4
#>  $ a: chr "integer"
#>  $ b: chr "numeric"
#>  $ y: chr [1:2] "POSIXct" "POSIXt"
#>  $ z: chr [1:2] "ordered" "factor"
df[1:2] %>% sapply(class) %>% str()
#>  Named chr [1:2] "integer" "numeric"
#>  - attr(*, "names")= chr [1:2] "a" "b"
df[3:4] %>% sapply(class) %>% str()
#>  chr [1:2, 1:2] "POSIXct" "POSIXt" "ordered" "factor"
#>  - attr(*, "dimnames")=List of 2
#>   ..$ : NULL
#>   ..$ : chr [1:2] "y" "z"

這種行爲使得sapply()適合於交互式使用,由於它一般會正確地猜想並給出一個有用的數據結構。
但在包或生產代碼中使用它是不合適的,由於若是輸入不是你所指望的,它將不會失敗,而是會返回一個意外的數據結構。這一般會在整個過程當中致使進一步的錯誤,因此你會收到使人困惑的錯誤消息,而且很難找出根本緣由。函數式編程

基礎R有一個sapply()的類型穩定版本叫作vapply()。它須要一個額外的參數來決定輸出結果。purrr則是採用不一樣的方法,purrr有多個函數而不是一個函數作全部的事情,每一個函數對應一種常見的輸出類型: map_lgl(), map_int(), map_dbl(), map_chr(), and map_df()函數

  • int,表明integer
  • dbl,表明double
  • chr,表明character向量或字符串。
  • dttm,表明日期+時間(a date + a time)
  • lgl,表明邏輯判斷TRUE或者FALSE
  • fctr,表明因子類型factor
  • date,表明日期dates.

這些要麼產生指定類型的輸出要麼拋出一個錯誤。 這迫使你當即處理這個問題:工具

df[1:4] %>% map_chr(class)
#> Error: Result 3 is not a length 1 atomic vector
df[1:4] %>% map_chr(~ paste(class(.), collapse = "/"))
#>                a                b                y                z
#>        "integer"        "numeric" "POSIXct/POSIXt" "ordered/factor"

map()的其餘變體具備類似的後綴。 例如,map2()容許你並行地迭代兩個向量:atom

x <- list(1, 3, 5)
y <- list(2, 4, 6)
map2(x, y, c)
#> [[1]]
#> [1] 1 2
#>
#> [[2]]
#> [1] 3 4
#>
#> [[3]]
#> [1] 5 6

map2()老是返回一個列表。若是要將相應的值相加,並將結果存儲爲double類型的向量,你可使用 map2_dbl()

map2_dbl(x, y, `+`)
#> [1]  3  7 11

另外一個map變體是invoke_map(),它接受函數列表和參數列表。 它也有類型穩定的後綴:

#IQR爲四分位距,mad爲中位數絕對誤差
spread <- list(sd = sd, iqr = IQR, mad = mad)
x <- rnorm(100)

invoke_map_dbl(spread, x = x)
#>        sd       iqr       mad
#> 0.9121309 1.2515807 0.9774154

Type-stable flatten

當類型穩定性很重要時,另外一種狀況是將嵌套列表展平爲更簡單的數據結構。基礎R有unlist函數,但它是危險的,由於它老是成功的。做爲替代,purrr提供了flatten_lgl(), flatten_int(), flatten_dbl(), 和 flatten_chr():

x <- list(1L, 2:3, 4L)
x %>% str()
#> List of 3
#>  $ : int 1
#>  $ : int [1:2] 2 3
#>  $ : int 4
x %>% flatten() %>% str()
#> List of 4
#>  $ : int 1
#>  $ : int 2
#>  $ : int 3
#>  $ : int 4
x %>% flatten_int() %>% str()
#>  int [1:4] 1 2 3 4

Type-stable try()

另外一個在基礎R中的非類型穩定函數是 trytry()確保表達式老是成功,返回原始值或錯誤消息:

str(try(log(10)))
#>  num 2.3
str(try(log("a"), silent = TRUE))
#> Class 'try-error'  atomic [1:1] Error in log("a") : non-numeric argument to mathematical function
#>
#>   ..- attr(*, "condition")=List of 2
#>   .. ..$ message: chr "non-numeric argument to mathematical function"
#>   .. ..$ call   : language log("a")
#>   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

safely()是一個類型穩定版本的try()。它老是返回2個元素:結果與錯誤,有一個總爲NULL(有結果則不報錯,報錯則沒結果)

safely(log)(10)
#> $result
#> [1] 2.302585
#>
#> $error
#> NULL
safely(log)("a")
#> $result
#> NULL
#>
#> $error
#> <simpleError in .f(...): non-numeric argument to mathematical function>

注意到safely()將一個函數做爲輸入,並返回一個「安全」函數,這個函數永遠不會拋出錯誤。一個強大的技術是地使用safely()map()一塊兒嘗試對列表中的每一個元素進行操做:

#注意safely()是將函數做爲輸入,故若對多個值進行操做,可用map
safe_sqrt <- safely(sqrt, otherwise = NA_real_)
numbers_with_error <- list(1, 2, 3, "spam", 4)
map(numbers_with_error, safe_sqrt)%>%transpose()
safe_log <- safely(log)
x <- list(10, "a", 5)
log_x <- x %>% map(safe_log)

str(log_x)
#> List of 3
#>  $ :List of 2
#>   ..$ result: num 2.3
#>   ..$ error : NULL
#>  $ :List of 2
#>   ..$ result: NULL
#>   ..$ error :List of 2
#>   .. ..$ message: chr "non-numeric argument to mathematical function"
#>   .. ..$ call   : language .f(...)
#>   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
#>  $ :List of 2
#>   ..$ result: num 1.61
#>   ..$ error : NULL

這是輸出稍微不方便,由於你更想有一個三個結果的列表,另外一個三個錯誤的列表。 您可使用新的transpose()函數來切換層次結構中第一個和第二個層次的順序:

log_x %>% transpose() %>% str()
#> List of 2
#>  $ result:List of 3
#>   ..$ : num 2.3
#>   ..$ : NULL
#>   ..$ : num 1.61
#>  $ error :List of 3
#>   ..$ : NULL
#>   ..$ :List of 2
#>   .. ..$ message: chr "non-numeric argument to mathematical function"
#>   .. ..$ call   : language .f(...)
#>   .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
#>   ..$ : NULL

這樣能夠很容易地提取原始函數失敗的輸入,或者保存成功結果:

keep()保留TRUE

discard()保留FALSE

results <- x %>% map(safe_log) %>% transpose()

(ok <- results$error %>% map_lgl(is_null))
#> [1]  TRUE FALSE  TRUE
(bad_inputs <- x %>% discard(ok))
#> [[1]]
#> [1] "a"
(successes <- results$result %>% keep(ok) %>% flatten_dbl())
#> [1] 2.302585 1.609438

通常分3步:

  1. 保留轉化好的含有result 與 error 的列表
  2. 使用map_lgl(is_null)獲取邏輯向量
  3. 根據邏輯向量與列表獲得想要的數
相關文章
相關標籤/搜索