I believe it is a very commom misconception to assume that outer(x, y, FUN)
calls the function parameter (FUN
) once for each required pair x[i]
and y[j]
. Actually, outer
calls FUN
only once, after creating all possible pairs, combining every element of x
with every element of y
, in a manner similar to the function expand.grid
.
I'll show that with an example: consider this function, which is a wrapper for the product and print a message every time it's called:
f <- function(x,y)
{
cat("f called with arguments: x =", capture.output(dput(x)), "y =", capture.output(dput(y)), "
")
x*y
}
This function is "naturally" vectorized, so we can call it with vector arguments:
> f(c(1,2), c(3,4))
f called with arguments: x = c(1, 2) y = c(3, 4)
[1] 3 8
Using outer
:
> outer(c(1,2), c(3,4), f)
f called with arguments: x = c(1, 2, 1, 2) y = c(3, 3, 4, 4)
[,1] [,2]
[1,] 3 4
[2,] 6 8
Notice the combinations generated.
If we can't guarantee that the function can handle vector arguments, there is a simple trick to ensure the function gets called only once for each pair in the combinations: Vectorize
. This creates another function that calls the original function once for each element in the arguments:
> Vectorize(f)(c(1,2),c(3,4))
f called with arguments: x = 1 y = 3
f called with arguments: x = 2 y = 4
[1] 3 8
So we can make a "safe" outer
with it:
> outer(c(1,2), c(3,4), Vectorize(f))
f called with arguments: x = 1 y = 3
f called with arguments: x = 2 y = 3
f called with arguments: x = 1 y = 4
f called with arguments: x = 2 y = 4
[,1] [,2]
[1,] 3 4
[2,] 6 8
In this case, the results are the same because f
was written in a vectorized way, i.e., because "*"
is vectorized. But if your function is not written with this in mind, using it directly in outer
may fail or (worse) may give wrong results.