How to store multidimensional subscript as variable in R

Viewed 175

Suppose I have a matrix,

mat <- matrix((1:9)^2, 3, 3)

I can slice the matrix like so

> mat[2:3, 2]
[1] 25 36

How does one store the subscript as a variable? That is, what should my_sub be, such that

> mat[my_sub]
[1] 25 36

A list gets "invalid subscript type" error. A vector will lose the multidimensionality. Seems like such a basic operation to not have a primitive type that fits this usage.

I know I can access the matrix via vector addressing, which means converting from [2:3, 2] to c(5, 6), but that mapping presumes knowledge of matrix shape. What if I simply want [2:3, 2] for any matrix shape (assuming it is at least those dimensions)?

3 Answers

Here are some alternatives. They both generalize to higher dimenional arrays.

1) matrix subscripting If the indexes are all scalar except possibly one, as in the question, then:

mi <- cbind(2:3, 2)
mat[mi]

# test
identical(mat[mi],   mat[2:3, 2])
## [1] TRUE

In higher dimensions:

a <- array(1:24, 2:4)
mi <- cbind(2, 2:3, 3)
a[mi]

# test
identical(a[mi],   a[2, 2:3, 3])
## [1] TRUE

It would be possible to extend this to eliminate the scalar restriction using:

L <- list(2:3, 2:3)
array(mat[as.matrix(do.call(expand.grid, L))], lengths(L))

however, in light of (2) which also uses do.call but avoids the need for expand.grid it seems unnecessarily complex.

2) do.call This approach does not have the scalar limitation. mat and a are from above:

L2 <- list(2:3, 1:2)
do.call("[", c(list(mat), L2))

# test
identical(do.call("[", c(list(mat), L2)),   mat[2:3, 1:2])
## [1] TRUE


L3 <- list(2, 2:3, 3:4)
do.call("[", c(list(a), L3))

# test
identical(do.call("[", c(list(a), L3)),   a[2, 2:3, 3:4])
## [1] TRUE

This could be made prettier by defining:

`%[%` <- function(x, indexList) do.call("[", c(list(x), indexList))
mat %[% list(2:3, 1:2)
a %[% list(2, 2:3, 3:4)

Use which argument arr.ind = TRUE.

x <- c(25, 36)
inx <- which(mat == x, arr.ind = TRUE)
Warning message:
In mat == x :
  longer object length is not a multiple of shorter object length

mat[inx]
#[1] 25 36

This is an interesting question. The subset function can actually help. You cannot subset directly your matrix using a vector or a list, but you can store the indexes in a list and use subset to do the trick.

mat <- matrix(1:12, nrow=4)
mat[2:3, 1:2]
# example using subset
subset(mat, subset = 1:nrow(mat) %in% 2:3, select = 1:2)

# double check
identical(mat[2:3, 1:2], 
          subset(mat, subset = 1:nrow(mat) %in% 2:3, select = 1:2))

# TRUE

Actually, we can write a custom function if we want to store the row- and column- indexes in the same list.

cust.subset <- function(mat, dim.list){
  subset(mat, subset = 1:nrow(mat) %in% dim.list[[1]], select = dim.list[[2]])
}

# initialize a list that includes your sub-setting indexes
sbdim <- list(2:3, 1:2)
sbdim
# [[1]]
# [1] 2 3

# [[2]]
# [1] 1 2

# subset using your custom f(x) and your list
cust.subset(mat, sbdim)
#      [,1] [,2]
# [1,]    2    6
# [2,]    3    7
Related