32

If I want to check whether a variable inherits from some class, I can either use is or inherits.

class(letters)
## [1] "character"
is(letters, "character")
## [1] TRUE
inherits(letters, "character")
## [1] TRUE

Is there a preference for which one I should use, and do they ever return different values?

Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • @Richie as you both asked and answered this question, can you include `"character" %in% class(letters)` to your question/answer? My understanding given this post is that it's equivalent to inherits, just slower, but i'm not sure. – moodymudskipper Jun 12 '18 at 13:46
  • @Moody_Mudskipper Yes, that also works, though the intention of the code is less clear, so I'm not sure when you'd want to use it. – Richie Cotton Jun 14 '18 at 16:23
  • I used it in this answer for example: https://stackoverflow.com/questions/18746456/simplified-dput-in-r . `is.list(iris)` is `TRUE` but `"list" %in% class(iris)` and `inherits(iris,"list")` are `FALSE`, which is useful to distinguish data.frames from lists. Should I understand, to hammer the nail, the 2 latter are indeed equivalent and that you recommend `inherits` in all cases ? Btw your nice datacamp course brought me here. – moodymudskipper Jun 14 '18 at 22:04

2 Answers2

14

Short version:

Use inherits, but be careful with numbers and S4 classes.


Longer version:

From the See Also section of the is help page:

inherits is nearly always equivalent to is, both for S4 and non-S4 objects, and is somewhat faster. The non-equivalence applies to classes that have conditional superclasses, with a non-trivial test= in the relation (not common and discouraged): for these, is tests for the relation but inherits by definition ignores conditional inheritance for S4 objects.

From the Formal Classes section of the inherits help page:

The analogue of inherits for formal classes is is. 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.

So they mostly return the same thing, but inherits is faster, so it should be the default choice in most cases. (As mentioned by Konrad, is also requires that the methods package is loaded, which may make it unsuitable for performance sensitive uses of Rscript.)

The values can differ if you are using S4 classes with conditional inheritance, but this is not recommended (see "Method Selection and Dispatch: Details" section), which means that it is hopefully rare.

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
Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • 1
    OK, so can you explain **why** integers don't "inherit" , whereas floats do: Rgames> class(1.4) [1] "numeric" Rgames> is.numeric(1.4) [1] TRUE Rgames> inherits(1.4,'numeric') [1] TRUE Rgames> is(1.4,'numeric') [1] TRUE – Carl Witthoft Jan 13 '15 at 14:14
  • 2
    I think this may be related to the fact that numeric objects have an *implicit* class only: from `?class`, `If the object does not have a class attribute, it has an implicit class, ‘"matrix"’, ‘"array"’ or the result of ‘mode(x)’ (except that integer vectors have implicit class ‘"integer"’).` I can imagine (??) that `is` looks at implicit classes and `inherits` doesn't ... ? – Ben Bolker Jan 13 '15 at 14:42
  • I remember overhearing a comment of high-R subjects calling this an "infelicity". – Dieter Menne Jan 13 '15 at 14:51
  • 3
    Another, quite 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. – Konrad Rudolph Jan 19 '15 at 15:18
1

Besides is() and inherits() we can also test objects with is.*() for a certain type. Those three function can return different results. Based on this answer I did the following

  • Created numerous R objects of different types
  • Extracted the type of those objects using storage.mode(), mode(), typeof() and class().
  • Tested the objects whether they are of the returned types using is(), inherits() and is.*().

Here is a small example of the three steps above:

# Get object classes withs torage.mode(), mode(), typeof() and class().
obj <- logical()
(types <- c(storage.mode= storage.mode(obj),
            mode= mode(obj),
            type= typeof(obj),
            class= class(obj)))
storage.mode         mode         type        class
    "double"    "numeric"     "double"    "numeric"

# Test returned types with is, inhertis and is.*.
> is(obj, "double"); is(obj, "numeric")
[1] FALSE
[1] TRUE
> inherits(obj, "double"); inherits(obj, "numeric")
[1] FALSE
[1] TRUE
> is.double(obj); is.numeric(obj)
[1] TRUE
[1] TRUE

Now we do this for a bunch of object types with the following code:

# Generate objects of different types.
library(xml2)
setClass("dummy", representation(x="numeric", y="numeric"))
obj <- list(
  "logical vector" = logical(),
  "integer vector" = integer(),
  "numeric vector" = numeric(),
  "complex vector" = complex(),
  "character vector" = character(),
  "raw vector" = raw(),
  "factor" = factor(),
  "logical matrix" = matrix(logical()),
  "numeric matrix" = matrix(numeric()),
  "logical array" = array(logical(8), c(2, 2, 2)),
  "numeric array" = array(numeric(8), c(2, 2, 2)),
  "list" = list(),
  "pairlist" = .Options,
  "data frame" = data.frame(),
  "closure function" = identity,
  "builtin function" = `+`,
  "special function" = `if`,
  "environment" = new.env(),
  "null" = NULL,
  "formula" = y ~ x,
  "expression" = expression(),
  "call" = call("identity"),
  "name" = as.name("x"),
  #"paren in expression" = expression((1))[[1]], # Code fails with this
  #"brace in expression" = expression({1})[[1]], # Code fails with this
  "S3 lm object" = lm(dist ~ speed, cars),
  "S4 dummy object" = new("dummy", x = 1:10, y = rnorm(10)),
  "external pointer" = read_xml("<foo><bar /></foo>")$node
  )

# Extract types and test them.
res <- do.call("rbind.data.frame", Map(function(x, name){
  types <- c(storage.mode= storage.mode(x),
                mode= mode(x),
                type= typeof(x),
                class= class(x))
  data.frame("object"= name,
             "extract_method"= names(types),
             "extract_result"= types,
             "inherits"= sapply(types, function(i) inherits(x, i)),
             "is"= sapply(types, function(i) is(x, i)),
             "is.type"= sapply(types, function(i) eval(parse(text= paste0("tryCatch({is.", i, "(x)}, error= function(e){'is.", i, "() does not exist'})"))))
  )}, obj, names(obj)))
rownames(res) <- 1:nrow(res)
res <- res[order(res$extract_method), ]

We can get a few insights from the results res. For example, we can loow were is.() does not return same as inherits():

> res[res$inherits != res$is, ]
           object extract_method extract_result inherits   is is.type
6  integer vector           mode        numeric    FALSE TRUE TRUE
87           call   storage.mode       language    FALSE TRUE TRUE
89           call           type       language    FALSE TRUE TRUE

Of course the results show much more, e.g. we can see where inherits() returns FALSE on types that where returned of one of the three extraction methods and so on. I left this out here. In fact, I think my answer is much broader since it takes into account both, differences in extraction and test of object type. After reading quite some time about object types I arrived at the code above and wanted to share it. However, using res[res$inherits != res$is, ] soly answers the question.

LulY
  • 976
  • 1
  • 9
  • 24