0
votes

Background

I have a fun function that compares vectors within a list using all.equal. As I'm making use of the all.equal I would like to pass relevant all.equal arguments via ellipsis. Without the need to pass anything to all.equal the function works as desired.

Function goals

  • The function aims to provide modified version of all.equal call working on any number of vectors
  • Each of the vectors can have any number of elements; however, if all vectors are not of equal lengths the function will return false.
  • The function should be able to take advantage of arguments available in all.equal. For instance vectors c(1.1, 2), c(1, 2) and c(1.3, 2) will be considered equal if tolerance argument with the right value is provided. The question is concerned with getting this to work.

Example

Comparing 1,000 vectors, each consisting of three integers.

    compare_multiple_vectors(x = lapply(
              X = vector(mode = "list", length = 1e3),
              FUN = function(...) {
                  c(1, 2, 3)
              }
          ))

    # [1] TRUE

Problem / desired results

all.equal called with tolerance = 1 on the following list of vectors will return expected TRUE

all.equal(c(1,2), c(1,1), tolerance = 1)
# [1] TRUE

The tolerance = 1 argument fails to filter down to inside Reduce.

compare_multiple_vectors(x = list(c(1,2), c(1,1)), tolerance = 1)
# [1] FALSE

The desired result should be TRUE.


Code

#' @title Compare Values of Multiple Vectors
#'
#' @description The function compares values across multiple vectors using
#'   \code{\link[base]{all.equal}}.
#'
#' @param x Alist of vectors to compare
#' @param ... as in  \code{\link[base]{all.equal}}
#'
#' @return A logical
#'
#' @export
#'
#' @importFrom checkmate assert_atomic_vector
#'
#' @examples
#' # Returns TRUE
#' compare_multiple_vectors(c(1,1,1), c(1,1,1))
#' # Returns FALSE
#' compare_multiple_vectors(c(1,1,1), c(1,1,1), c(1,2,1))
#' # Returns FALSE
#' compare_multiple_vectors(c(1,2,3), c(3,2,1))
compare_multiple_vectors <- function(x, ...) {
    # Check if all elements of x are atomic vectors
    Vectorize(FUN = checkmate::assert_atomic_vector,
              vectorize.args = "x")(x)

    # Compare list elements
    Reduce(
        f = function(a, b, ...) {
            if (isTRUE(all.equal(target = a, current = b, ...))) {
                a
            } else {
                FALSE
            }
        },
        x = x
    ) -> res_red

    # Return results
    if (isFALSE(res_red)) {
        return(FALSE)
    } else {
        return(TRUE)
    }
}

Notes

  • I'm interested in making use of ellipsis and leaving the initial call the way it is with

    compare_multiple_vectors(x = list_of_vectors_to_compare, 
                             ... # all.equal arguments
                             )
    
1
Reduce doesn't have a ... argument, but Map does. Would something like this Map(function(a, b, ...) all.equal(a, b, ...), list(c(1,2)), list(c(1,1)), tolerance = 1) do it? Note that the list is now two lists. - Rui Barradas
Or simply Map(all.equal, list(c(1,2)), list(c(1,1)), tolerance = 1). Apparently, Map recognizes the ellipses argument of all.equal. - Rui Barradas
@RuiBarradas I reckon this could work; how this affects performance as Reduce will always compare two vectors? Also, I want to pass one argument as a list of vectors to check, for Map, this would have to broken down into separate list, what with list that has odd number of vectors? - Konrad
I think it's unclear what sorts of comparisons you are attempting and what sort of error checking would be needed. Can we assume that all entries in the list will be of length 2? Or is it the case that some are of different lengths? This all cries out for a set of test cases for testing and a correspond set of explanations why the specified results should be as offered. And wouldn't the results need to be in the form of a list, rather than as a vector? (Voting to close as unclear, but would encourage you to clarify so I can reverse my vote.) - IRTFM
@42- thanks for looking at this. I’ve clarified what the function is supposed to do. - Konrad

1 Answers

3
votes

I think just need a little change:

compare_multiple_vectors <- function(x, ...) {
    # Check if all elements of x are atomic vectors
    Vectorize(FUN = checkmate::assert_atomic_vector,
              vectorize.args = "x")(x)

    # Compare list elements
    Reduce(
        f = function(a, b) {  # <===================== Remove *...*

            if (isTRUE(all.equal(target = a, current = b, ...))) {
                a
            } else {
                FALSE
            }
        },
        x = x
    ) -> res_red

    # Return results
    if (isFALSE(res_red)) {
        return(FALSE)
    } else {
        return(TRUE)
    }
}

The arguments f of Reduce seems to have a signature like function(x, y). So Reduce will ignore ... in f. If remove the ellipsis of f, ... will reference from the outer space, and will have the right result you want.