This is a list of canonical ways to do certain things in R. If you have a suggestion to include on this list, please let me know.

1. How to check the class of an object

I recently contributed to a fix for assertr package. This was the problematic line of code:

if (class(log.mat) == "logical")

This triggered a warning:

warning "the condition has length > 1 and only the first element will be used"

This happened because the object had multiple classes, not only one. To avoid this kind of problems, is better to use the functions base::inherits or methods::is:

x <- list(1)
class(x)
# "list"
is.list(x)
# TRUE
methods::is(object = x, class2 = "list")
# TRUE
inherits(x = x, what = "list")
# TRUE

class(x) <- c("list", "my_special_list")
class(x)
# "list"            "my_special_list"
is.list(x)
# TRUE
inherits(x = x, what = "list")
# TRUE
inherits(x = x, what = "my_special_list")
# TRUE

The differences between base::inherits and methods::is, as explained in the documentation (?class) and here are:

  • The two functions behave consistently with one exception: S4 classes can have conditional inheritance, with an explicit test. In this case, is will test the condition, but inherits ignores all conditional superclasses.
  • A crucial difference is that is is in package methods which is by default not loaded when running Rscript (because it’s slow to load). inherits, by contrast, is from base and therefore readily available in R script programs. 
  • The most obvious place where the two functions differ is when checking if integers are numeric:
    class(1L)
    ## [1] "integer"
    is.numeric(1L)
    ## [1] TRUE
    is(1L, "numeric")
    ## [1] TRUE
    inherits(1L, "numeric")
    ## [1] FALSE

    For more details, see for example this blog entry.

To summarize, instead class(x) == "foo", you should use inherits(x, "foo") or maybe alternatively is(x, "foo").

2. How to create a missing variable

A few days ago I saw this very strange (to me) piece of R code:

quote(expr =)

If this is strange to you, read on (and yes, this is perfectly valid R code).

While trying to understand what it does, I found an example on stackoverflow:

 foo <- function(x, do_round) {
    x <- x * 0.01
    if (!missing(do_round)) {
      message("do_round not missing")
      message("do_round: ", do_round)
      round(x)
    } else {
      x
    }
  }

The question was: if I call this function from another function bar,  and x < 10 then do_round in bar() is not defined, but is passed to foo anyway. How can I pass “missing”?

 bar <-function(x) {
    if (x > 10) {
      do_round <- TRUE
    } 
    foo(x = x, do_round = do_round)
  }
  
  bar(x = 9)
  # do_round not missing
  # Error in message("do_round: ", do_round) : object 'do_round' not found 

It turns out that the canonical way to create a missing variable in R is using quote(expr =). An alternative is to use rlang::missing_arg:

bar_missing <-function(x) {
  if (x > 10) {
    do_round <- TRUE
  } else {
    do_round <- quote(expr =)
    #do_round <- rlang::missing_arg()
    foo(x = x, do_round = do_round)
  }
}

bar_missing(x = 9)
# 0.09

I would have rather solved the problem by using default arguments:

foo <- function(x, do_round = FALSE) {
  x <- x * 0.01
  if (do_round) {
    message("do_round not missing")
    message("do_round: ", do_round)
    round(x)
  } else {
    x
  }
}
bar <-function(x) {
  do_round <- x > 10
  foo(x = x, do_round = do_round)
}
bar(9)
# 0.09

but just in case you ever find yourself in the situation where you need to create a missing  variable, you now know how to do it.

Final thought

Just in case you wonder why should you care about all these details, here is a reason: 

Jono Hey - sketchplanations.com

A little bit of slope makes up for a lot of y-intercept.

sketchexplained a little bit of slope makes up for a lot of y-intercept

 

Make a promise. Show up. Do the work. Repeat.