Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between is and inherits?

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?

like image 318
Richie Cotton Avatar asked Jan 13 '15 13:01

Richie Cotton


2 Answers

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
like image 102
3 revs Avatar answered Nov 15 '22 05:11

3 revs


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.

like image 36
Igor stands with Ukraine Avatar answered Nov 15 '22 05:11

Igor stands with Ukraine