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()
。函數
這些要麼產生指定類型的輸出要麼拋出一個錯誤。 這迫使你當即處理這個問題:工具
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
當類型穩定性很重要時,另外一種狀況是將嵌套列表展平爲更簡單的數據結構。基礎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
try()
另外一個在基礎R中的非類型穩定函數是 try
。try()
確保表達式老是成功,返回原始值或錯誤消息:
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步:
map_lgl(is_null)
獲取邏輯向量