In this lab, we will learn how to handle conditions.
Goal: by the end of this lab, you should be able to handle an error and return gracefully.
Recall that there are three main types of conditions in R:
The following function will compute the number of unique values in
whatever columns in a data frame you select. The first argument is the
data frame, and the second argument is the dots, which are simply passed
to select().
Note that this function may display a message, or a
warning(), or an error, depending on what happens.
unique_values <- function(.data, ...) {
  x <- .data %>%
    select(...)
  
  if (ncol(x) == ncol(.data)) {
    warning("All columns selected!")
  }
  
  if (ncol(x) < 1) {
    stop("No columns selected.")
  }
  message(paste("Computing on", ncol(x), "columns..."))  
  map_int(x, n_distinct)
}unique_values() carefully. Do you
understand how it works?If we give this function no valid selection, we will throw a warning.
unique_values(starwars)## Error in unique_values(starwars): No columns selected.unique_values() or by select()? How do you
know?If we select all of the columns in the data frame, then we get a warning. However, the code still executes.
unique_values(starwars, everything())## Warning in unique_values(starwars, everything()): All columns selected!## Computing on 14 columns...##       name     height       mass hair_color skin_color  eye_color birth_year 
##         87         46         39         13         31         15         37 
##        sex     gender  homeworld    species      films   vehicles  starships 
##          5          3         49         38         24         11         17message() came after the call to
warning() in the function?Since unique_values() passes the dots to
select(), we can leverage all of the functionality of the
select helpers!
unique_values(starwars, contains("n"))## Computing on 3 columns...##       name skin_color     gender 
##         87         31          3However, if we pass garbage to select(), then of course
select() will still throw an error.
unique_values(starwars, i_love_r)## Error in `select()`:
## ! Can't subset columns that don't exist.
## ✖ Column `i_love_r` doesn't exist.unique_values(starwars). What is different?Instead of just failing whenever the user passes bad arguments to
select(), we might want to catch those
errors and do something with them. Here, we use a
tryCatch() statement to provide some additional information
about what went wrong, and to continue with the original data frame if
the select() statement failed.
unique_values_safe <- function(.data, ...) {
  x <- tryCatch(
    error = function(cnd) {
      warning("Attempt to select column has failed")
      message("Here is what we know about the error")
      str(cnd)
      .data
    },
    .data %>%
      select(...)
  )
  
  if (ncol(x) == ncol(.data)) {
    warning("All columns selected!")
  }
  
  if (ncol(x) < 1) {
    stop("No columns selected.")
  }
  message(paste("Computing on", ncol(x), "columns..."))  
  map_int(x, n_distinct)
}Now, even though an error still occurs, we still get output.
unique_values_safe(starwars, i_love_r)## Warning in value[[3L]](cond): Attempt to select column has failed## Here is what we know about the error## List of 9
##  $ message         : chr ""
##  $ trace           :Classes 'rlang_trace', 'rlib_trace', 'tbl' and 'data.frame': 33 obs. of  6 variables:
##   ..$ call       :List of 33
##   .. ..$ : language unique_values_safe(starwars, i_love_r)
##   .. ..$ : language tryCatch(error = function(cnd) {     warning("Attempt to select column has failed") ...
##   .. .. ..- attr(*, "srcref")= 'srcref' int [1:8] 2 3 11 3 3 3 2 11
##   .. .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fe571fb6310> 
##   .. ..$ : language tryCatchList(expr, classes, parentenv, handlers)
##   .. ..$ : language tryCatchOne(expr, names, parentenv, handlers[[1L]])
##   .. ..$ : language doTryCatch(return(expr), name, parentenv, handler)
##   .. ..$ : language .data %>% select(...)
##   .. ..$ : language select(., ...)
##   .. ..$ : language select.data.frame(., ...)
##   .. ..$ : language tidyselect_fix_call(tidyselect::eval_select(expr(c(...)), .data), call = error_call)
##   .. ..$ : language withCallingHandlers(expr, error = function(cnd) {     cnd$call <- call ...
##   .. ..$ : language tidyselect::eval_select(expr(c(...)), .data)
##   .. ..$ : language eval_select_impl(data, names(data), as_quosure(expr, env), include = include,      exclude = exclude, strict = st| __truncated__ ...
##   .. ..$ : language with_subscript_errors(vars_select_eval(vars, expr, strict = strict, data = x,      name_spec = name_spec, uniquel| __truncated__ ...
##   .. ..$ : language tryCatch(with_entraced_errors(expr), vctrs_error_subscript = function(cnd) {     cnd$subscript_action <- subscript_action(type) ...
##   .. ..$ : language tryCatchList(expr, classes, parentenv, handlers)
##   .. ..$ : language tryCatchOne(expr, names, parentenv, handlers[[1L]])
##   .. ..$ : language doTryCatch(return(expr), name, parentenv, handler)
##   .. ..$ : language with_entraced_errors(expr)
##   .. ..$ : language try_fetch(expr, simpleError = function(cnd) {     abort(conditionMessage(cnd), call = conditionCall(cnd)) ...
##   .. ..$ : language withCallingHandlers(expr, simpleError = function(cnd) {     { ...
##   .. ..$ : language vars_select_eval(vars, expr, strict = strict, data = x, name_spec = name_spec,      uniquely_named = uniquely_nam| __truncated__ ...
##   .. ..$ : language walk_data_tree(expr, data_mask, context_mask, error_call)
##   .. ..$ : language eval_c(expr, data_mask, context_mask)
##   .. ..$ : language reduce_sels(node, data_mask, context_mask, init = init)
##   .. ..$ : language walk_data_tree(new, data_mask, context_mask)
##   .. ..$ : language as_indices_sel_impl(out, vars = vars, strict = strict, data = data, call = error_call)
##   .. ..$ : language as_indices_impl(x, vars, call = call, strict = strict)
##   .. ..$ : language chr_as_locations(x, vars, call = call)
##   .. ..$ : language vctrs::vec_as_location(x, n = length(vars), names = vars)
##   .. ..$ : language `<fn>`()
##   .. ..$ : language stop_subscript_oob(i = i, subscript_type = subscript_type, names = names,      subscript_action = subscript_actio| __truncated__ ...
##   .. ..$ : language stop_subscript(class = "vctrs_error_subscript_oob", i = i, subscript_type = subscript_type,      ..., call = call)
##   .. ..$ : language abort(class = c(class, "vctrs_error_subscript"), i = i, ..., call = vctrs_error_call(call))
##   ..$ parent     : int [1:33] 0 1 2 3 4 1 0 0 8 9 ...
##   ..$ visible    : logi [1:33] TRUE TRUE TRUE TRUE TRUE TRUE ...
##   ..$ namespace  : chr [1:33] NA "base" "base" "base" ...
##   ..$ scope      : chr [1:33] "global" "::" "local" "local" ...
##   ..$ error_frame: logi [1:33] FALSE FALSE FALSE FALSE FALSE FALSE ...
##   ..- attr(*, "version")= int 2
##  $ i               : chr "i_love_r"
##  $ subscript_type  : chr "character"
##  $ names           : chr [1:14] "name" "height" "mass" "hair_color" ...
##  $ subscript_action: chr "subset"
##  $ subscript_arg   : chr "x"
##  $ call            : language select(., ...)
##  $ subscript_elt   : chr "column"
##  - attr(*, "class")= chr [1:5] "vctrs_error_subscript_oob" "vctrs_error_subscript" "rlang_error" "error" ...## Warning in unique_values_safe(starwars, i_love_r): All columns selected!## Computing on 14 columns...##       name     height       mass hair_color skin_color  eye_color birth_year 
##         87         46         39         13         31         15         37 
##        sex     gender  homeworld    species      films   vehicles  starships 
##          5          3         49         38         24         11         17Whether this output is sensible is an open question for the developer. In this case I think it is probably not sensible.
Prompt: Have you tried to catch errors in other languages? If so, how does the condition handling system in R compare? If not, can you think of a more intuitive way to handle errors?