有没有办法在 R 中使用 tryCatch(或类似的)作为循环,或者在警告参数中操纵 expr?

Is there a way to use tryCatch (or similar) in R as a loop, or to manipulate the expr in the warning argument?

我有一个回归模型(lmglmlmer ...)并且我做 fitmodel <- lm(inputs) 其中 inputs 在循环内发生变化(公式和数据)。然后,如果模型函数没有产生任何警告,我想保留 fitmodel,但如果我收到警告,我想 update 模型并且我想要警告 not 打印出来,所以我在 tryCatch 里面做了 fitmodel <- lm(inputs)。因此,如果它产生警告,在 warning = function(w){f(fitmodel)}f(fitmodel) 内将类似于

fitmodel <- update(fitmodel, something suitable to do on the model)

事实上,这个赋值将在 if-else 结构中,根据警告 if(w$message satisfies something) 我会在 update 中调整 suitable to do on the model

问题是我得到 Error in ... object 'fitmodel' not found。如果我将 withCallingHandlersinvokeRestarts 一起使用,它只会在没有 update 警告的情况下完成模型的计算。如果我在 something suitable to do on the model 中再次添加 fitmodel <- lm(inputs),我会打印警告;现在我想我可以尝试 suppresswarnings(fitmodel <- lm(inputs)),但我认为这不是一个优雅的解决方案,因为我必须将行 fitmodel <- lm(inputs) 添加 2 倍,使所有计算成为 2 倍(在 [=36= 内) ] 和内部 warning).

总结一下,我想要但失败的是:

tryCatch(expr = {fitmodel <- lm(inputs)},
         warning = function(w) {if (w$message satisfies something) {
                                    fitmodel <- update(fitmodel, something suitable to do on the model)
                                } else if (w$message satisfies something2){
                                    fitmodel <- update(fitmodel, something2 suitable to do on the model)

                                }
         }
)

我能做什么?

问题的循环部分是因为我认为它如下(也许是另一个问题,但目前我把它留在这里):可能会在 update 之后收到另一个警告,所以我会做类似 while(get a warning on update){update} 的事情;在某种程度上,warning 中的 update 也应该理解为 expr。这样的事情可能吗?

非常感谢!


带有最少示例的问题的通用版本:

