Javascript required
Skip to content Skip to sidebar Skip to footer

If Error Try Again in R While

You lot're reading the kickoff edition of Advanced R; for the latest on this topic, encounter the Conditions and Debugging capacity in the second edition.

Debugging, condition treatment, and defensive programming

What happens when something goes wrong with your R code? What do you practise? What tools practise you have to address the problem? This chapter will teach you lot how to fix unanticipated problems (debugging), show yous how functions can communicate issues and how you can have action based on those communications (condition handling), and teach you how to avoid common problems before they occur (defensive programming).

Debugging is the fine art and science of fixing unexpected problems in your code. In this section you lot'll larn the tools and techniques that aid you get to the root cause of an error. You lot'll larn full general strategies for debugging, useful R functions like traceback() and browser(), and interactive tools in RStudio.

Not all problems are unexpected. When writing a role, you can often conceptualize potential problems (similar a not-existent file or the incorrect blazon of input). Communicating these problems to the user is the job of weather condition: errors, warnings, and messages.

  • Fatal errors are raised by stop() and force all execution to terminate. Errors are used when at that place is no way for a role to continue.

  • Warnings are generated by warning() and are used to display potential problems, such as when some elements of a vectorised input are invalid, like log(-ane:ii).

  • Messages are generated past message() and are used to requite informative output in a mode that can hands be suppressed by the user (?suppressMessages()). I ofttimes utilise messages to let the user know what value the function has chosen for an of import missing argument.

Atmospheric condition are unremarkably displayed prominently, in a assuming font or coloured cherry-red depending on your R interface. Y'all can tell them apart because errors always start with "Error" and warnings with "Warning bulletin". Part authors can also communicate with their users with print() or true cat(), simply I think that'due south a bad idea because it's hard to capture and selectively ignore this sort of output. Printed output is not a status, so yous can't employ any of the useful status treatment tools yous'll learn about below.

Status handling tools, similar withCallingHandlers(), tryCatch(), and try() permit you to take specific deportment when a condition occurs. For instance, if you're fitting many models, you might want to go on fitting the others even if one fails to converge. R offers an exceptionally powerful condition treatment arrangement based on ideas from Common Lisp, but it's currently not very well documented or often used. This chapter will introduce you to the most important basics, but if you want to acquire more than, I recommend the following two sources:

  • A epitome of a condition arrangement for R by Robert Gentleman and Luke Tierney. This describes an early on version of R's status system. While the implementation has inverse somewhat since this certificate was written, it provides a skilful overview of how the pieces fit together, and some motivation for its design.

  • Across Exception Handling: Weather and Restarts by Peter Seibel. This describes exception treatment in Lisp, which happens to be very similar to R'southward approach. It provides useful motivation and more sophisticated examples. I have provided an R translation of the affiliate at http://adv-r.had.co.nz/across-exception-handling.html.

The affiliate concludes with a word of "defensive" programming: ways to avoid common errors before they occur. In the short run y'all'll spend more than time writing lawmaking, merely in the long run y'all'll save time because mistake messages volition exist more informative and will permit you narrow in on the root cause more quickly. The basic principle of defensive programming is to "fail fast", to raise an error every bit shortly every bit something goes wrong. In R, this takes iii item forms: checking that inputs are correct, avoiding not-standard evaluation, and fugitive functions that tin can return dissimilar types of output.

Quiz

Want to skip this chapter? Become for it, if y'all can answer the questions below. Find the answers at the end of the chapter in answers.

  1. How tin can y'all find out where an fault occurred?

  2. What does browser() do? List the five useful single-cardinal commands that you tin can employ inside of a browser() environment.

  3. What part exercise you employ to ignore errors in block of code?

  4. Why might yous desire to create an mistake with a custom S3 form?

Outline
  1. Debugging techniques outlines a general arroyo for finding and resolving bugs.

  2. Debugging tools introduces you to the R functions and RStudio features that assistance you locate exactly where an error occurred.

  3. Condition handling shows yous how you can take hold of conditions (errors, warnings, and messages) in your ain code. This allows you to create code that's both more robust and more than informative in the presence of errors.

  4. Defensive programming introduces you to some important techniques for defensive programming, techniques that assistance prevent bugs from occurring in the first place.

Debugging techniques

"Finding your bug is a process of confirming the many things that you lot believe are true — until you find 1 which is not true."

—Norm Matloff

