--- title: "Tratando erros: the tidy way" date: "2017-04-20" tags: ["coding", "pacotes", "purrr"] categories: ["Tutoriais"] image: "images/posts/banner/error.jpg" author: ["Julio"] summary: "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." ---

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.

Admita, você não imaginava que teria de pensar em gramática para programar em R.

Figura 1: Admita, você não imaginava que teria de pensar em gramática para programar em R.

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
## 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>
## 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"
## 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 x 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!