假设我有一个 tryCatch(expr = {result <- operations}, warning = function(w){f(...)},如果我在 expr 中收到警告(实际上是在 operations 中生成的),我想用 result 做一些事情,所以我会做 warning = function(w){f(result)},但后来我得到 Error in ... object 'result' not found.

一个最小的例子:

y <- "a"
tryCatch(expr = {x <- as.numeric(y)},
    warning = function(w) {print(x)})
Error in ... object 'x' not found

我尝试使用 withCallingHandlers 而不是 tryCatch 但没有成功,并且还使用 invokeRestart 但它执行表达式部分,而不是我收到警告时想要执行的操作。

你能帮帮我吗?

谢谢!

也许你可以在处理条件中再次分配x

tryCatch(
  warning = function(cnd) {
    x <- suppressWarnings(as.numeric(y))
    print(x)},
  expr = {x <- as.numeric(y)}
)
#> [1] NA

也许不是最优雅的答案,但可以解决您的玩具示例。

不要把赋值放在tryCatch调用里面,放在外面。例如,

y <- "a"
x <- tryCatch(expr = {as.numeric(y)},
    warning = function(w) {y})

这会将 y 分配给 x,但您可以在警告正文中放入任何内容,结果将分配给 x

您的“我想要的”示例更复杂,因为您想要访问 expr 值,但在生成警告时尚未在任何地方分配它。我认为你必须重新计算它:

fitmodel <- tryCatch(expr = {lm(inputs)},
         warning = function(w) {if (w$message satisfies something) {
                                    update(lm(inputs), something suitable to do on the model)
                                } else if (w$message satisfies something2){
                                    update(lm(inputs), something2 suitable to do on the model)

                                }
         }
)

编辑添加:

要让评估在处理警告之前继续完成,您不能使用 tryCatchevaluate 包有一个函数(也称为 evaluate)可以做到这一点。例如,

y <- "a"
res <- evaluate::evaluate(quote(x <- as.numeric(y)))
for (i in seq_along(res)) {
    if (inherits(res[[i]], "warning") && 
        conditionMessage(res[[i]]) == gettext("NAs introduced by coercion",
                                              domain = "R"))
        x <- y
}

一些注意事项:res 列表将包含许多不同的内容,包括消息、警告、错误等。我的代码只查看警告。我使用 conditionMessage 来提取警告消息,但是 它将被翻译成当地语言,因此您应该使用 gettext 翻译英文版本的消息以进行比较。

您是否在寻找类似以下的内容?如果是 运行 和 y <- "123",将打印 "OK" 消息。

y <- "a"
#y <- "123"
x <- tryCatch(as.numeric(y),
              warning = function(w) w
)
if(inherits(x, "warning")){
  message(x$message)
} else{
  message(paste("OK:", x))
}

将上面的代码重写为函数,可以更轻松地测试多个参数值。

testWarning <- function(x){
  out <- tryCatch(as.numeric(x),
                  warning = function(w) w
  )
  if(inherits(out, "warning")){
    message(out$message)
  } else{
    message(paste("OK:", out))
  }
  invisible(out)
}

testWarning("a")
#NAs introduced by coercion
testWarning("123")
#OK: 123

您似乎正在寻找一个函数包装器,它可以捕获函数调用的返回值和副作用。我认为 purrr::quietly 是此类任务的完美人选。考虑这样的事情

quietly <- purrr::quietly

foo <- function(x) {
  if (x < 3)
    warning(x, " is less than 3")
  if (x < 4)
    warning(x, " is less than 4")
  x
}

update_foo <- function(x, y) {
  x <- x + y
  foo(x)
}

keep_doing <- function(inputs) {
  out <- quietly(foo)(inputs)
  repeat {
    if (length(out$warnings) < 1L)
      return(out$result)
    
    cat(paste0(out$warnings, collapse = ", "), "\n")
    # This is for you to see the process. You can delete this line.
    
    if (grepl("less than 3", out$warnings[[1L]])) {
      out <- quietly(update_foo)(out$result, 1.5)
    } else if (grepl("less than 4", out$warnings[[1L]])) {
      out <- quietly(update_foo)(out$result, 1)
    }
  }
}

输出

> keep_doing(1)
1 is less than 3, 1 is less than 4 
2.5 is less than 3, 2.5 is less than 4 
[1] 4

> keep_doing(3)
3 is less than 4 
[1] 4

从根本上说,问题是在分配发生之前调用了处理程序。即使不是这种情况,处理程序在与 tryCatch 表达式不同的范围内运行,因此处理程序无法访问其他范围内的名称。

我们需要将处理与值转换分开。

对于错误(但不是警告),基础 R 提供了函数 try,它包装了 tryCatch 以实现此效果。但是,不鼓励使用 try,因为它的 return 类型是 unsound.1 As mentioned , ‘purrr’ provides soundly typed functional wrappers(例如 safely)以达到类似的效果。

但是,我们也可以构建自己的,这可能更适合这种情况:

with_warning = function (expr) {
    self = environment()
    warning = NULL

    result = withCallingHandlers(expr, warning = function (w) {
        self$warning = w
        tryInvokeRestart('muffleWarning')
    })
    list(result = result, warning = warning)
}

这为我们提供了一个区分结果值和警告的包装器。我们现在可以使用它来实现您的要求:

fitmodel = with(with_warning(lm(inputs)), {
    if (! is.null(warning)) {
        if (conditionMessage(warning) satisfies something) {
            update(result, something suitable to do on the model)
        } else {
            update(result, something2 suitable to do on the model)
        }
    } else {
        result
    }
})

1 这意味着 try 的 return 类型不区分类型 [ 的错误值和非错误值=18=]。这是可能发生的真实情况,例如,嵌套多个 try 调用时。