Reputation: 713
What is the best way to compare more than two objects with all.equal()
?
Here's one way:
foo <- c(1:10)
bar <- letters[1:10]
baz <- c(1:10)
# doesn't work because all.equal() returns a character vector when objects not all equal
all(sapply(list(bar, baz), all.equal, foo))
# this works
mode(sapply(list(bar, baz), all.equal, foo)) == "logical" #FALSE
bar <- c(1:10)
mode(sapply(list(bar, baz), all.equal, foo)) == "logical" #TRUE
UPDATE: @BrodieG pointed out that the one-liner above only tells you whether the objects are all equal or not, whereas all.equal()
tells you what isn't equal about them if they aren't equal.
Upvotes: 10
Views: 4487
Reputation: 713
I think this comes as close to the behavior of all.equal()
as possible.
It returns TRUE
if the objects are all equal and a list of pairwise comparisons otherwise. It returns a single-item list if only two objects are compared for consistency of output.
# function: all.equal.mult()
# description: compares >=2 objects with all.equal()
# input: >=2 comma-separated object names
# output: TRUE or list of pairwise all.equal() object comparisons
# examples:
# foo <- c(1:10)
# bar <- c(1:10)
# foz <- c(1:10)
# baz <- letters[1:10]
#
# all.equal.mult(foo, bar) # TRUE
# all.equal.mult(foo, baz) # results of all.equal(foo, baz) as one-item list
# all.equal.mult(foo, bar, foz) # TRUE
# all.equal.mult(foo, bar, baz) # list of pairwise all.equal() comparisons among objects
all.equal.mult <- function(...) {
# more than one object required
if (length(list(...)) < 2) stop("More than one object required")
# character vector of object names
names <- as.character(substitute(list(...)))[-1L]
# matrix of object name pairs
pairs <- t(combn(names, 2))
# if only two objects, return one item list containing all.equal() for them
if (nrow(pairs) == 1) return(list(all.equal(get(pairs[1,1]), get(pairs[1,2]))))
# function: eq.fun()
# description: applies all.equal() to two quoted names of objects
# input: two quoted names of objects
# output: list containing all.equal() comparison and "[obj1] vs. [obj2]"
# examples:
# x <- 1
# y <- 1
# z <- 2
# eq.fun("x", "y") # list(TRUE, "x vs. y")
# eq.fun("x", "z") # list("Mean relative difference: 1", "x vs. z")
eq.fun <- function(x, y) {
all.eq <- all.equal(get(x, inherits=TRUE), get(y, inherits=TRUE))
name <- paste0(x, " vs. ", y)
return(list(all.eq, name))
}
# list of eq.fun object comparisons
out <- vector(mode="list", length=nrow(pairs))
for (x in 1:nrow(pairs)) {
eq.list <- eq.fun(pairs[x, 1], pairs[x, 2])
out[[x]] <- eq.list[[1]]
names(out)[x] <- eq.list[[2]]
}
# return TRUE if all objects equal, comparison list otherwise
if (mode(unlist(out)) == "logical") {return(TRUE)} else {return(out)}
}
Testing 1, 2:
foo <- c(1:10)
bar <- c(1:10)
foz <- c(1:10)
baz <- letters[1:10]
all.equal.mult(foo) # Error
all.equal.mult(foo, bar) # TRUE
all.equal.mult(foo, baz) # results of all.equal(foo, baz) as one-item list
all.equal.mult(foo, bar, foz) # TRUE
all.equal.mult(foo, bar, baz) # list of pairwise all.equal() comparisons among objects
Upvotes: 6
Reputation: 52667
Here is an option:
objs <- mget(c("foo", "bar", "faz"))
outer(objs, objs, Vectorize(all.equal))
It's better than yours because it will detect when bar
and faz
are the same, even when foo
isn't. That said, it does a lot of unnecessary comparisons and will be slow. For example, if we change foo
to be letters[1:10]
we get:
foo bar faz
foo TRUE Character,2 Character,2
bar Character,2 TRUE TRUE
faz Character,2 TRUE TRUE
For details on what went wrong, just subset:
outer(objs, objs, Vectorize(all.equal))[1, 2]
Produces:
[[1]]
[1] "Modes: character, numeric"
[2] "target is character, current is numeric"
If all you care about is that all the objects must be all.equal
, then your solution is pretty good.
Also, as per comments to limit some of the duplicate calculations:
res <- outer(objs, objs, function(x, y) vector("list", length(x)))
combs <- combn(seq(objs), 2)
res[t(combs)] <- Vectorize(all.equal)(objs[combs[1,]], objs[combs[2,]])
res
Produces
foo bar faz
foo NULL Character,2 Character,2
bar NULL NULL TRUE
faz NULL NULL NULL
This still shows the full matrix, but makes it obvious what comparisons produced what.
Upvotes: 15