What is Error Handling?

Error handling refers to the process of managing and responding to errors or exceptions that occur during the execution of a program. In R, error handling ensures that your code can gracefully handle unexpected situations, such as invalid inputs or runtime errors, without crashing.

Importance of Error Handling

Error handling is crucial because it:

  1. Prevents Crashes: Ensures that your code can continue running or exit gracefully when encountering errors.
  2. Improves User Experience: Provides informative error messages that can help users understand what went wrong.
  3. Debugging: Helps in identifying and fixing bugs in the code by providing detailed information about the errors.
  4. Robustness: Makes your code more reliable and resilient to unexpected situations.

Functions for Error Handling in R

The following functions are designed to handle errors in the R programming language.

  • try()
  • tryCatch()
  • stop()
  • stopifnot()
  • warning()

try()

The try() function is used to execute an expression and handle errors that occur. It returns the result of the expression if successful, or an error object if an error occurs.

division <- function(x, y) {
  result <- try(x / y, silent = TRUE)
  if (inherits(result, 'try-error')) {
    cat("\nBoth x and y must be numeric, got", typeof(x), "and", typeof(y))
    return(NA)
  }
  return(result)
}

The following will be successful.

division(x = 8, y = 5)
## [1] 1.6

The following will print a custom error message, and return NA as the result because there is an attempt to divide 8 by M which is a character.

division(x = 8, y = "M")
## 
## Both x and y must be numeric, got double and character
## [1] NA

tryCatch()

The tryCatch() function is used to execute an expression and catch any errors, warnings, or messages that occur during its execution. This function helps in managing errors gracefully by providing alternative actions or custom messages when an error occurs, rather than stopping the entire program.

The syntax is given as follows

tryCatch(
  expr, 
  error = function(e) {
    
  }, 
  warning = function(w) {
    
  },
  finally = {
  
  }
)

where

expr: The expression to be evaluated.

error: A function to handle errors.

warning: A function to handle warnings.

finally: A block of code that will be executed regardless of whether an error or warning occurred.

write_df_to_csv <- function(file_path, df) {
  tryCatch({
    write.csv(df, file = file_path, row.names = FALSE)
  }, 
  error = function(e) {
    cat("Error while writing to file:", conditionMessage(e))
  })
}

The following will write the dataset women to the default working directory (in my case, it is my documents directory).

write_df_to_csv(file_path = "P:/women.csv", df = women)
## Warning in file(file, ifelse(append, "a", "w")): cannot open file
## 'P:/women.csv': No such file or directory
## Error while writing to file: cannot open the connection

The following will fail because I do not have a directory named H on my local drive.

write_df_to_csv(file_path = "H:/women.csv", df = women)
## Warning in file(file, ifelse(append, "a", "w")): cannot open file
## 'H:/women.csv': No such file or directory
## Error while writing to file: cannot open the connection

stop()

The stop() function is used to generate an error and halt execution. It is useful for indicating that something has gone wrong and should be addressed.

stopifnot()

The stopifnot() function checks if the provided conditions are TRUE. If any condition is FALSE, it generates an error.

warning()

The warning() function generates a warning message without halting execution. It is useful for non-critical issues that should be brought to the user’s attention.

Example

We use the example of calculating body mass index do demonstrate the application of stop() stopifnot()andwarning()`.

bmi <- function(weight, height) {
  
  # if weight or height is not a vector, e.g. its a matrix, then first 
  # convert it to a vector but remember to inform the user of this action
  if (!is.vector(weight)) {
    warning("weight was converted from ", class(weight), " to a vector")
    weight = c(weight)
  }
  if (!is.vector(height)) {
    warning("height was converted from ", class(height), " to a vector")
    height = c(height)
  }
  # using `stop` to check length
  if (length(weight) != length(height)) {
    stop(
      "weight and height must have same number of elements ",
      "got ", length(weight), " and ", length(height), " elements ",
      "respectively"
    )
  }
  # using `stopifnot` to validate that both are numeric
  stopifnot(is.numeric(weight), is.numeric(height))
  
  # calculate the body mass index
  calculate_bmi <- function(height, weight) {
    return(weight / (height ^ 2))
  }
  bmi <- mapply(calculate_bmi, height, weight)
  suspicious_bmi = bmi[bmi > 500 | bmi < 5]
  n = length(suspicious_bmi)
  if (n > 0) {
    index = ifelse(n == 1, "index", "indices")
    suspicious_bmi_str = paste(round(suspicious_bmi, 4), collapse = ", ")
    warning("Body mass ", index, ": ", suspicious_bmi_str, " are suspicious.")
  }
  df = data.frame(weight, height, bmi)
  return(df)
}

Apply the functio

Will work successfully

weight <- c(94, 85, 82, 70, 83, 85, 77, 80, 64, 57, 98, 95)
height <- height <- c(1.62, 1.82, 1.66, 1.85, 1.88, 1.52, 1.71, 1.86, 1.72, 1.68, 1.88, 1.68)
bmi(weight = weight, height = height)
##    weight height      bmi
## 1      94   1.62 35.81771
## 2      85   1.82 25.66115
## 3      82   1.66 29.75758
## 4      70   1.85 20.45289
## 5      83   1.88 23.48348
## 6      85   1.52 36.79017
## 7      77   1.71 26.33289
## 8      80   1.86 23.12406
## 9      64   1.72 21.63332
## 10     57   1.68 20.19558
## 11     98   1.88 27.72748
## 12     95   1.68 33.65930

Will work but return a warning message

weight <- c(94, 85, 2000, 70, 83, 85, 77, 80, 64, 57, 98, 95)
height <- height <- c(1.62, 1.82, 1.66, 1.85, 1.88, 1.52, 1.71, 1.86, 1.72, 1.68, 1.88, 168)
bmi(weight = weight, height = height)
## Warning in bmi(weight = weight, height = height): Body mass indices: 725.7947,
## 0.0034 are suspicious.
##    weight height          bmi
## 1      94   1.62  35.81771071
## 2      85   1.82  25.66115203
## 3    2000   1.66 725.79474525
## 4      70   1.85  20.45288532
## 5      83   1.88  23.48347669
## 6      85   1.52  36.79016620
## 7      77   1.71  26.33288875
## 8      80   1.86  23.12406059
## 9      64   1.72  21.63331531
## 10     57   1.68  20.19557823
## 11     98   1.88  27.72747850
## 12     95 168.00   0.00336593

Will crush because length are not the same (uncomment last line)

weight <- c(94, 85, 82, 70, 83, 85, 77, 80, 64, 57, 98, 95)
height <- height <- c(1.62, 1.82, 1.66, 1.85, 1.88, 1.52, 1.71)
#bmi(weight = weight, height = height)

Will crush because one is character due to presence of “N/A” (uncomment last line) - this is as result of the stopifnot() function validation.

weight <- c(94, 85, 82, 70, 83, 85, 77, 80, 64, 57, 98, 95)
height <- height <- c(1.62, 1.82, "N/A", 1.85, 1.88, 1.52, 1.71, 1.86, 1.72, 1.68, 1.88, 1.68)
#bmi(weight = weight, height = height)