Reputation: 7313
Suppose I have two arrays (or tensors if tensor package is needed)
dim(Xbeta)
products draws Households
13 20 10
dim(denom)
1 20 10
set.seed(1)
Xbeta=array(rnorm(13*20*10,0,1),dim=c(13,20,10))
denom=array(rnorm(1*20*10,0,1),dim=c(1,20,10))
Without looping, I want to do the following:
for(i in 1:10){
Xbeta[,,i]=t(t(Xbeta[,,i]) / denom[,,i])
}
I want to to divide each column in Xbeta[,,i]
slice by each corresponding number in denom[,,i].
For example...Xbeta[,1,i]/denom[,1,i]
...etc
Upvotes: 2
Views: 417
Reputation: 35324
You can avoid looping and replication by (1) 3-dimensionally transposing the numerator array and (2) flattening the denominator array to a vector, such that the division operation will naturally cycle the incomplete denominator vector across the entirety of the transposed numerator array in such a way that the data lines up the way you want. You then must 3-dimensionally "untranspose" the result to get back the original transposition.
aperm(aperm(Xbeta,c(2,3,1))/c(denom),c(3,1,2));
The first call to aperm()
transposes columns to rows, z-slices to columns, and rows to z-slices. The c()
call on denom
flattens the denominator array to a vector, because when cycling, we don't care about dimensionality. The final call to aperm()
reverses the original transposition.
To go into more detail about the logic of this solution, what you have with your inputs is basically a vector of divisors per z-slice of the numerator array, and you want to apply each divisor to every row of the corresponding z-slice and column. This means the vector of divisors must be applied across columns, first-and-foremost, and then, as each denominator z-slice is exhausted, applied across numerator z-slices. After a complete row (covering all z-slices in the row) of the numerator array has been exhausted, the entirety of the denominator vector has been exhausted, causing it to be cycled back to the beginning for the next row of the numerator array.
See https://stat.ethz.ch/R-manual/R-devel/library/base/html/aperm.html.
For a rough idea on performance:
r> set.seed(1);
r> Xbeta <- array(rnorm(13*20*10,0,1), dim=c(13,20,10) );
r> denom <- array(rnorm(1*20*10,0,1), dim=c(1,20,10) );
r> robert <- function() { result <- array(NA, dim=c(13,20,10) ); for (i in 1:10) { result[,,i] <- t(t(Xbeta[,,i]) / denom[,,i]); }; };
r> andre <- function() { denom_myVersion <- array(rep(c(denom), each=13 ), c(13,20,10) ); result <- Xbeta / denom_myVersion; };
r> bgoldst <- function() { result <- aperm(aperm(Xbeta,c(2,3,1))/c(denom),c(3,1,2)); };
r> N <- 99999;
r> system.time({ replicate(N, robert() ); });
user system elapsed
25.421 0.000 25.440
r> system.time({ replicate(N, andre() ); });
user system elapsed
12.578 0.594 13.283
r> system.time({ replicate(N, bgoldst() ); });
user system elapsed
8.484 0.594 9.142
Also, as a general recommendation, it is helpful (for both questioners and answerers) to present these kinds of problems using minimal sample input, e.g.:
r> n <- array(1:12,dim=c(2,3,2)); n;
, , 1
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
, , 2
[,1] [,2] [,3]
[1,] 7 9 11
[2,] 8 10 12
r> d <- array(1:6,dim=c(1,3,2)); d;
, , 1
[,1] [,2] [,3]
[1,] 1 2 3
, , 2
[,1] [,2] [,3]
[1,] 4 5 6
r> aperm(aperm(n,c(2,3,1))/c(d),c(3,1,2));
, , 1
[,1] [,2] [,3]
[1,] 1 1.5 1.666667
[2,] 2 2.0 2.000000
, , 2
[,1] [,2] [,3]
[1,] 1.75 1.8 1.833333
[2,] 2.00 2.0 2.000000
Upvotes: 1
Reputation: 128
I'm not sure why you choose a 3-dimensional array for the denon
. Anyway, this can be done by paying close attention to how these numbers are stored in memory. In an array the first dimensions "moves the fastest". By replicating the denom values 13 times "each" then you create an array with the exact same dimensions as your numerator.
So, let's test it out: Let's save the ramdom values so we can use them for both methods:
set.seed(1)
Num_2600 <- rnorm(13*20*10,0,1)
Denom_200 <- rnorm(20*10,0,1)
Xbeta=array(Num_2600,dim=c(13,20,10))
denom=array(Denom_200,dim=c(1,20,10))
Your_result <- array(NA, dim=c(13,20,10))
Your code gives:
for(i in 1:10){
Your_result[,,i] <- t(t(Xbeta[,,i]) / denom[,,i])
}
My code:
denom_myVersion <- array(rep( Denom_200 , each=13), c(13,20,10))
> all(Your_result == Xbeta / denom_myVersion)
[1] TRUE
>
So we get the same results. The hard part is how to decide how to replicate so the numbers fall in the right spot. Notice:
denom_myVersion <- array(rep( Denom_200 , times=13), c(13,20,10))
> all(Your_result == Xbeta / denom_myVersion)
[1] FALSE
>
With 'each' as a parameter in rep
each element is repeated 13 times before going to the next element. With times, the whole vector is repeated 13 times. Compare:
> rep(1:3, each =3)
[1] 1 1 1 2 2 2 3 3 3
> rep(1:3, times=3)
[1] 1 2 3 1 2 3 1 2 3
Upvotes: 1
Reputation: 898
# Is this what you're looking for?
Xbeta <- array(rnorm(13*20*10,0,1),dim=c(13,20,10))
denom <- array(rnorm(1*20*10,0,1),dim=c(1,20,10))
div.list <- sapply(1:10, FUN = function(x) t(Xbeta[,,x]) / denom[,,x], simplify = FALSE)
result <- array(do.call(c, div.list), dim = dim(Xbeta)[c(2,1,3)])
Upvotes: 1