3

forgive me if I missed the right way to do this here, but I can't seem to make progress. Skipping error in for-loop helped a lot, as did some of the other answers related to tryCatch, but I'm still struggling. using tryCatch() in R to assign error values in loop didn't work for me, or I'm missing something.

I am running a for loop with tryCatch, but if I get an error, I would like to record it as a row in the resulting matrix. I can't seem to get the error function output up one level to the loop to be recorded. Here is a simple version of what I'm trying:

collectem <- function(eList){ 
  tmpList <- NULL
  for (e in eList){
    tryCatch({
    tmpVar <- c("foo", e)
    if (e==3) stop("BLAH!")
    }, error=function(d){c("No",d) -> tmpVar})
    tmpList <- rbind(tmpList, tmpVar)
  }
  return(tmpList)
}

Call:

x <- collectem(1:10)

Which results in:

> x
       [,1]  [,2]
tmpVar "foo" "1" 
tmpVar "foo" "2" 
tmpVar "foo" "3" 
tmpVar "foo" "4" 
tmpVar "foo" "5" 
tmpVar "foo" "6" 
tmpVar "foo" "7" 
tmpVar "foo" "8" 
tmpVar "foo" "9" 
tmpVar "foo" "10"

But I'm looking for this:

x
       [,1]  [,2]   
tmpVar "foo" "1"    
tmpVar "foo" "2"    
tmpVar "No"  "BLAH!"
tmpVar "foo" "4"    
tmpVar "foo" "5"    
tmpVar "foo" "6"    
tmpVar "foo" "7"    
tmpVar "foo" "8"    
tmpVar "foo" "9"    
tmpVar "foo" "10"   

Thanks!!

Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
Old_Newb
  • 43
  • 6
  • Note your assignment of `tmpVar` in the tryCatch is local to the function. Thus it has no effect on the value of `tmpVar`. – Hugh Mar 13 '19 at 07:25
  • @Hugh Thanks, I do understand that is the issue, how do I push it up into the environment of the for loop? – Old_Newb Mar 13 '19 at 07:38

2 Answers2

4

You may use the pattern of returning whatever tuple you want from the error and/or warning handler functions of tryCatch:

collectem <- function(eList) { 
    tmpList <- NULL
    for (e in eList) {
        tmpVar <- tryCatch(
            {
                if (e == 3) stop("**BLAH!**")
                c("foo", e)
            },
            error = function(d) {
                return(c("No", sub(".*\\*\\*(.*?)\\*\\*.*", "\\1", d)))
            }
        )
                print(tmpVar)
        tmpList <- rbind(tmpList, tmpVar)
    }
    return(tmpList)
}

eList <- c(1:10)
collectem(eList)

       [,1]  [,2]   
tmpVar "foo" "1"    
tmpVar "foo" "2"    
tmpVar "No"  "BLAH!"
tmpVar "foo" "4"    
tmpVar "foo" "5"    
tmpVar "foo" "6"    
tmpVar "foo" "7"    
tmpVar "foo" "8"    
tmpVar "foo" "9"    
tmpVar "foo" "10"

