Search code examples
rdplyrtry-catchmagrittr

tryCatch() or exists() in R pipe chain error handling


I am currently experiencing a hiccup where I am trying to create a table of values from some upstream calculations. Typically, I've been assuming that there would be at least one value of 1 each time these data frames are created; but I've encountered an example where this wasn't the case, and ended up with a table that looks like this:

df <- data.frame(
  Experiment_Batch = c(rep("008_1", 83),
                       rep("008_6", 82),
                       rep("520_0", 2),
                       rep("944_10", 84),
                       rep("944_8", 85),
                       rep("944_9", 72)),
  Overall = rep(0, 408)
  ) 

This has caused the following downstream processing:

df %>% 
  dplyr::count(Experiment_Batch, Overall) %>%
  tidyr::spread(Overall, n, fill = 0) %>% 
  dplyr::select(Experiment_Batch, `1`)

to error out: Error in overscope_eval_next(overscope, expr) : object '1' not found.

I've tried using tryCatch() and exists(), but I can't quite seem to get these to work properly. Ideally, this would all be handled elegantly using pipe operators. I already know I could create an object and add in a few if-else statements as necessary to my workflow, but I'm wondering if there's a... "more interesting" way to take care of this, so I wouldn't have to break up the work.


Solution

  • You can use select helper functions, if you want to ignore the selection if the column doesn't exist; Here matches("^1$") will try to select the column whose name exactly matches 1, since the data frame doesn't have the column, it simply ignores the selection as matches returns integer(0):

    library(tidyverse)
    df %>% 
        count(Experiment_Batch, Overall) %>%
        spread(Overall, n, fill = 0) %>% 
        select(Experiment_Batch, matches("^1$"))
    
    # A tibble: 6 x 1
    #  Experiment_Batch
    #*           <fctr>
    #1            008_1
    #2            008_6
    #3            520_0
    #4           944_10
    #5            944_8
    #6            944_9
    

    matches returns integer(0) when non of the column names matches the pattern which gets ignored in select:

    matches("^1$", vars = c("0", "experiment"))
    # integer(0)
    
    matches("^1$", vars = c("0", "experiment", "1"))
    # [1] 3
    

    If you need to customize the error catch:

    library(tidyverse)
    df %>% 
        count(Experiment_Batch, Overall) %>%
        spread(Overall, n, fill = 0) %>% 
        {
            tryCatch(
                select(., Experiment_Batch, `1`), 
                error=function(e) select(., Experiment_Batch)
            )
        }
        # replace the error with the customized function to handle the exception
    
    # A tibble: 6 x 1
    #  Experiment_Batch
    #*           <fctr>
    #1            008_1
    #2            008_6
    #3            520_0
    #4           944_10
    #5            944_8
    6            944_9