当前位置: 首页 > 工具软件 > Purrr > 使用案例 >

purrr 0.2.0

章城
2023-12-01

purrr 0.2.0

Hadley Wickham

2016-01-06

Categories: Packages tidyverse

原文地址

我很高兴的发布了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的例子,它会根据他的输入返回一个向量,矩阵或者列表。

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()允许你并行地迭代两个向量:

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. 根据逻辑向量与列表得到想要的数
 类似资料:

相关阅读

相关文章

相关问答