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:
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.
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).
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
Installing R Packages Error in Readrds(File):Error Reading from Connection
Print the Sourced R File to an Appendix Using Sweave
Convert a File Encoding Using R? (Ansi to Utf-8)
Assign Column Names to List of Dataframes
Converting Utc Time to Local Standard Time in R
Split Data.Frame into Groups by Column Name
Understanding Lm and Environment
R Looping Through in Survey Package
How to Turn the Filename into a Variable When Reading Multiple CSVS into R
Adding Missing Dates to Dataframe
Manually Colouring Plots with 'Scale_Fill_Manual' in Ggplot2 Not Working
Stargazer Left Align Latex Table Columns
R: Interactive Plots (Tooltips): Rcharts Dimple Plot: Formatting Axis
How to Use an R Script from Github
Error in Bind_Rows_(X, .Id):Column Can't Be Converted from Factor to Numeric
Generate 3 Random Number That Sum to 1 in R
Select Rows in a Dataframe in R Based on Values in One Row
Longtable in a Knitr (Pdf) Document: Using Xtable (Or Kable)