Search code examples
rfunctionscopevectorizationapply

Problem with 'match.arg()' inside 'outer()' function


Background/Problem

I am trying to use the outer() function to apply a function on all the pairwise combinations of two vectors as arguments. In my case one of the inputs is a character vector that should serve as arguments to an inner function (cor()) that will need to run match.arg() on it.

Example 1 (it works)

This works fine when the inner function doesn't require this such as with paste().

# define function to test
f1 <- function(method, vec_len){
  paste(method, vec_len)
}

# define inputs to test
methods <- c("pearson", "spearman", "kendall")
vec_lens <- 33:35

# outer runs fine
outer(methods, vec_lens, f1)
#>      [,1]          [,2]          [,3]         
#> [1,] "pearson 33"  "pearson 34"  "pearson 35" 
#> [2,] "spearman 33" "spearman 34" "spearman 35"
#> [3,] "kendall 33"  "kendall 34"  "kendall 35"

Example 2 (it doesn't work)

However when the inner function needs to run match.arg() on the input this fails:

# function that parses string with match.arg fails
f2 <- function(method, vec_len){
  x <- runif(vec_len, 0, 10)
  y <- x + rnorm(vec_len)
  
  cor(x, y, method = method)
}

# outer fails on match.arg within cor()
outer(methods, vec_lens, f2)
#> Error in match.arg(method): 'arg' must be of length 1

My research

From the research I've done (1, 2, 3) it looks like this might be related to the scoping of the inner function being unable to access the global environment but I'm not sure and don't know how to fix it.


BTW

I know the code is fine because I can get the result with a loop:

# desired result by loop
out <- list()
for (method in methods) {
  for (j in seq_along(vec_lens)) {
    out[[method]][[j]] <- f2(method, vec_lens[j])
    names(out[[method]])[j] <- vec_lens[j]
  }
}
do.call(rbind, out)
#>          33        34        35       
#> pearson  0.9351946 0.9491004 0.9516057
#> spearman 0.9067513 0.9315508 0.9456583
#> kendall  0.8371212 0.8146168 0.7983193

Solution

  • We can wrap with Vectorize

    set.seed(24)
    outer(methods, vec_lens, FUN = Vectorize(f2))
           [,1]      [,2]      [,3]
    [1,] 0.9060101 0.9661931 0.9572807
    [2,] 0.9542112 0.9495798 0.9490196
    [3,] 0.8863636 0.8680927 0.7983193
    

    which gives the same output as for loop (except for the dimnames)

    > set.seed(24)
    > out <- list()
    > for (method in methods) {
    +   for (j in seq_along(vec_lens)) {
    +     out[[method]][[j]] <- f2(method, vec_lens[j])
    +     names(out[[method]])[j] <- vec_lens[j]
    +   }
    + }
    > do.call(rbind, out)
             33        34        35       
    pearson  0.9060101 0.9309473 0.9637686
    spearman 0.9532086 0.9254393 0.9526611
    kendall  0.8257576 0.8074866 0.7983193