What I learned here is that tryCatch indeed returns a value when calling it. However, the value returned for the try block is just the implicit statement which executes. Calling return from the try block will cause the entire function to return, which is not what we want. On the other hand, we can (and arguably should) use an explicit return for the error and warning blocks. In this case, the return just returns from the call to tryCatch, and not from the entire enclosing function.

Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
  • Thanks Tim, but now my output is only: `[1] "foo" "1" ` for x – Old_Newb Mar 13 '19 at 05:59
  • @ Tim Biegeleisen that's giving the same result: `[1] "foo" "1"` --sorry, don't have the hang of this formatting yet! – Old_Newb Mar 13 '19 at 06:08
  • I fixed it. Sorry for the confusion. – Tim Biegeleisen Mar 13 '19 at 06:17
  • That's awesome, and thanks for sharing the reason for the behavior. Only one little detail: the message/row is hardcoded. I added a different error for 4 `if (e == 4) stop("FOUR?")` and substituted the d in for the return and it blew up again. How do I pass that argument? – Old_Newb Mar 13 '19 at 06:42
  • I updated the question to reflect this (I hope that's the right thing to do). – Old_Newb Mar 13 '19 at 07:14
  • No changing the question about 2 people have already answered isn't the right thing to do, it's the wrong thing. To answer your new question, I would have to give a substantially different answer. – Tim Biegeleisen Mar 13 '19 at 07:20
  • Understood, will fix the question - I thought it made it clearer. But my original question is still how to add the error value returned to the row, not how to hard code an error response into a row. Can you help? You clearly have a mastery of R. – Old_Newb Mar 13 '19 at 07:26
  • Check my updated answer. Basically, the value `d` which gets passed is formal error message, which contains what you pass in along with other things. One approach, perhaps not the best, would be to surround your message with markers, and then extract it out using the `error()` handling function. – Tim Biegeleisen Mar 13 '19 at 07:29
  • But as stated this is a simple example of what I'm trying, I will not be writing the errors, just dealing with them. So I can't list or even know what errors may be returned, I am not the one creating them, except for the sake of this example. – Old_Newb Mar 13 '19 at 07:35
  • Well if that's the case then displaying the full error message may be your only option here. Keep in mind that typically such error messages is something you would be writing to a log file, and would not be intended for an end user/customer to see. So, the makers of this API probably didn't have a problem with how they designed it. – Tim Biegeleisen Mar 13 '19 at 07:37
  • That's exactly what I want, the full error message displayed in the row that it belongs to. I don't understand your comment about the API makers, whatever message they send is what I want to show up in the row. – Old_Newb Mar 13 '19 at 07:46
  • Then just display `d`, the parameter passed into the `error()` function. Note that if you call `stop()` with `Blah!`, then this message _won't_ just be `Blah!`. It will contain other information. – Tim Biegeleisen Mar 13 '19 at 07:48
  • Ok cool. I get it. Thanks for sticking with me, and thanks for pointing that out as well. – Old_Newb Mar 13 '19 at 20:28
1

Here a pattern using try

collectem <- function(eList){ 
  #browser()
  tmpList <- NULL
  for (e in eList){
      flag <- try(if (e==3) stop("BLAH!"),silent = TRUE)
      if(!is.null(flag) && class(flag)=="try-error"){
        #tmpVar <- c("No","BLAH!")  
        d <- gsub('.*\\: (.*)\n','\\1',flag)
        tmpVar <- c("No",d)  
      } else {tmpVar <- c("foo", e)}

    tmpList <- rbind(tmpList, tmpVar)
  }
  return(tmpList)
}

When we hit e=3 flag will be

flag
[1] "Error in try(if (e == 3) stop(\"BLAH!\"), silent = TRUE) : BLAH!\n"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in doTryCatch(return(expr), name, parentenv, handler): BLAH!>

So we can extract anything after : and before \n as the Error message using gsub and grouping. Here what we will get

gsub('.*\\: (.*)\n','\\1',flag)
[1] "BLAH!"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in doTryCatch(return(expr), name, parentenv, handler): BLAH!>
A. Suliman
  • 12,923
  • 5
  • 24
  • 37
  • @ A. Suliman Awesome, that works. Thank you! Getting warmer... I'm not sure I understand your "PS" about extracting the d. Would you mind elaborating? (The actual error will be a "404" or similar returned from an API call, if that helps…) – Old_Newb Mar 13 '19 at 06:25
  • @Old_Newb see my update. Let me know if you need more elaborate. – A. Suliman Mar 13 '19 at 06:41
  • 1
    This is a great alternative and thorough explanation. Thank you! Wish I could select two answers. – Old_Newb Mar 13 '19 at 20:36