Subsetting R Array: Dimension Lost When Its Length Is 1

Subsetting R array: dimension lost when its length is 1

As you've found out by default R drops unnecessary dimensions. Adding drop=FALSE while indexing can prevent this:

> dim(ay[,1:2,])
[1] 2 4
> dim(ax[,1:2,])
[1] 2 2 4
> dim(ay[,1:2,,drop = F])
[1] 1 2 4

subset an R array that may have a dimension of length 1

As you note, from ?"[" there are only two options to control the dimension, drop=TRUE (the default, which in this case will drop both the first and third dimensions) and drop=FALSE, which won't drop any dimension. Neither of these options returns the desired dimension of c(1, 4):

dim(Arrrrgh[,,1])
# NULL
dim(Arrrrgh[,,1,drop=FALSE])
# [1] 1 4 1

One way to address this would be to set the dimension yourself after the subsetting operation:

`dim<-`(Arrrrgh[,,1], dim(Arrrrgh)[1:2])
# [,1] [,2] [,3] [,4]
# [1,] 0.1548771 0.6833689 -0.7507798 1.271966

You could generalize this to a function that drops specified indices if they have a single value passed and doesn't drop any other indices:

extract.arr <- function(arr, ...) {
m <- match.call(expand.dots=FALSE)
missing <- sapply(m[["..."]], is.symbol)
dot.len <- sapply(m[["..."]], function(x) if (is.symbol(x)) 0 else length(eval(x)))
cdim <- dim(arr)
eff.dim <- ifelse(missing, cdim, dot.len)
`dim<-`(do.call("[", c(list(arr), m[["..."]])), eff.dim[eff.dim > 1 | missing])
}
extract.arr(Arrrrgh, ,,1)
# [,1] [,2] [,3] [,4]
# [1,] -0.8634659 1.031382 0.4290036 0.8359372

extract.arr(Arrrrgh, ,,1:2)
# , , 1
#
# [,1] [,2] [,3] [,4]
# [1,] -0.8634659 1.031382 0.4290036 0.8359372
#
# , , 2
#
# [,1] [,2] [,3] [,4]
# [1,] 0.6970842 0.1185803 0.3768951 -0.4577554

extract.arr(Arrrrgh, 1,1,)
# [1] -0.8634659 0.6970842 0.1580495 -1.6606119 -0.2749313 0.4810924 -1.1139392

R Array subsetting: flexible use of drop

Two ways to do this, either use adrop from package abind, or build a new array with the dimensions you choose, after doing the subsetting.

library(abind)
arr <- array(sample(100, 24), dim=c(1, 2, 3, 4))
arr2 <- adrop(arr[ , , 1, , drop=FALSE], drop=3)
dim(arr2)
arr3 <- array(arr[ , , 1 , ], dim=c(1,2,4))
identical(arr2, arr3)

If you want a function that takes a single specified margin, and a single index of that margin, and drops that margin to create a new array with exactly one fewer margin, here is how to do it with abind:

specialsubset <- function(ARR, whichMargin, whichIndex) {
library(abind)
stopifnot(length(whichIndex) == 1, length(whichMargin) == 1, is.numeric(whichMargin))
return(adrop(x = asub(ARR, idx = whichIndex, dims = whichMargin, drop = FALSE), drop = whichMargin))
}
arr4 <- specialsubset(arr, whichMargin=3, whichIndex=1)
identical(arr4, arr2)

Subsetting array with NULL does not drop dimensions?

In fact if you use drop=FALSE in your example you will see that in the first case the first dimension has 1 level, while in the second it has 0 levels. So the behaviour of drop is not totally inconsistent. Sorry, I see that you realized this. But the consequence of this is the fact that r2 is an array with NO entries. As the number of entries must be equal to the product of the of the dimensions, dropping the first dimension as you would like would produce an error. In other terms: you can drop when you have one level because 1*5*4=5*4, while you cannot drop 0 levels because 0*5*4=0, which is different from 5*4.

To specifically answer your questions:

  1. Yes, there are reasons behind this default behaviour. You cannot drop a dimension with 0 levels because if the remaining dimensions have more than zero levels, after dropping, the number of entries (0) will no more match the product of dimension.

  2. The point of keeping 0 levels dimensions is that the result of subsetting an array with NULL is an array with NO entries. This is different from a slice of an array (1 level) which still has entries, and cannot be viewed as an array with one dimension less. So dropping doesn't make sense in for 0 levels (probably the only other possible behaviour would be drop all the dimensions if one has 0 levels, but you would lose the info e.g. on dimnames).

  3. No, you should not report this issue to R dev platform.

Is there a way to select all elements of a dimension when matrix-indexing a multidimensional array in R?

Simpler reproducible example:

array3d <- array(1:27,dim=c(3,3,3))
x <- array3d[,1,1]

In the comments @user2957945 points out that setting the 'blank' elements of the index vector to TRUE will allow do.call('[',...) to select all of the elements from that dimension.

i <- list(TRUE, 1, 1);  do.call('[', c(list(array3d), i))

Previous (suboptimal) answer:

I don't know if there's a simpler/better way, but this works without using str2lang/eval/etc.:

i <- c(NA,1,1)  ## NA denotes "get all elements from this dimension"
getfun <- function(a,i) {
i <- as.list(i)
for (j in seq_along(i)) {
if (all(is.na(i[[j]]))) i[[j]] <- seq(dim(a)[j])
}
v <- as.matrix(do.call(expand.grid,i))
a[v]
}
getfun(array3d,i)

avoid R to simplify array into a matrix when subsetting

You can use drop=FALSE

a[,,3:5, drop=FALSE]

Generally disable dimension dropping for matrices?

You can do it by redefining the [ function:

x <- matrix(1:4,2)

`[` <- function(...) base::`[`(...,drop=FALSE)
x[,1]
[,1]
[1,] 1
[2,] 2

You cannot override the drop argument when you call it now though, so you might want to use it sparingly and delete when done.

Subset matrix with arrays in r

Not sure if there is any easy built in extract syntax, but you can work around this with mapply:

mapply(function(i, j) m[i,j,], idxrows, idxcols)

# [,1] [,2] [,3]
#[1,] 1 2 6
#[2,] 10 11 15

Or slightly more convoluted, create a index matrix whose columns match the dimensions of the original array:

thirdDim <- dim(m)[3]
index <- cbind(rep(idxrows, each = thirdDim), rep(idxcols, each = thirdDim), 1:thirdDim)
matrix(m[index], nrow = thirdDim)

# [,1] [,2] [,3]
#[1,] 1 2 6
#[2,] 10 11 15


Related Topics



Leave a reply



Submit