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?
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
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
storage.mode()
, mode()
, typeof()
and class()
.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With