Debugging lawmaking is challenging. Many bugs are subtle and hard to notice. Indeed, if a bug was obvious, you probably would've been able to avoid it in the first place. While it'due south true that with a good technique, y'all tin productively debug a problem with just print(), there are times when additional aid would be welcome. In this department, we'll hash out some useful tools, which R and RStudio provide, and outline a general procedure for debugging.

While the procedure below is past no means foolproof, information technology will hopefully help you to organise your thoughts when debugging. There are four steps:

  1. Realise that you have a bug

    If you're reading this affiliate, you lot've probably already completed this step. Information technology is a surprisingly important one: you tin can't fix a bug until y'all know it exists. This is 1 reason why automated test suites are of import when producing high-quality code. Unfortunately, automated testing is outside the scope of this book, simply you can read more than about information technology at http://r-pkgs.had.co.nz/tests.html.

  2. Make information technology repeatable

    In one case y'all've determined you take a bug, you need to be able to reproduce it on command. Without this, it becomes extremely hard to isolate its cause and to confirm that you've successfully stock-still it.

    Generally, y'all volition get-go with a big block of code that you lot know causes the error then slowly whittle information technology down to get to the smallest possible snippet that still causes the fault. Binary search is particularly useful for this. To do a binary search, y'all repeatedly remove half of the code until you lot observe the bug. This is fast because, with each step, you lot reduce the amount of code to await through past half.

    If it takes a long time to generate the bug, it'due south also worthwhile to effigy out how to generate it faster. The quicker yous can do this, the quicker you lot can figure out the cause.

    Every bit you lot piece of work on creating a minimal instance, you'll as well notice similar inputs that don't trigger the issues. Make notation of them: they volition be helpful when diagnosing the cause of the issues.

    If you're using automated testing, this is also a skillful time to create an automated test case. If your existing test coverage is low, have the opportunity to add some nearby tests to ensure that existing expert behaviour is preserved. This reduces the chances of creating a new issues.

  3. Figure out where it is

    If you're lucky, one of the tools in the following section will assistance yous to quickly identify the line of code that'due south causing the bug. Usually, even so, you'll have to recollect a bit more almost the problem. It's a great idea to adopt the scientific method. Generate hypotheses, design experiments to test them, and record your results. This may seem like a lot of work, only a systematic approach will end upwards saving you time. I ofttimes waste a lot of time relying on my intuition to solve a bug ("oh, it must be an off-past-one error, and then I'll just decrease 1 hither"), when I would take been ameliorate off taking a systematic approach.

  4. Set it and test it

    Once you've found the problems, you need to figure out how to fix it and to cheque that the fix actually worked. Over again, it'due south very useful to have automated tests in place. Not simply does this help to ensure that yous've actually fixed the bug, information technology too helps to ensure yous oasis't introduced any new bugs in the procedure. In the absence of automatic tests, brand certain to carefully tape the right output, and cheque confronting the inputs that previously failed.

To implement a strategy of debugging, you'll need tools. In this section, you lot'll larn near the tools provided by R and the RStudio IDE. RStudio's integrated debugging support makes life easier past exposing existing R tools in a user friendly style. I'll show y'all both the R and RStudio ways so that you tin can piece of work with whatever environment yous apply. You may also want to refer to the official RStudio debugging documentation which always reflects the tools in the latest version of RStudio.

There are three key debugging tools:

  • RStudio'southward fault inspector and traceback() which list the sequence of calls that atomic number 82 to the error.

  • RStudio's "Rerun with Debug" tool and options(error = browser) which open up an interactive session where the error occurred.

  • RStudio'south breakpoints and browser() which open an interactive session at an arbitrary location in the code.

I'll explain each tool in more than particular below.

You lot shouldn't demand to utilize these tools when writing new functions. If you find yourself using them oftentimes with new code, you may want to reconsider your approach. Instead of trying to write 1 large function all at once, work interactively on small pieces. If you start small, you can chop-chop identify why something doesn't piece of work. Just if you start big, you may stop up struggling to identify the source of the problem.

Determining the sequence of calls

The first tool is the call stack, the sequence of calls that atomic number 82 up to an error. Here's a simple example: you can meet that f() calls one thousand() calls h() calls i() which adds together a number and a cord creating a error:

            f <-                            function(a)              yard(a) g <-                            part(b)              h(b) h <-                            function(c)              i(c) i <-                            role(d)              "a"              +                            d              f(x)          

