I often explain outer(x, y, FUN)
when both x
and y
are vectors with the following:
xx <- rep(x, times = length(y))
yy <- rep(y, each = length(x))
zz <- FUN(xx, yy)
stopifnot(length(zz) == length(x) * length(y)) ## length = product?
z <- matrix(zz, length(x), length(y))
funError
fails because zz
has length 1, while funNoError
does not because "recycling rule" has been applied when you paste a
(a vector with length > 1) and class(a)
(a length-1 vector).
This is illustrative as you will see why outer(1:5, 1:5, "+")
works but outer(1:5, 1:5, sum)
fails. Basically, FUN
must be able to process xx
and yy
element-wise. Otherwise, wrap FUN
with a sugar function called Vectorize
. More details are given later.
Note that "list" is also a valid mode of a vector. So outer
could be used to some non-standard things like How to perform pairwise operation like `%in%` and set operations for a list of vectors.
You can pass matrices / arrays to outer
, too. Given that they are just vectors with an "dim" attribute (optionally with "dimnames"), how outer
works does not change.
x <- matrix(1:4, 2, 2) ## has "dim"
y <- matrix(1:9, 3, 3) ## has "dim"
xx <- rep(x, times = length(y)) ## xx <- rep(c(x), times = length(y))
yy <- rep(y, each = length(x)) ## yy <- rep(c(y), each = length(x))
zz <- "*"(xx, yy)
stopifnot(length(zz) == length(x) * length(y)) ## length = product?
z <- "dim<-"( zz, c(dim(x), dim(y)) )
z0 <- outer(x, y, "*")
all.equal(z, z0)
#[1] TRUE
?outer
explains the code above in plain words.
‘X’ and ‘Y’ must be suitable arguments for ‘FUN’. Each will be
extended by ‘rep’ to length the products of the lengths of ‘X’ and
‘Y’ before ‘FUN’ is called.
‘FUN’ is called with these two extended vectors as arguments (plus
any arguments in ‘...’). It must be a vectorized function (or the
name of one) expecting at least two arguments and returning a
value with the same length as the first (and the second).
Where they exist, the [dim]names of ‘X’ and ‘Y’ will be copied to
the answer, and a dimension assigned which is the concatenation of
the dimensions of ‘X’ and ‘Y’ (or lengths if dimensions do not
exist).
The word "vectorized" is NOT the most discussed one in R on performance. It means "vectorizing the action of a function":
## for FUN with a single argument
FUN( c(x1, x2, x3, x4) ) = c( FUN(x1), FUN(x2), FUN(x3), FUN(x4) )
## for FUN with two arguments
FUN( c(x1, x2, x3, x4), c(y1, y2, y3, y4) )
= c( FUN(x1, y1), FUN(x2, y2), FUN(x3, y3), FUN(x4, y4) )
Some functions say "+"
, "*"
, paste
behave like this, but many others don't, say class
, sum
, prod
. The *apply
family functions in R are there to help you to vectorize function action, or you can write your own loop to achieve the same effect.
Another worth reading good-quality Q & A: Why doesn't outer work the way I think it should (in R)?