Tratar erros no R é importante para identificar problemas nos códigos e evitar retrabalho. Quem nunca rodou um algoritmo pesadíssimo que deu errado na última iteração? Nesse artigo, veremos como trabalhar com erros no R e a versão tidy
dessas soluções.
Usando try()
e tryCatch()
A forma tradicional de tratar erros no R é com a função tryCatch()
. Essa função tem como primeiro argumento uma expressão a ser avaliada e argumentos diversos para trabalhar com os erros. A versão mais compacta do tryCatch()
é escrita assim:
tryCatch(sqrt(1), error = function(e) e)
## [1] 1
tryCatch(sqrt('a'), error = function(e) e)
## <simpleError in sqrt("a"): non-numeric argument to mathematical function>
O try()
é uma simplificação de tryCatch()
que assume que não estamos interessados no erro, mas sim no resultado da função quando ela dá certo. O código abaixo não trava:
try(sqrt(1))
## [1] 1
try(sqrt('a'))
## Error in sqrt("a") : non-numeric argument to mathematical function
## Error in sqrt("a") : non-numeric argument to mathematical function
Existe até mesmo uma versão quieta do try()
, usando o parâmetro silent =
. Quando a expressão dá um erro, o try()
retorna a mensagem de erro de forma invisível, ou seja, sem mostrar explicitamente para o usuário.
x <- try(sqrt('a'), silent = TRUE)
x
## [1] "Error in sqrt(\"a\") : non-numeric argument to mathematical function\n"
## attr(,"class")
## [1] "try-error"
## attr(,"condition")
## <simpleError in sqrt("a"): non-numeric argument to mathematical function>
Usando advérbios do purrr
Hoje em dia, o jeito mais arrumado de tratar erros é usando as funções purrr::possibly()
e suas amigas, quietly()
e safely()
. Note que todas essas palavras são advérbios: o objetivo delas é alterar o comportamento de outros verbos (outras funções). Essa forma de pensar nos nomes das funções (funções são verbos, modificadores de funções são advérbios) faz parte do princípio tidy.
safely()
retorna uma lista com elementosresult
eerror
. Quando a função não dá erro,error
fica igual aNULL
. Quando a função dá erro,error
guarda a mensagem de erro eresult
guarda o valor do parâmetrootherwise =
, que por padrão éNULL
.possibly()
é uma versão mais otimista dosafely()
, que exige a definição deotherwise =
e não guarda as mensagens de erro.quietly()
não trata erros (ou seja, ela trava quando dá erro), mas guarda informações sobre warnings e messages.
Vamos ver as três funções colocadas em prática. Como exemplo usaremos a função log
, que i) retorna um número quando a entrada é um número positivo, ii) dá um warning quando a entrada é um número menor ou igual a zero, e iii) dá um erro se a entrada não é um número.
log(10)
## [1] 2.302585
log(-1)
## Warning in log(-1): NaNs produced
## [1] NaN
log('a')
## Error in log("a"): non-numeric argument to mathematical function
Vamos fazer as versões modificadas de log
:
library(purrr)
safe_log <- safely(log) # outra forma fancy de escrever isso: log %>% safely()
possible_log <- possibly(log, otherwise = 'putz')
quiet_log <- quietly(log)
Vamos mapear os seguintes elementos nessas funções:
entradas <- list(10, -1, 'a')
Agora, os resultados:
## Esse código vai travar
map(entradas, log)
## Warning in .Primitive("log")(x, base): NaNs produced
## Error in .Primitive("log")(x, base): non-numeric argument to mathematical function
safely()
:
## Retorna uma lista com erros e resultados NULL
map(entradas, safe_log)
## Warning in .Primitive("log")(x, base): NaNs produced
## [[1]]
## [[1]]$result
## [1] 2.302585
##
## [[1]]$error
## NULL
##
##
## [[2]]
## [[2]]$result
## [1] NaN
##
## [[2]]$error
## NULL
##
##
## [[3]]
## [[3]]$result
## NULL
##
## [[3]]$error
## <simpleError in .Primitive("log")(x, base): non-numeric argument to mathematical function>
possibly()
:
## Retorna uma lista com os resultados que deram certo
map(entradas, possible_log)
## Warning in .Primitive("log")(x, base): NaNs produced
## [[1]]
## [1] 2.302585
##
## [[2]]
## [1] NaN
##
## [[3]]
## [1] "putz"
quietly()
:
## Também trava, mesmo problema de log
map(entradas, quiet_log)
## Error in .Primitive("log")(x, base): non-numeric argument to mathematical function
## Quando funciona, retorna todos os warnings e messages
map(entradas[1:2], quiet_log)
## [[1]]
## [[1]]$result
## [1] 2.302585
##
## [[1]]$output
## [1] ""
##
## [[1]]$warnings
## character(0)
##
## [[1]]$messages
## character(0)
##
##
## [[2]]
## [[2]]$result
## [1] NaN
##
## [[2]]$output
## [1] ""
##
## [[2]]$warnings
## [1] "NaNs produced"
##
## [[2]]$messages
## character(0)
Combo com purrr
e tibble
Um combo que eu gosto bastante de usar é adicionar erros do código retornado por safely()
dentro de uma tibble
. Uma forma de fazer isso é com o código abaixo:
library(tibble)
# adiciona um resultado default caso dê erro. No caso, NA.
safe_log2 <- log %>% safely(otherwise = NA_real_)
d_result <- entradas %>%
map(safe_log2) %>%
# ao invés de uma lista de tamanho 3 com 2 elementos (result e error),
# temos uma lista de tamanho 2 (result e error) com 3 elementos.
transpose() %>%
# simplifica o vetor de resultados
simplify_all() %>%
# converte para data frame
as_tibble()
d_result
## # A tibble: 3 × 2
## result error
## <dbl> <list>
## 1 2.30 <NULL>
## 2 NaN <NULL>
## 3 NA <smplErrr>
Assim, é possível guardar as informações dos erros de forma concisa, sem perder a informação dos erros.
E é isso. Happy coding ;)
PS: Até pouco tempo atrás eu usava a função dplyr::failwith()
para fazer o mesmo que possibly()
. Porém, descobri que essa função será retirada do dplyr
no futuro. Então se você é um usuário de failwith()
, está na hora de mudar!