Reputation: 913
In Mathematica, I can make arrays using:
Table[f(x1, x2, ..., xn), {x1, v1}, {x2, v2}, ..., {xn, vn}]
Where f
is any function, the xi
are variables, and the corresponding vi
are the vectors of values the variables take on.
If the vi
have lengths Li
, then the array is L1xL2x...xLn
.
Is this functionality available in R with some built in function, or would I have to write my own program to do this?
Here's a function that I wrote which accomplishes what I want, but is there a built-in function that does this?
# Function definition
xapply <- function(fun,vecs){
n <- length(vecs)
dims <- sapply(vecs,length)
prods <- c(1,cumprod(dims))
ivecs <- vector(mode="list",length=n)
for(i in seq(n)){
ivecs[[i]] <- rep(rep(vecs[[i]],each=prods[i]),prods[n+1]/prods[i+1])
}
my.args <- c(fun,ivecs)
return(array(do.call(mapply,my.args),dims))
}
# Example
v1=c(1,2,3,4)
v2=c(10,20,30,40,50)
v3=c(100,200,300)
v123=list(v1,v2,v3)
my.array1 <- xapply(function(x,y,z) x+y+z,v123)
my.array1
Upvotes: 1
Views: 2891
Reputation: 263301
I would have used rowSums (which takes hte place of your function) and used it on the result of expand.grid which tos the all-combinations expansions:
> array( rowSums( expand.grid(v123) ) , c(4,5,3) )
, , 1
[,1] [,2] [,3] [,4] [,5]
[1,] 111 121 131 141 151
[2,] 112 122 132 142 152
[3,] 113 123 133 143 153
[4,] 114 124 134 144 154
, , 2
[,1] [,2] [,3] [,4] [,5]
[1,] 211 221 231 241 251
[2,] 212 222 232 242 252
[3,] 213 223 233 243 253
[4,] 214 224 234 244 254
, , 3
[,1] [,2] [,3] [,4] [,5]
[1,] 311 321 331 341 351
[2,] 312 322 332 342 352
[3,] 313 323 333 343 353
[4,] 314 324 334 344 354
You could use a less vectorised solution if the function didn't operate on a row by row basis. The mapply
or Reduce
functions would let you get the same vector:
> array( Reduce("+", expand.grid(v123) ) , c(4,5,3) )
, , 1
[,1] [,2] [,3] [,4] [,5]
[1,] 111 121 131 141 151
[2,] 112 122 132 142 152
[3,] 113 123 133 143 153
[4,] 114 124 134 144 154
, , 2
[,1] [,2] [,3] [,4] [,5]
[1,] 211 221 231 241 251
[2,] 212 222 232 242 252
[3,] 213 223 233 243 253
[4,] 214 224 234 244 254
, , 3
[,1] [,2] [,3] [,4] [,5]
[1,] 311 321 331 341 351
[2,] 312 322 332 342 352
[3,] 313 323 333 343 353
[4,] 314 324 334 344 354
Upvotes: 1
Reputation: 913
Here's another way that I figured out how to make my arrays using expand.grid(), but it's formatted slightly differently from my original question in that: (1) I'm entering the vectors individually, instead of in a list, (2) I'm defining the function using the variables x[1], x[2], etc., and (3) I'm assuming that I know the lengths of the vectors. I still wish that outer() would just take an arbitrary number of arrays as arguments, because that function does exactly what I want, but only for two arrays.
array(apply(expand.grid(1:4,4:8,3:5), 1, function(x) 100*x[1]+10*x[2]+x[3]),c(4,5,3))
Upvotes: 0
Reputation: 35314
Here's how I would do this, demonstrating with your lambda and vector list v123
as input:
array(do.call(function(x,y,z) x+y+z,unname(do.call(expand.grid,v123))),lapply(v123,length));
## , , 1
##
## [,1] [,2] [,3] [,4] [,5]
## [1,] 111 121 131 141 151
## [2,] 112 122 132 142 152
## [3,] 113 123 133 143 153
## [4,] 114 124 134 144 154
##
## , , 2
##
## [,1] [,2] [,3] [,4] [,5]
## [1,] 211 221 231 241 251
## [2,] 212 222 232 242 252
## [3,] 213 223 233 243 253
## [4,] 214 224 234 244 254
##
## , , 3
##
## [,1] [,2] [,3] [,4] [,5]
## [1,] 311 321 331 341 351
## [2,] 312 322 332 342 352
## [3,] 313 323 333 343 353
## [4,] 314 324 334 344 354
##
identical(.Last.value,my.array1);
## [1] TRUE
The advantage of this line over an apply()
- or Map()
-based solution is that it is vectorized. Only one call to the function is made.
First, expand.grid()
is called via do.call()
with the vector list as the argument list. This produces a data.frame containing all possible combinations of the input vectors, one per row. We must then remove the automatically-generated column names from the data.frame by calling unname()
.
We can then pass the resulting unnamed data.frame as the argument list to the lambda, again invoked via do.call()
. This works because data.frames are lists internally, with one list component per column, hence a data.frame is suitable for use as the list of arguments expected by do.call()
. This also clarifies why the unname()
call was needed; otherwise the lambda would be invoked with named arguments (the automatically-generated names being Var1
, Var2
, Var3
) which would fail to bind to the parameters (x
, y
, z
).
The result of the vectorized lambda call is an atomic vector. Since you want an array, we must wrap it in a call to array()
, and pass the appropriate dimensionality from the lengths of the input vectors via lapply()
.
Also you may want to look into the outer()
function, which internally performs this kind of cartesian expansion, vectorized call, and dimensionality wrapping, but only for two input objects at a time.
Upvotes: 2
Reputation: 38500
If your goal is to apply a function to every combination of v1 ... vn, you could use expand.grid
and apply
:
x1 <- 1:5
x2 <- 1:10
x3 <- 8:10
# get the average
apply(expand.grid(x1,x2,x7), 1, FUN=mean)
# get the sum of squares
apply(expand.grid(x1,x2,x7), 1, FUN=function(i) sum(i^2))
If you instead have v1, ..., vn of equal length and you want to apply some function to corresponding entries, you can use Map
(or mapply
)
x1 <- 1:5
x2 <- 2:6
x3 <- 6:10
# get the sum of squares
Map(f=function(x,y,z) sum(x^2 + y^2 + z^2), x1, x2, x3)
Upvotes: 1