Menu Home

Free data science video lecture: debugging in R

We are pleased to release a new free data science video lecture: Debugging R code using R, RStudio and wrapper functions. In this 8 minute video we demonstrate the incredible power of R using wrapper functions to catch errors for later reproduction and debugging. If you haven’t tried these techniques this will really improve your debugging game.

All code and examples can be found here and in WVPlots.

For those that don’t want to watch the video or follow links, below is the (non-printing) version of the wrapper.

#' Capture arguments of exception throwing plot for later debugging.
#'
#' Run fn, save arguments on failure.
#'
#' @param saveFile path to save RDS to.
#' @param fn function to call
#' @param ... arguments for fn
#' @return fn(...) normally, but if f(...) throws an exception,
#'   save to saveFile RDS of list r such that do.call(r$fn,r$args) 
#'   repeats the call to fn with args.
#'
DebugFn <- function(saveFile,fn,...) {
  args <- list(...)
  tryCatch({
    res = do.call(fn,args)
    res
  },
  error = function(e) {
    saveRDS(object=list(fn=fn,args=args),file=saveFile)
    stop(paste0("Wrote '",saveFile,"' on catching '",as.character(e),"'"))
    })
}

And how to use it.

> f <- function(a,b) { a[[b]] }
> f(0,0)
Error in a[[b]] : attempt to select less than one element
> DebugFn('example.RDS','f',0,0)
Error in value[[3L]](cond) : 
  Wrote 'example.RDS' on catching 'Error in a[[b]]: attempt to select less than one element'
  
  
> p <- readRDS('example.RDS')
> print(p)
$fn
[1] "f"

$args
$args[[1]]
[1] 0

$args[[2]]
[1] 0


> do.call(p$fn,p$args)
Error in a[[b]] : attempt to select less than one element
> 

If you are using RStudio you may need to toggle the RStudo->Debug->”On Error” message settings to “Message Only” on the capture run (see below).

Debug

However, for context I strongly recommend watching the short video and checking the included resources. Also, please check here and here for more ideas.

Edit: Improved versions of the debug functions are now part of the development version of the replyr package: https://github.com/WinVector/replyr .

Categories: Coding Programming Tutorials

Tagged as:

jmount

Data Scientist and trainer at Win Vector LLC. One of the authors of Practical Data Science with R.

2 replies

  1. Just a note: the technique as implemented only works for functions that are pure in the sense they only depend on their arguments (and not functions that grab additional values from their lexical closure or other environments).

    Like

  2. Nice idea !

    The concept can be taken even further using a function operator (i.e. a function that returns a function).

    See here:

    #' Turn a function into a new function that helps debugging upon exception
    #'
    #' @param fn the function to transform
    #' @param saveFile an optional path to save RDS to, if NULL output will be in global variable '.problem'
    #' @return new function that behaves like fn(…) normally, but if fn(…) throws an exception saves to variable or saveFile RDS of list .problem such that do.call(.problem$fn_name,.problem$args) repeats the call to fn with args.
    #'
    #' @examples
    #' sum_of_log <- function(x, y){
    #' stopifnot(x>=0)
    #' stopifnot(y>=0)
    #' return(log(x)+log(y))
    #' }
    #'
    #' sum_of_log2 <- debuggable(sum_of_log, "problem.RDS")
    #'
    #' sum_of_log2(1,2)
    #' sum_of_log2(1,-2)
    #'
    #' .problem
    #'
    #' do.call(.problem$fn_name, .problem$args)
    #'
    #' @references
    #' inspired from http://winvector.github.io/Debugging/
    #'
    debuggable <- function(fn, saveFile=NULL){
    fn_name <- as.character(match.call())[2]
    new_fn <- function(){
    args <- list()
    tryCatch({
    res = do.call(fn,args)
    res
    },
    error = function(e) {
    out <- list(fn_name=fn_name,args=args, fn_def = fn)
    if (is.null(saveFile)){
    .problem <<- out
    stop(paste0("Wrote object '.problem' on catching '",as.character(e),"'",
    "\n You can reproduce the error with:\n'do.call(.problem$fn_name, .problem$args)'"))
    }else{
    saveRDS(out,file=saveFile)
    stop(paste0("Wrote '",saveFile,"' on catching '",as.character(e),"'",
    "\n You can reproduce the error with:\n'p <- readRDS('",saveFile,"'); do.call(p$fn_name, p$args)'"))
    }
    })
    }
    return(new_fn)
    }

    view raw
    debuggable.R
    hosted with ❤ by GitHub

    Your example becomes simply:

    > f f(0,0)
    Error in a[[b]] : attempt to select less than one element

    > f f(0,0)
    Error in value[[3L]](cond) :
    Wrote ‘example.RDS’ on catching ‘Error in a[[b]]: attempt to select less than one element’

    Like

%d bloggers like this: