38

Code written using lapply and friends is usually easier on the eyes and more Rish than loops. I love lapply just as much as the next guy, but how do I debug it when things go wrong? For example:

> ## a list composed of numeric elements 
> x <- as.list(-2:2)
> ## turn one of the elements into characters
> x[[2]] <- "what?!?"
> 
> ## using sapply
> sapply(x, function(x) 1/x)
Error in 1/x : non-numeric argument to binary operator

Had I used a for loop:

> y <- rep(NA, length(x))
> for (i in 1:length(x)) {
+     y[i] <-  1/x[[i]]
+ }
Error in 1/x[[i]] : non-numeric argument to binary operator

But I would know where the error happened:

> i
[1] 2

What should I do when using lapply/sapply?

Eduardo Leoni
  • 8,991
  • 6
  • 42
  • 49
  • 4
    This may be an unpopular response, but after 15 years of R development I've almost always found it easier to, temporarily, convert to a for loop to find the edge case that's breaking my code. Also, starting with a for loop instead of an sapply/lapply can simplify your initial process (you can refactor your code for speed/performance when it matters - but first it needs to work!) – Brandon Bertelsen Jun 19 '19 at 18:31

7 Answers7

30

Use the standard R debugging techniques to stop exactly when the error occurs:

options(error = browser) 

or

options(error = recover)

When done, revert to standard behaviour:

options(error = NULL)
27

If you wrap your inner function with a try() statement, you get more information:

> sapply(x, function(x) try(1/x))
Error in 1/x : non-numeric argument to binary operator
[1] "-0.5"                                                    
[2] "Error in 1/x : non-numeric argument to binary operator\n"
[3] "Inf"                                                     
[4] "1"                                                       
[5] "0.5"

In this case, you can see which index fails.

Shane
  • 98,550
  • 35
  • 224
  • 217
9

Use the plyr package, with .inform = TRUE:

library(plyr)
laply(x, function(x) 1/x, .inform = TRUE)
hadley
  • 102,019
  • 32
  • 183
  • 245
3

Like geoffjentry said:

> sapply(x, function(x) {
  res <- tryCatch(1 / x,
                  error=function(e) {
                          cat("Failed on x = ", x, "\n", sep="") ## browser()
                          stop(e)
                        })
})

Also, your for loop could be rewritten to be much cleaner (possibly a little slower):

> y <- NULL
> for (xi in x)
    y <- c(y, 1 / xi)

Error in 1/xi : non-numeric argument to binary operator

For loops are slow in R, but unless you really need the speed I'd go with a simple iterative approach over a confusing list comprehension.

If I need to figure out some code on the fly, I'll always go:

sapply(x, function(x) {
  browser()
  ...
})

And write the code from inside the function so I see what I'm getting.

-- Dan

ephpostfacto
  • 303
  • 1
  • 2
  • 7
  • 3
    Bad call on the for loop. That will make things **much** slower. – hadley Sep 09 '09 at 22:50
  • 1
    @hadley ... because the entire array needs to be reallocated (reserve memory, copy old data) in every single loop step :) `y <- numeric(length(x))` should be the fastest way to preallocate. – AlexR Dec 13 '16 at 12:51
1

Using debug or browser isn't a good idea in this case, because it will stop your code so frequently. Use Try or TryCatch instead, and deal with the situation when it arises.

griffin
  • 3,158
  • 8
  • 37
  • 34
0

You can debug() the function, or put a browser() inside the body. This is only particularly useful if you don't have a gajillion iterations to work through.

Also, I've not personally done this, but I suspect you could put a browser() in as part of a tryCatch(), such that when the error is generated you can use the browser() interface.

geoffjentry
  • 4,674
  • 3
  • 31
  • 37
0

I've faced the same problem and have tended to make my calls with (l)(m)(s)(t)apply to be functions that I can debug().

So, instead of blah<-sapply(x,function(x){ x+1 })

I'd say,

 myfn<-function(x){x+1}
 blah<-sapply(x,function(x){myfn(x)})

and use debug(myfn) with options(error=recover).

I also like the advice about sticking print() lines here and there to see what is happening.

Even better is to design a test of myfn(x) that it has to pass and to be sure it passes said test before subjecting it to sapply. I only have patience to to this about half the time.

Jake
  • 743
  • 1
  • 5
  • 10