When nosotros run this code in RStudio nosotros run across:

Ii options appear to the right of the mistake message: "Show Traceback" and "Rerun with Debug". If yous click "Bear witness traceback" you come across:

If yous're not using RStudio, you can use traceback() to become the same information:

                          traceback()              # 4: i(c) at exceptions-example.R#3              # iii: h(b) at exceptions-example.R#2              # ii: g(a) at exceptions-example.R#1              # 1: f(10)                      

Read the call stack from bottom to top: the initial call is f(), which calls k(), then h(), then i(), which triggers the fault. If you're calling lawmaking that you source()d into R, the traceback volition also display the location of the function, in the grade filename.r#linenumber. These are clickable in RStudio, and will accept you to the corresponding line of code in the editor.

Sometimes this is enough information to let you lot rails downwards the error and ready it. However, it's usually not. traceback() shows you lot where the error occurred, but not why. The next useful tool is the interactive debugger, which allows you to pause execution of a function and interactively explore its land.

Browsing on fault

The easiest way to enter the interactive debugger is through RStudio'southward "Rerun with Debug" tool. This reruns the control that created the mistake, pausing execution where the mistake occurred. You're now in an interactive state inside the function, and you can interact with any object defined there. You'll run into the respective lawmaking in the editor (with the statement that will be run next highlighted), objects in the current environment in the "Environs" pane, the call stack in a "Traceback" pane, and you tin can run arbitrary R lawmaking in the panel.

Likewise as whatsoever regular R function, in that location are a few special commands you can use in debug mode. You can access them either with the RStudio toolbar () or with the keyboard:

  • Next, n: executes the next stride in the function. Be careful if you accept a variable named due north; to print information technology y'all'll need to do print(due north).

  • Footstep into, or southward: works like side by side, but if the next stride is a function, it will pace into that function so you can work through each line.

  • Finish, or f: finishes execution of the current loop or office.

  • Go on, c: leaves interactive debugging and continues regular execution of the office. This is useful if y'all've stock-still the bad state and want to bank check that the office gain correctly.

  • Stop, Q: stops debugging, terminates the function, and returns to the global workspace. Utilise this once you lot've figured out where the trouble is, and you're set to ready it and reload the code.

At that place are 2 other slightly less useful commands that aren't available in the toolbar:

  • Enter: repeats the previous command. I find this too easy to activate accidentally, and so I turn it off using options(browserNLdisabled = TRUE).

  • where: prints stack trace of active calls (the interactive equivalent of traceback).

To enter this style of debugging outside of RStudio, you tin use the error option which specifies a function to run when an fault occurs. The function most like to Rstudio'southward debug is browser(): this volition start an interactive console in the environment where the error occurred. Apply options(fault = browser) to turn it on, re-run the previous command, so apply options(error = NULL) to return to the default error behaviour. You lot could automate this with the browseOnce() function as defined below:

            browseOnce <-                            function() {   erstwhile <-                            getOption("mistake")              function() {              options(fault =              quondam)              browser()   } }              options(error =              browseOnce())  f <-                            function()              finish("!")              # Enters browser              f()              # Runs usually              f()          

