Errors and Warnings

There are three types of R error messages: errors, warnings and messages. These are ‘thrown’ with stop(), warning() and message() respectively.

stop("This isn't working.")
## Error: This isn't working.
warning("I'm not sure about this")
## Warning: I'm not sure about this
message("By the way...")
## By the way...

Some error messages are quite informative:

rnorm("hello")
## Warning: NAs introduced by coercion
## Error: invalid arguments

Some are less so.

f = function(lambda, dat) sum(dat*log(lambda) - lambda)
y = rpois(100, lambda=2)
newton(f, start=1, dat=y)
## Warning: NaNs produced
## Error: missing value where TRUE/FALSE needed

When you write code try to make your messages as specific and as helpful as possible!

Tracebacks

The most powerful way to find out what’s gone wrong is to ‘step in’ to the function at the point of failure.

options(error=recover)

When your function throws an error, you’ll be presented with a call stack, and you can select any environment to enter into. My advice is to start in the deepest environment containing code which you wrote yourself.

In the case below (we’re calling the code from the profiling page) you don’t have a choice: press ‘1’ to step inside your function.

> newton(f, start=1, dat=y)
Error in while (max(abs(FUN(x, ...))) > tol) { : 
  missing value where TRUE/FALSE needed
In addition: Warning message:
In log(lambda) : NaNs produced

Enter a frame number, or 0 to exit   

1: newton(f, start = 1, dat = y)

Selection: 

To get back just press enter and you’ll return to this menu; 0 exits completely.

Warnings

A warning is usually an indication that something is wrong, even if it hasn’t generated a full blown error, so don’t ignore them! For debugging purposes it’s useful to set

options(warn=2)

which turns warnings straight into errors (the default is 0).

> newton(f, start=1, dat=y)
Error in log(lambda) : (converted from warning) NaNs produced

Enter a frame number, or 0 to exit   

1: newton(f, start = 1, dat = y)
2: #4: FUN(x, ...)
3: #1: .signalSimpleWarning("NaNs produced", quote(log(lambda)))
4: withRestarts({
    .Internal(.signalCondition(simpleWarning(msg, call), msg, call))
    .Int
5: withOneRestart(expr, restarts[[1]])
6: doWithOneRestart(return(expr), restart)

Selection:

If you step into frame number 2 (which is the function f being called) we can check what the dat and lambda are:

Browse[1]> lambda
           [,1]
[1,] -0.6477124

So f was called with inappropriate arguments. If we step out of this frame and back to frame 1…

Browse[2]> x
           [,1]
[1,] -0.6477124

Now we see the problem: we stepped outside the valid range of our function.

Error Handling

In order to make newton() deal with this problem, we have to allow it to handle an error given to it by the function FUN.

## step()
##' Obtain one step of a Newton algorithm
##'
##' @param FUN    function taking single vector argument
##' @param x      current value of argument to \code{FUN}
##' @param eps    length of step size used for calculating numerical derivative
##'
##' @return numeric vector giving next \code{x} value
step = function(FUN, x, ..., eps = 1e-8) {
  val = FUN(x, ...)
  deriv = matrix(NA, length(val), length(val))
  
  for (i in 1:length(val)) {
    eps_vector = rep(0, length(val))
    eps_vector[i] = eps
    
    deriv[,i] = (FUN(x + eps_vector, ...) - FUN(x, ...)) / eps
  }
  out = -solve(deriv) %*% val
  return(out)
}

## newton()
##' Run Newton algorithm to find root of function
##'
##' @param FUN    function taking single vector argument
##' @param start  starting value of argument to FUN
##' @param tol    maximum value of function for convergence
##' @param eps    length of step size used for calculating numerical derivative
##'
##' @return numeric vector of point achieved after convergence
##' of the Newton algorithm
newton = function(FUN, start, ..., tol = 1e-8, eps = 1e-8) {
  x = start

  val = tryCatch(FUN(x, ...),
    error = function(e) stop("Invalid starting value for FUN")
    )
  
  while (max(abs(val)) > tol) {
    move = step(FUN, x, ..., eps=eps)
    x = x + move

    val = tryCatch(FUN(x, ...),
      error = function(e) stop("Moved outside valid range")
      )

  }
  
  return(x)
}

newton(f, start=2, dat=y)
## Warning: NaNs produced
## Error: missing value where TRUE/FALSE needed

At this point you might notice that the function f I keep passing is the log-likelihood, but I should be giving the derivative of the log-likelihood. Hence:

Df = function(lambda, dat) sum(dat/lambda - 1)
newton(Df, start=1, dat=y)
##      [,1]
## [1,] 1.82

And now it works!

debug()

Sometimes the traceback doesn’t help, because the problem occurred before an error was thrown. If you call debug() on a function, you can ‘step through’ the function as it runs line-by-line, and see what it’s doing at each stage.

debug(lm)
x = rnorm(20); y = x+rnorm(20)
lm(y ~ x)

There are some special commands you can use: f will ‘finish’ the current loop; s allows you to step into the function calls in the next statement, again using the browser. Q finishes all executions and gets you back to the ordinary command line.

Run undebug(lm) (or equivalent) to stop this behaviour.

trace()

The trace() function provides a principled way of adding (for example) print statements to your code, without actually modifying it permanently. The most intuitive way to do this is with the edit=TRUE option, which gives you an editor containing a copy of your function. Any changes you make here will not alter your real code, so you can play around as you like. This version of the function will be run until you redeine your function, or call untrace().

trace(lm, edit=TRUE)
# try adding a print statement or two