Existe uma maneira de usar tryCatch (ou semelhante) em R como um loop, ou manipular o expr no argumento de aviso?

Nov 27 2020

Eu tenho um modelo de regressão ( lmou glmou lmer...) e faço fitmodel <- lm(inputs)onde as inputsmudanças dentro de um loop (a fórmula e os dados). Então, se a função modelo não produzir nenhum aviso eu quero manter fitmodel, mas se eu receber um aviso eu quero para updateo modelo e quero que o aviso não seja impresso, então eu faço por fitmodel <- lm(inputs)dentro tryCatch. Portanto, se produzir um aviso, por dentro warning = function(w){f(fitmodel)}, f(fitmodel)seria algo como

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

Na verdade, esta atribuição estaria dentro de uma if-elseestrutura de tal forma que dependendo do aviso if(w$message satisfies something)eu adaptaria o suitable to do on the modelinterior update.

O problema é que eu entendo Error in ... object 'fitmodel' not found. Se eu usar withCallingHandlerscom invokeRestarts, ele apenas conclui o cálculo do modelo com o aviso sem updateele. Se eu adicionar novamente fitmodel <- lm(inputs)dentro something suitable to do on the model, recebo o aviso impresso; agora acho que poderia tentar suppresswarnings(fitmodel <- lm(inputs)), mas ainda acho que não é uma solução elegante, já que tenho que somar 2 vezes a linha fitmodel <- lm(inputs), fazendo 2 vezes todo o cálculo (interno expre interno warning).

Resumindo, o que eu gostaria, mas falhava:

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)

                                }
         }
)

O que eu posso fazer?

A parte do loop da pergunta é porque eu pensei assim (talvez seja outra pergunta, mas por enquanto deixo aqui): pode acontecer que depois de updateeu receba outro aviso, então eu faria algo como while(get a warning on update){update}; de alguma forma, esse updateinterior warningdeve ser entendido também como expr. Algo assim é possível?

Muito obrigado!


Versão genérica da pergunta com exemplo mínimo:

Digamos que eu tenha um tryCatch(expr = {result <- operations}, warning = function(w){f(...)}e se receber um aviso em expr(produzido de fato em operations) quero fazer algo com result, então faria warning = function(w){f(result)}, mas então recebo Error in ... object 'result' not found.

Um exemplo mínimo:

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

Tentei usar em withCallingHandlersvez de tryCatchsem sucesso, e também usando, invokeRestartmas faz a parte da expressão, não o que eu quero fazer quando recebo um aviso.

Você poderia me ajudar?

Obrigado!

Respostas

2 KonradRudolph Nov 28 2020 at 14:24

O problema, fundamentalmente, é que o manipulador é chamado antes que a atribuição aconteça. E mesmo se esse não fosse o caso, o manipulador é executado em um escopo diferente do da tryCatchexpressão, portanto, o manipulador não pode acessar os nomes no outro escopo.

Precisamos separar o manuseio da transformação de valor.

Para erros (mas não avisos), a base R fornece a função try, que encapsula tryCatchpara obter esse efeito. No entanto, o uso tryé desencorajado, porque seu tipo de retorno é incorreto . 1 Conforme mencionado na resposta de ekoam , 'purrr' fornece invólucros funcionais devidamente digitados (por exemplo safely) para obter um efeito semelhante.

No entanto, também podemos construir o nosso próprio, o que pode ser mais adequado nesta situação:

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

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

Isso nos dá um invólucro que distingue entre o valor do resultado e um aviso. Agora podemos usá-lo para implementar seus requisitos:

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 O que isso significa é que tryo tipo de retorno de não distingue entre um erro e um valor de tipo sem erro try-error. Esta é uma situação real que pode ocorrer, por exemplo, ao aninhar várias trychamadas.

2 ekoam Nov 27 2020 at 11:37

Parece que você está procurando um wrapper funcional que capture o valor retornado e os efeitos colaterais de uma chamada de função. Acho que purrr::quietlyé um candidato perfeito para esse tipo de tarefa. Considere algo assim

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)
    }
  }
}

Resultado

> 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
1 RuiBarradas Nov 27 2020 at 10:38

Você está procurando algo como o seguinte? Se for executado com y <- "123", a "OK"mensagem será impressa.

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))
}

É mais fácil testar vários valores de argumento com o código acima reescrito como uma função.

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
JoaoPedroMacalos Nov 27 2020 at 10:04

Talvez você pudesse atribuir xnovamente na condição de manuseio?

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

Talvez não seja a resposta mais elegante, mas resolve seu exemplo de brinquedo.

user2554330 Nov 27 2020 at 10:05

Não coloque a tarefa na tryCatchchamada, coloque-a do lado de fora. Por exemplo,

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

Isso atribui ya x, mas você pode colocar qualquer coisa no corpo do aviso e o resultado será atribuído x.

Seu exemplo "o que eu gostaria" é mais complicado, porque você deseja acessar o exprvalor, mas ele não foi atribuído em nenhum lugar no momento em que o aviso é gerado. Acho que você terá que recalcular isso:

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)

                                }
         }
)

Editado para adicionar:

Para permitir que a avaliação prossiga até a conclusão antes de processar o aviso, você não pode usar tryCatch. O evaluatepacote tem uma função (também chamada evaluate) que pode fazer isso. Por exemplo,

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
}

Algumas notas: a reslista conterá muitas coisas diferentes, incluindo mensagens, avisos, erros, etc. Meu código só olha os avisos. Eu costumava conditionMessageextrair a mensagem de aviso, mas ela será traduzida para o idioma local, então você deve usar gettextpara traduzir a versão em inglês da mensagem para comparação.