(You'll learn more about functions that render functions in Functional programming.)

There are two other useful functions that you can apply with the error pick:

  • recover is a step upwardly from browser, every bit it allows you lot to enter the surroundings of any of the calls in the telephone call stack. This is useful because oftentimes the root cause of the fault is a number of calls back.

  • dump.frames is an equivalent to recover for not-interactive code. Information technology creates a last.dump.rda file in the current working directory. Then, in a later on interactive R session, you load that file, and utilize debugger() to enter an interactive debugger with the same interface as recover(). This allows interactive debugging of batch code.

                                      # In batch R process ----                  dump_and_quit <-                                    function() {                  # Save debugging info to file last.dump.rda                  dump.frames(to.file =                  True)                  # Quit R with mistake status                  q(status =                  1) }                  options(mistake =                  dump_and_quit)                  # In a later interactive session ----                  load("last.dump.rda")                  debugger()              

To reset fault behaviour to the default, utilize options(fault = Cypher). So errors volition print a message and abort function execution.

Browsing arbitrary code

Also as entering an interactive console on error, yous can enter it at an arbitrary code location by using either an Rstudio breakpoint or browser(). You can set a breakpoint in Rstudio by clicking to the left of the line number, or pressing Shift + F9. Equivalently, add browser() where you want execution to pause. Breakpoints comport similarly to browser() but they are easier to set (1 click instead of nine key presses), and you don't run the risk of accidentally including a browser() statement in your source code. There are 2 minor downsides to breakpoints:

  • There are a few unusual situations in which breakpoints volition not work: read breakpoint troubleshooting for more than details.

  • RStudio currently does not back up conditional breakpoints, whereas you can always put browser() inside an if statement.

As well as calculation browser() yourself, in that location are two other functions that will add information technology to code:

  • debug() inserts a browser statement in the offset line of the specified function. undebug() removes information technology. Alternatively, yous can use debugonce() to browse only on the adjacent run.

  • utils::setBreakpoint() works similarly, simply instead of taking a function name, it takes a file name and line number and finds the appropriate function for you.

These two functions are both special cases of trace(), which inserts arbitrary code at any position in an existing function. trace() is occasionally useful when you're debugging lawmaking that you don't accept the source for. To remove tracing from a function, use untrace(). You lot can only perform one trace per function, just that one trace tin can call multiple functions.

The call stack: traceback(), where, and recover()

Unfortunately the call stacks printed past traceback(), browser() + where, and recover() are non consistent. The post-obit table shows how the call stacks from a simple nested fix of calls are displayed by the three tools.

traceback() where recover()
iv: stop("Error") where 1: stop("Fault") one: f()
three: h(x) where 2: h(x) ii: g(x)
2: g(10) where three: k(x) three: h(x)
1: f() where 4: f()

Note that numbering is different between traceback() and where, and that recover() displays calls in the contrary guild, and omits the call to terminate(). RStudio displays calls in the same club every bit traceback() simply omits the numbers.

Other types of failure

There are other ways for a role to neglect autonomously from throwing an mistake or returning an incorrect result.

  • A role may generate an unexpected alarm. The easiest fashion to track down warnings is to convert them into errors with options(warn = 2) and employ the regular debugging tools. When you do this yous'll see some extra calls in the call stack, like doWithOneRestart(), withOneRestart(), withRestarts(), and .signalSimpleWarning(). Ignore these: they are internal functions used to turn warnings into errors.

  • A part may generate an unexpected message. There's no built-in tool to assist solve this problem, just it'southward possible to create 1:

                    message2error <-                                    role(code) {                  withCallingHandlers(code,                  message =                  function(e)                  stop(due east)) }  f <-                                    part()                  1000() g <-                                    function()                  message("Hi!")                  g()                  # Hi!                                    message2error(g())                  # Error in message("Hi!"): Hello!                  traceback()                  # 10: stop(eastward) at #2                  # 9: (function (e) finish(e))(list(bulletin = "Hi!\due north",                                    #      call = message("Howdy!")))                  # viii: signalCondition(cond)                  # seven: doWithOneRestart(return(expr), restart)                  # half-dozen: withOneRestart(expr, restarts[[1L]])                  # 5: withRestarts()                  # 4: bulletin("How-do-you-do!") at #1                  # 3: g()                  # 2: withCallingHandlers(code, message = function(e) stop(e))                                    #      at #2                  # 1: message2error(g())                              

    Equally with warnings, y'all'll need to ignore some of the calls on the traceback (i.e., the commencement two and the last seven).

  • A part might never render. This is especially hard to debug automatically, but sometimes terminating the function and looking at the call stack is informative. Otherwise, apply the basic debugging strategies described in a higher place.

  • The worst scenario is that your code might crash R completely, leaving yous with no way to interactively debug your code. This indicates a issues in the underlying C lawmaking. This is hard to debug. Sometimes an interactive debugger, like gdb, can be useful, but describing how to use information technology is beyond the scope of this book.

    If the crash is caused by base R lawmaking, postal service a reproducible example to R-aid. If information technology's in a package, contact the package maintainer. If it'south your ain C or C++ code, you'll demand to use numerous print() statements to narrow down the location of the issues, so you'll need to utilize many more print statements to figure out which information structure doesn't accept the properties that you lot expect.

Condition handling

Unexpected errors crave interactive debugging to figure out what went wrong. Some errors, even so, are expected, and you lot want to handle them automatically. In R, expected errors crop upwards most frequently when you're fitting many models to dissimilar datasets, such as bootstrap replicates. Sometimes the model might fail to fit and throw an error, merely yous don't want to finish everything. Instead, you want to fit equally many models equally possible and then perform diagnostics after the fact.

In R, there are 3 tools for handling weather (including errors) programmatically:

  • try() gives yous the ability to continue execution even when an fault occurs.

  • tryCatch() lets you specify handler functions that command what happens when a condition is signalled.

  • withCallingHandlers() is a variant of tryCatch() that establishes local handlers, whereas tryCatch() registers exiting handlers. Local handlers are called in the same context equally where the condition is signalled, without interrupting the execution of the function. When a exiting handler from tryCatch() is called, the execution of the function is interrupted and the handler is called. withCallingHandlers() is rarely needed, simply is useful to be aware of.

The following sections describe these tools in more detail.

Ignore errors with try

try() allows execution to continue even after an error has occurred. For instance, ordinarily if you run a function that throws an error, it terminates immediately and doesn't return a value:

            f1 <-                            role(x) {              log(x)              10              }              f1("x")              #> Mistake in log(x): non-numeric argument to mathematical function                      

However, if you lot wrap the argument that creates the error in endeavour(), the error message will be printed but execution volition continue:

            f2 <-                            function(10) {              try(log(x))              10              }              f2("a")              #> Error in log(x) : non-numeric argument to mathematical function              #> [one] 10                      

You can suppress the message with endeavor(..., silent = Truthful).

To pass larger blocks of lawmaking to try(), wrap them in {}:

                          try({   a <-                            1              b <-                "x"              a              +                            b })              #> Error in a + b : non-numeric statement to binary operator                      

Y'all can also capture the output of the effort() function. If successful, it will be the last effect evaluated in the cake (but like a function). If unsuccessful it will exist an (invisible) object of grade "attempt-error":

            success <-                            endeavor(i              +                                          2) failure <-                            endeavour("a"              +                              "b")              #> Mistake in "a" + "b" : non-numeric argument to binary operator              class(success)              #> [1] "numeric"              class(failure)              #> [1] "endeavor-mistake"                      

effort() is particularly useful when you're applying a function to multiple elements in a list:

            elements <-                            list(1              :              10,              c(-              one,              10),              c(Truthful,              FALSE), letters) results <-                            lapply(elements, log)              #> Warning in FUN(X[[i]], ...): NaNs produced              #> Fault in FUN(10[[i]], ...): not-numeric statement to mathematical function              results <-                            lapply(elements,              part(ten)              attempt(log(10)))              #> Warning in log(x): NaNs produced              #> Error in log(x) : non-numeric argument to mathematical role                      

There isn't a congenital-in function to test for the effort-fault class, so we'll define one. Then you can hands find the locations of errors with sapply() (as discussed in Functionals), and extract the successes or look at the inputs that lead to failures.

            is.error <-                            role(10)              inherits(x,              "try-error") succeeded <-                            !              vapply(results, is.error,              logical(ane))              # look at successful results              str(results[succeeded])              #> Listing of 3              #>  $ : num [1:10] 0 0.693 one.099 1.386 1.609 ...              #>  $ : num [i:two] NaN 2.3              #>  $ : num [one:2] 0 -Inf              # look at inputs that failed              str(elements[!succeeded])              #> List of i              #>  $ : chr [i:26] "a" "b" "c" "d" ...                      

Another useful try() idiom is using a default value if an expression fails. Simply assign the default value outside the try cake, and then run the risky code:

            default <-                            NULL              try(default <-                            read.csv("possibly-bad-input.csv"),              silent =              Truthful)          

At that place is as well plyr::failwith(), which makes this strategy even easier to implement. See Function Operators for more details.

Handle conditions with tryCatch()

tryCatch() is a general tool for handling conditions: in improver to errors, yous tin have unlike actions for warnings, messages, and interrupts. Yous've seen errors (made past stop()), warnings (warning()) and messages (message()) earlier, but interrupts are new. They can't exist generated directly past the programmer, just are raised when the user attempts to terminate execution by pressing Ctrl + Break, Escape, or Ctrl + C (depending on the platform).

With tryCatch() you map conditions to handlers, named functions that are called with the status as an input. If a condition is signalled, tryCatch() will telephone call the showtime handler whose name matches one of the classes of the status. The only useful born names are error, alert, message, interrupt, and the catch-all condition. A handler office can do anything, but typically information technology volition either return a value or create a more informative error message. For example, the show_condition() function beneath sets up handlers that render the blazon of condition signalled:

            show_condition <-                            function(code) {              tryCatch(code,              mistake =              function(c)              "error",              warning =              part(c)              "alert",              message =              function(c)              "message"              ) }              show_condition(stop("!"))              #> [1] "error"              show_condition(warning("?!"))              #> [1] "warning"              show_condition(message("?"))              #> [1] "message"              # If no condition is captured, tryCatch returns the                            # value of the input              show_condition(x)              #> [ane] 10                      

You can utilise tryCatch() to implement try(). A elementary implementation is shown beneath. base::attempt() is more complicated in order to make the error bulletin look more like what you'd see if tryCatch() wasn't used. Note the use of conditionMessage() to extract the message associated with the original error.

            try2 <-                            function(code,              silent =              Fake) {              tryCatch(code,              error =              function(c) {     msg <-                            conditionMessage(c)              if              (!silent)              message(c)              invisible(structure(msg,              class =              "try-error"))   }) }              try2(1)              #> [1] 1              try2(finish("Hi"))              #> Mistake in doTryCatch(return(expr), name, parentenv, handler): Hi              try2(stop("Howdy"),              silent =              Truthful)          

Also every bit returning default values when a condition is signalled, handlers can be used to make more informative error messages. For example, by modifying the message stored in the error status object, the following function wraps read.csv() to add the file name to any errors:

            read.csv2 <-                            role(file, ...) {              tryCatch(read.csv(file, ...),              error =              function(c) {     c$message <-                            paste0(c$bulletin,              " (in ", file,              ")")              stop(c)   }) }              read.csv("lawmaking/dummy.csv")              #> Fault in file(file, "rt"): cannot open up the connection              read.csv2("lawmaking/dummy.csv")              #> Error in file(file, "rt"): cannot open the connection (in lawmaking/dummy.csv)                      

Communicable interrupts tin exist useful if you desire to take special activeness when the user tries to arrest running lawmaking. But exist careful, it's easy to create a loop that yous tin never escape (unless you kill R)!

                          # Don't allow the user interrupt the code              i <-                            i              while(i              <                                          3) {              tryCatch({              Sys.sleep(0.v)              message("Endeavour to escape")   },              interrupt =              function(x) {              message("Attempt again!")     i <<-              i              +                                          1              }) }          

tryCatch() has one other argument: finally. It specifies a block of lawmaking (not a office) to run regardless of whether the initial expression succeeds or fails. This tin be useful for clean up (eastward.g., deleting files, closing connections). This is functionally equivalent to using on.leave() merely it can wrap smaller chunks of lawmaking than an unabridged office.

withCallingHandlers()

An culling to tryCatch() is withCallingHandlers(). The difference between the two is that the erstwhile establishes exiting handlers while the latter registers local handlers. Here the main differences between the two kind of handlers:

  • The handlers in withCallingHandlers() are called in the context of the call that generated the condition whereas the handlers in tryCatch() are chosen in the context of tryCatch(). This is shown hither with sys.calls(), which is the run-time equivalent of traceback() — information technology lists all calls leading to the electric current function.

                    f <-                                    part()                  yard() g <-                                    function()                  h() h <-                                    function()                  finish("!")                  tryCatch(f(),                  fault =                  function(e)                  print(sys.calls()))                  # [[one]] tryCatch(f(), error = function(east) impress(sys.calls()))                  # [[2]] tryCatchList(expr, classes, parentenv, handlers)                  # [[3]] tryCatchOne(expr, names, parentenv, handlers[[1L]])                  # [[iv]] value[[3L]](cond)                  withCallingHandlers(f(),                  mistake =                  role(east)                  impress(sys.calls()))                  # [[1]] withCallingHandlers(f(),                                    #    mistake = function(e) impress(sys.calls()))                  # [[two]] f()                  # [[3]] g()                  # [[4]] h()                  # [[5]] stop("!")                  # [[6]] .handleSimpleError(                  #    function (e) impress(sys.calls()), "!", quote(h()))                  # [[7]] h(simpleError(msg, telephone call))                              

    This also affects the order in which on.go out() is called.

  • A related difference is that with tryCatch(), the catamenia of execution is interrupted when a handler is called, while with withCallingHandlers(), execution continues normally when the handler returns. This includes the signalling function which continues its course later on having called the handler (eastward.chiliad., stop() will continue stopping the program and message() or warning() will continue signalling a bulletin/warning). This is why it is often amend to handle a message with withCallingHandlers() rather than tryCatch(), since the latter will stop the program:

                    message_handler <-                                    function(c)                  true cat("Defenseless a bulletin!                  \n                  ")                  tryCatch(message =                  message_handler, {                  message("Someone there?")                  message("Why, yes!") })                  #> Caught a bulletin!                  withCallingHandlers(message =                  message_handler, {                  message("Someone there?")                  message("Why, yep!") })                  #> Defenseless a bulletin!                  #> Someone in that location?                  #> Defenseless a message!                  #> Why, yes!                              
  • The return value of a handler is returned by tryCatch(), whereas it is ignored with withCallingHandlers():

                    f <-                                    part()                  message("!")                  tryCatch(f(),                  message =                  function(m)                  1)                  #> [1] 1                  withCallingHandlers(f(),                  message =                  function(m)                  1)                  #> !                              

These subtle differences are rarely useful, except when yous're trying to capture exactly what went wrong and pass information technology on to another function. For most purposes, you should never demand to use withCallingHandlers().

Custom bespeak classes

I of the challenges of error handling in R is that most functions simply phone call terminate() with a string. That means if y'all want to figure out if a item error occurred, you have to look at the text of the fault message. This is mistake decumbent, not only because the text of the error might change over time, but besides because many error letters are translated, and so the message might exist completely different to what you expect.

R has a little known and piddling used feature to solve this problem. Weather condition are S3 classes, and so you can define your own classes if yous want to distinguish different types of error. Each status signalling function, terminate(), alert(), and bulletin(), can be given either a listing of strings, or a custom S3 status object. Custom status objects are not used very oftentimes, merely are very useful because they brand it possible for the user to respond to unlike errors in unlike ways. For example, "expected" errors (similar a model failing to converge for some input datasets) tin can exist silently ignored, while unexpected errors (like no disk space available) can exist propagated to the user.

R doesn't come with a built-in constructor office for conditions, but we tin easily add one. Conditions must contain message and call components, and may contain other useful components. When creating a new condition, information technology should always inherit from condition and should in most cases inherit from 1 of error, alert, or message.

            status <-                            function(bracket, message,              telephone call =              sys.call(-              1), ...) {              structure(              class =              c(subclass,              "condition"),              list(message =              message,              call =              call),     ...   ) } is.status <-                            function(x)              inherits(x,              "condition")          

You can signal an capricious condition with signalCondition(), but null volition happen unless you lot've instantiated a custom point handler (with tryCatch() or withCallingHandlers()). Instead, pass this condition to terminate(), warning(), or message() equally appropriate to trigger the usual treatment. R won't mutter if the class of your condition doesn't friction match the function, merely in existent code yous should laissez passer a condition that inherits from the appropriate class: "fault" for stop(), "warning" for alarm(), and "message" for bulletin().

            eastward <-                            condition(c("my_error",              "error"),              "This is an error")              signalCondition(east)              # NULL              end(east)              # Mistake: This is an fault              westward <-                            condition(c("my_warning",              "warning"),              "This is a warning")              warning(w)              # Warning message: This is a alert              yard <-                            condition(c("my_message",              "bulletin"),              "This is a message")              message(k)              # This is a message                      

You tin can then use tryCatch() to take unlike actions for different types of errors. In this example nosotros make a convenient custom_stop() office that allows us to signal mistake conditions with arbitrary classes. In a real application, it would be meliorate to have individual S3 constructor functions that yous could certificate, describing the error classes in more detail.

            custom_stop <-                            function(bracket, message,              call =              sys.telephone call(-              i),                          ...) {   c <-                            condition(c(subclass,              "error"), message,              call =              call, ...)              stop(c) }  my_log <-                            function(x) {              if              (!              is.numeric(ten))              custom_stop("invalid_class",              "my_log() needs numeric input")              if              (whatever(x              <                                          0))              custom_stop("invalid_value",              "my_log() needs positive inputs")              log(x) }              tryCatch(              my_log("a"),              invalid_class =              office(c)              "course",              invalid_value =              function(c)              "value"              )              #> [1] "form"                      

Note that when using tryCatch() with multiple handlers and custom classes, the first handler to match any class in the signal'south class hierarchy is called, not the best match. For this reason, y'all demand to make certain to put the most specific handlers first:

                          tryCatch(custom_stop("my_error",              "!"),              fault =              function(c)              "error",              my_error =              office(c)              "my_error"              )              #> [1] "mistake"              tryCatch(custom_stop("my_error",              "!"),              my_error =              function(c)              "my_error",              error =              function(c)              "error"              )              #> [1] "my_error"                      

Exercises

  • Compare the following two implementations of message2error(). What is the master reward of withCallingHandlers() in this scenario? (Hint: wait carefully at the traceback.)

                    message2error <-                                    function(code) {                  withCallingHandlers(code,                  message =                  function(eastward)                  stop(due east)) } message2error <-                                    part(lawmaking) {                  tryCatch(code,                  message =                  office(eastward)                  end(e)) }              

Defensive programming

Defensive programming is the art of making code fail in a well-defined style fifty-fifty when something unexpected occurs. A key principle of defensive programming is to "fail fast": as soon as something incorrect is discovered, signal an error. This is more piece of work for the writer of the function (you!), but information technology makes debugging easier for users because they get errors earlier rather than later, after unexpected input has passed through several functions.

In R, the "fail fast" principle is implemented in three means:

  • Be strict about what you accept. For instance, if your function is not vectorised in its inputs, but uses functions that are, make certain to check that the inputs are scalars. You tin can utilize stopifnot(), the assertthat package, or simple if statements and cease().

  • Avert functions that utilise non-standard evaluation, like subset, transform, and with. These functions salve time when used interactively, just considering they make assumptions to reduce typing, when they fail, they oftentimes fail with uninformative mistake messages. You lot tin acquire more than about non-standard evaluation in not-standard evaluation.

  • Avoid functions that render unlike types of output depending on their input. The two biggest offenders are [ and sapply(). Whenever subsetting a information frame in a function, you lot should always utilize drop = FALSE, otherwise you volition accidentally convert 1-column data frames into vectors. Similarly, never utilise sapply() inside a role: always use the stricter vapply() which will throw an mistake if the inputs are incorrect types and return the correct type of output even for zero-length inputs.

In that location is a tension between interactive analysis and programming. When you're working interactively, you want R to do what yous mean. If information technology guesses wrong, you want to discover that right abroad so you lot can gear up it. When you're programming, y'all want functions that signal errors if annihilation is even slightly wrong or underspecified. Go on this tension in mind when writing functions. If y'all're writing functions to facilitate interactive data analysis, experience gratuitous to guess what the analyst wants and recover from small misspecifications automatically. If yous're writing functions for programming, be strict. Never try to judge what the caller wants.

Exercises

  • The goal of the col_means() function defined below is to compute the means of all numeric columns in a data frame.

                    col_means <-                                    function(df) {   numeric <-                                    sapply(df, is.numeric)   numeric_cols <-                  df[, numeric]                  information.frame(lapply(numeric_cols, mean)) }              

    Even so, the office is not robust to unusual inputs. Expect at the post-obit results, make up one's mind which ones are wrong, and modify col_means() to be more robust. (Hint: there are two role calls in col_means() that are particularly prone to issues.)

                                      col_means(mtcars)                  col_means(mtcars[,                  0])                  col_means(mtcars[0, ])                  col_means(mtcars[,                  "mpg",                  drop =                  F])                  col_means(i                  :                  10)                  col_means(as.matrix(mtcars))                  col_means(as.list(mtcars))  mtcars2 <-                  mtcars mtcars2[-                  i] <-                                    lapply(mtcars2[-                  i], as.character)                  col_means(mtcars2)              
  • The following function "lags" a vector, returning a version of x that is n values behind the original. Ameliorate the function then that information technology (1) returns a useful error message if due north is not a vector, and (2) has reasonable behaviour when n is 0 or longer than x.

                    lag <-                                    function(ten,                  n =                  1L) {   xlen <-                                    length(x)                  c(rep(NA, n), x[seq_len(xlen                  -                                    northward)]) }              

Quiz answers

  1. The most useful tool to determine where a error occurred is traceback(). Or apply RStudio, which displays it automatically where an fault occurs.

  2. browser() pauses execution at the specified line and allows you to enter an interactive surround. In that surround, at that place are five useful commands: n, execute the next command; s, step into the side by side role; f, cease the electric current loop or function; c, continue execution normally; Q, end the office and return to the console.

  3. Yous could use try() or tryCatch().

  4. Because y'all can then capture specific types of mistake with tryCatch(), rather than relying on the comparing of error strings, which is risky, especially when messages are translated.

gellatlysteranded.blogspot.com

Source: http://adv-r.had.co.nz/Exceptions-Debugging.html