使用 purrr::possibly 捕捉动态错误信息

Using purrr::possibly to catch dynamic error messages

我编写了一个自定义函数,该函数执行多项检查并在检查失败时抛出不同的错误。 下面是一个简单的示例函数,它接受一个 data.frame 和一个列名,并简单地输出该列的总和。我正在使用 purrr::possibly() 创建该函数的保存版本,以便我可以遍历列名向量。

foo <- function(df, var){     

  #check 1  
if(var %in% names(df) == FALSE){
    stop(paste0("No column with name ", var, " found."))}  

  #check 2
if(all(is.na(dplyr::select(df, {{var}})))) {
   stop(paste0("All values of column ", var, " are missing."))}
  
  # main function  
  result <- df %>% 
    dplyr::rename(var = {{var}}) %>%
    dplyr::summarise(sum = sum(var))

#print(result) printing shows the correct error messages   
}

safer_foo <- purrr::possibly(.f = foo, otherwise = "error", quiet = FALSE)

我使用 purrr::map 遍历列向量并将输出存储在列表中。但是,对于函数失败的元素,我想存储特定的错误消息,而不是 purrr::possibly 要求的“否则”参数的静态输入。 将 purrr::possibly 替换为 purrr::safely 实际上会捕获列表的 $error 元素中预期的特定错误消息,但我想避免安全创建的额外嵌套级别。

test_df <- tibble(A = 1:10, C = NA)
input <- c("A", "B", "C")

output_list <- map(input, ~safer_foo(test_df, .x)) %>% set_names(input)

输出

> output_list

    sum
  <int>
1    55

$B
[1] "error"

$C
[1] "error"

期望的输出

> output_list

    sum
  <int>
$A   55

$B
[1] "Error: No column with name B found."

$C
[1] "Error: All values of column C are missing."

您可以将 purrr::possibly() 从其原始代码调整为 return 而不是 message 错误。

原代码:

## > possibly
## function (.f, otherwise, quiet = TRUE) 
## {
##     .f <- as_mapper(.f)
##     force(otherwise)
##     function(...) {
##         tryCatch(.f(...), error = function(e) {
##             if (!quiet) 
##                 message("Error: ", e$message) ## <--- tweak
##             otherwise
##         }, interrupt = function(e) {
##             stop("Terminated by user", call. = FALSE)
##         })
##     }
## }

调整函数:

possibly2 <- function (.f, otherwise, quiet = TRUE) {
    .f <- as_mapper(.f)
    force(otherwise)
    function(...) {
        tryCatch(.f(...), error = function(e) {
            if (!quiet) 
                return(e$message) ## <-- tweaked
            otherwise
        }, interrupt = function(e) {
            stop("Terminated by user", call. = FALSE)
        })
    }
}

示例:

safer_foo <- possibly2(.f = foo, otherwise = "error",
                       quiet = FALSE ## don't forget to "unquiet"
                       )

## all other objects / code as in your example

输出:

## > output_list
## $A
## # A tibble: 1 x 1
##     sum
##   <int>
## 1    55
## 
## $B
## [1] "No column with name B found."
## 
## $C
## [1] "All values of column C are missing."

编辑

实际上,possibly2 继承了不再需要的代码。省略 不需要的静态参数 otherwisequiet,并跳过用户中断的处理程序,所需代码缩减为:

possibly2 <- function (.f) {
    .f <- as_mapper(.f)
    function(...) {
        tryCatch(.f(...), error = function(e)  e$message)
    }
}