Existe uma maneira de usar tryCatch (ou semelhante) em R como um loop, ou manipular o expr no argumento de aviso?
Eu tenho um modelo de regressão ( lm
ou glm
ou lmer
...) e faço fitmodel <- lm(inputs)
onde as inputs
mudanç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 update
o 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-else
estrutura de tal forma que dependendo do aviso if(w$message satisfies something)
eu adaptaria o suitable to do on the model
interior update
.
O problema é que eu entendo Error in ... object 'fitmodel' not found
. Se eu usar withCallingHandlers
com invokeRestarts
, ele apenas conclui o cálculo do modelo com o aviso sem update
ele. 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 expr
e 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 update
eu receba outro aviso, então eu faria algo como while(get a warning on update){update}
; de alguma forma, esse update
interior warning
deve 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 withCallingHandlers
vez de tryCatch
sem sucesso, e também usando, invokeRestart
mas faz a parte da expressão, não o que eu quero fazer quando recebo um aviso.
Você poderia me ajudar?
Obrigado!
Respostas
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 tryCatch
expressã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 tryCatch
para 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 try
o 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 try
chamadas.
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
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
Talvez você pudesse atribuir x
novamente 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.
Não coloque a tarefa na tryCatch
chamada, coloque-a do lado de fora. Por exemplo,
y <- "a"
x <- tryCatch(expr = {as.numeric(y)},
warning = function(w) {y})
Isso atribui y
a 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 expr
valor, 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 evaluate
pacote 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 res
lista conterá muitas coisas diferentes, incluindo mensagens, avisos, erros, etc. Meu código só olha os avisos. Eu costumava conditionMessage
extrair a mensagem de aviso, mas ela será traduzida para o idioma local, então você deve usar gettext
para traduzir a versão em inglês da mensagem para comparação.