Debugging in R

During the past few months I have used R and RStudio a lot and since I have implemented some functions that also other people use I found it very useful to know how to debug in R. Debugging is not as convenient or comfortable as in Visual Studio but you can make your life a lot easier knowing a few tricks.

For this purpose I wrote a small test function that will cause an error.

errorFunc<-function(causeError) {
  if (causeError) {
    stop("errorMessage")
  }  
}

errorFunc(TRUE)

Without debugging or error handling, this will happen:

> errorFunc(TRUE)
Error in errorFunc(TRUE) : errorMessage

options(error=recover)

By typing options(error=recover) into the R command line, you can tell the environment to call the function recover() in case of an error.

The options functions has several other global environment besides "error".

The description of the parameter error says:

either a function or an expression governing the handling of non-catastrophic errors such as those generated by stop as well as by signals and internally detected errors. If the option is a function, a call to that function, with no arguments, is generated as the expression. The default value is NULL: see stop for the behaviour in that case. The functions dump.frames and recover provide alternatives that allow post-mortem debugging. Note that these need to specified as e.g. options(error = utils::recover) in startup files such as ‘.Rprofile’.

It suggests to put this line into a startup file, but I usually just activate it in case I need it because for me it is not necessary to debug every error that way.

Debugging of the above function will look like this:

> errorFunc(TRUE)
Error in errorFunc(TRUE) : errorMessage

Enter a frame number, or 0 to exit   

1: errorFunc(TRUE)

Selection: 1
Called from: top level 
Browse[1]> causeError
[1] TRUE
Browse[1]> 

After an error occurs you get a list of frames that you can access (similar to the callstack in any other language). After entering the frame number you can access any variable in that scope, in our case that is only "causeError".

When you are finished debugging you can reset the environment:

options(error=NULL)

You might have noticed that you pass a function (recover()) which means that you could even pass your own functions as parameter. I haven't had the need for it but it's worth mentioning.

browser()

Another possibility is to use the function browser(). The function above can only be used when an error already occurred but often you want to find out what happens inside a function or why the results are different then expected.

In that case I use browser().

errorFuncBrowse<-function(causeError) {
  browser()
  if (causeError) {
    stop("errorMessage")
  }  
}

errorFuncBrowse(TRUE)

This is what happens:

Debugging in R using browser().
Debugging in R using browser().

This is your console when you debug using browser() (of course it will look differently if you do not use RStudio):

Console in browser() mode.
Console in browser() mode.

I often use this when I don't know what happens inside a function of a more complex program. If there are many functions calling each other it might be exhausting to find the source of an error. It can also be useful to include some sanity checks in your program and in case they fail, call browser():

if (someValue != expectedValue) {
    browser()
}

message(), print(), warning()

This methods are also always useful in case you have no clue where your program breaks and sometimes it is faster to write several messages to get a rough idea instead of debugging line by line.

I am a fan of this kind of debugging, because in that way I can also find out what another user did. In my current project I log almost everything, because when a user has a problem they can send me the log file and I can almost immediately see what went wrong or at least I can follow the same steps and reproduce the error (users often don't remember what they did ;)).

debug()

When researching for this post I stumbled over another useful function, debug().

You simply call

debug(errorFunc)

And what happens then is the following:

> errorFunc(TRUE)
debugging in: errorFunc(TRUE)
debug at #1: {
    if (causeError) {
        stop("errorMessage")
    }
}
Browse[2]> 

And voila, you can debug your function.

When you are done, call

undebug(errorFunc)

Package debug

There is also a package called debug, but I did not have a look at it yet since I am happy with the possibilities by the above functions.

traceback()

Often, when you have complex functions, where the method that causes an error is called at different lines, you want to know which call caused the error.

Then you can use the traceback() function.

> traceback()
2: stop("errorMessage") at #3
1: errorFunc(TRUE)

This shows the traceback of the last uncaught error.

Warnings

Often you want to debug warnings as well. To do this, there is a little trick: options(warn=2) converts warnings into errors, which means you can use all the above functions for warnings as well.

Don't forget to reset options(warn=0) otherwise your future code will crash because of a warning!

Leave a Reply

Your email address will not be published. Required fields are marked *