Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill multidimensional array by row

Before presenting the question, I will point out that something similar was asked here but that this thread doesn't really answer my question.

Consider the following dimensional arrays:

 1D: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
 2D: [[1,2,3,4,5,6,7,8], [9,10,11,12,13,14,15,16]]
 3D: [[[1,2,3,4],[5,6,7,8]], [[9,10,11,12],[13,14,15,16]]]
 4D: [[[[1,2],[3,4]], [[5,6],[7,8]], [[[9,10],[11,12]], [[13,14],[15,16]]]]
 ...
  • The 1D array is length 16
  • The 2D array is 2x8
  • The 3D array is 2x2x4
  • The 4D array is 2x2x2x2

Suppose I want to create the arrays. For the first two, I could do something like this in R

oneD <- array(1:16, dim=16) # class(oneD) = array
twoD <- array(1:16, dim=8) # class(twoD) = matrix

However, the twoD array is now represented as

[[1,3,5,7,9,11,13,15], [2,4,6,8,10,12,14,16]]

I am aware of two ways around this.

twoD <- aperm(array(1:16, dim=8))
twoD <- matrix(1:16, nrow=2, byrow=TRUE)

However, these methods won't work for filling the 3D and 4D arrays. I fill them below, but I would like them to match my definitions above.

threeD <- array(1:16, dim=c(2,2,4)) # class(threeD) = array
fourD <- array(1:16, dim=c(2,2,2,2)) # class(fourD) = array

EDIT

bgoldst's answer made me realize that in fact aperm does work for what I want.

threeD <- aperm(array(1:16, dim=c(2,2,4))
# threeD[1,1,1] = 1
# threeD[1,1,2] = 2
# threeD[1,2,1] = 3
# threeD[1,2,2] = 4
# threeD[2,1,1] = 5
# ....
like image 357
nathanesau Avatar asked Jul 31 '15 21:07

nathanesau


1 Answers

The way you've written your data, you need to fill your arrays across the deepest dimension first, and then across shallower dimensions. This is the opposite of the way R normally fills matrices/arrays.

It also needs to be said that this is slightly different from simply filling by row. To use your 3D array as an illustration of this, you've indicated it requires 4 z-slices, and the innermost "subarrays" have length 4. This means you need to fill across z-slices first, then across columns, then across rows. This is not merely filling by row, but by deepest dimension to shallowest dimension (or greatest to least, if you prefer). Admittedly, this concept is often referred to as "by row" or "row-major order", but I don't care for those terms, since they're too 2D, and they're also misleading IMO, since rows are considered to be the shallowest dimension.

To elaborate: It's better to think of fill order as being across dimensions rather than along dimensions. Think of an r×c×z cube. If you're facing the front of the cube (that is, facing the r×c matrix formed from z = 1), if you move along row r = 1, that is, from left to right along the top row, then you're also moving along (or within) z-slice z = 1. The idea of moving along a dimension is not helpful. But if you think of such left-to-right movement as being across columns, then that is completely unambiguous. Thus, across rows means up-down, across columns means left-right, and across z-slices means front-back. Another way of thinking about this is each respective movement is along the dimension "axis", although I don't usually like to think of it that way, because then you have to introduce the idea of axes. Anyway, this is why I don't care for the terms "by row" and "row-major order" (and similarly "column-major order"), since the proper way to think about that movement (IMO) is across columns for 2D, or across the deepest dimension (followed by shallower dimensions) for higher dimensionalities.

You can achieve the requirement by first building the arrays with reversed dimensionality, and then transposing them to "dereverse" (?) the dimensionality. This will lay out the data as you need. Of course, for 1D, no transposition is necessary, and for 2D we can just use t(), but for higher dimensionalities we'll need aperm(). And conveniently, when you call aperm() without specifying the perm argument, by default it reverses the dimensionality of the input; this is just like calling t().

array(1:16,16);
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
t(array(1:16,c(8,2))); ## alternatives: matrix(1:16,2,byrow=T), aperm(array(1:16,c(8,2)))
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,]    1    2    3    4    5    6    7    8
## [2,]    9   10   11   12   13   14   15   16
aperm(array(1:16,c(4,2,2))); ## same as aperm(array(1:16,c(4,2,2)),3:1)
## , , 1
##
##      [,1] [,2]
## [1,]    1    5
## [2,]    9   13
##
## , , 2
##
##      [,1] [,2]
## [1,]    2    6
## [2,]   10   14
##
## , , 3
##
##      [,1] [,2]
## [1,]    3    7
## [2,]   11   15
##
## , , 4
##
##      [,1] [,2]
## [1,]    4    8
## [2,]   12   16
##
aperm(array(1:16,c(2,2,2,2))); ## same as aperm(array(1:16,c(4,2,2)),4:1)
## , , 1, 1
##
##      [,1] [,2]
## [1,]    1    5
## [2,]    9   13
##
## , , 2, 1
##
##      [,1] [,2]
## [1,]    3    7
## [2,]   11   15
##
## , , 1, 2
##
##      [,1] [,2]
## [1,]    2    6
## [2,]   10   14
##
## , , 2, 2
##
##      [,1] [,2]
## [1,]    4    8
## [2,]   12   16
##
like image 128
bgoldst Avatar answered Nov 09 '22 01:11

bgoldst