treysp
treysp

Reputation: 713

R: all.equal() for multiple objects?

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

Answers (2)

treysp
treysp

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

BrodieG
BrodieG

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

Related Questions