kai
kai

Reputation: 2185

Select along one of n dimensions in array

I have an array in R, created by a function like this:

A <- array(data=NA, dim=c(2,4,4), dimnames=list(c("x","y"),NULL,NULL))

And I would like to select along one dimension, so for the example above I would have:

A["x",,]
dim(A["x",,])    #[1] 4 4

Is there a way to generalize if I do not know in advance how many dimensions (in addition to the named one I want to select by) my array might have? I would like to write a function that takes input that might formatted as A above, or as:

B <- c(1,2)
names(B) <- c("x", "y")

C <- matrix(1, 2, 2, dimnames=list(c("x","y"),NULL))

Background

The general background is that I am working on an ODE model, so for deSolve's ODE function it must take a single named vector with my current state. For some other functions, like calculating phase-planes/direction fields, it would be more practical to have a higher-dimensional array to apply the differential equation to, and I would like to avoid having many copies of the same function, simply with different numbers of commas after the dimension I want to select.

Upvotes: 17

Views: 4257

Answers (5)

Colombo
Colombo

Reputation: 609

Similar logic to https://stackoverflow.com/a/14502298/4868692, instead of constructing call itself, something that is a bit opaque, we utilize the well-known function do.call.

  1. [ is a function with variable number of arguments depending on x.
  2. do.call is typically used to call functions with constructed argumnets.
dimget = function(x, dim, idx, drop = TRUE){
    d = rep(list(TRUE), length(dim(x)))
    d[[dim]] = idx
    do.call(`[`, c(list(x), d, drop = drop))
    }

Advantages:

  1. Probably safer, no non-standard evaluation going on in here.
  2. Easier to understand since we are just making a list of arguments instead of constructing call, no quote, bquote or as.call makes the code easier to read and understand even for simpletons like me. Otherwise, the code is de-facto identical.

Disadvantages:

  1. Performance? We are constructing a list, but not modifying any elements of it. I would say no memory allocation should happen, but it is hard to say without benchmarking.

Upvotes: 0

oropendola
oropendola

Reputation: 1101

The abind package has a function, asub, to do this in addition to other very useful array manipulation functions:

library(abind)
A <- array(data=1:32, dim=c(2,4,4),
           dimnames=list(c("x","y"), LETTERS[1:4], letters[1:4]))

asub(A, 'x', 1)
asub(A, 'D', 2)
asub(A, 'b', 3)

And it allows indexing in multiple dimensions:

asub(A, list('x', c('C', 'D')), c(1,2))

Upvotes: 4

hadley
hadley

Reputation: 103938

I spent quite a lot of time figuring out the fastest way to do this for plyr, and the best I could come up with was manually constructing the call to [:

index_array <- function(x, dim, value, drop = FALSE) { 
  # Create list representing arguments supplied to [
  # bquote() creates an object corresponding to a missing argument
  indices <- rep(list(bquote()), length(dim(x)))
  indices[[dim]] <- value

  # Generate the call to [
  call <- as.call(c(
    list(as.name("["), quote(x)),
    indices,
    list(drop = drop)))
  # Print it, just to make it easier to see what's going on
  print(call)

  # Finally, evaluate it
  eval(call)
}

(You can find more information about this technique at https://github.com/hadley/devtools/wiki/Computing-on-the-language)

You can then use it as follows:

A <- array(data=NA, dim=c(2,4,4), dimnames=list(c("x","y"),NULL,NULL))
index_array(A, 2, 2)
index_array(A, 2, 2, drop = TRUE)
index_array(A, 3, 2, drop = TRUE)

It would also generalise in a straightforward way if you want to extract based on more than one dimension, but you'd need to rethink the arguments to the function.

Upvotes: 11

James
James

Reputation: 66844

Perhaps there is an easier way, but this works:

do.call("[",c(list(A,"x"),lapply(dim(A)[-1],seq)))
     [,1] [,2] [,3] [,4]
[1,]   NA   NA   NA   NA
[2,]   NA   NA   NA   NA
[3,]   NA   NA   NA   NA
[4,]   NA   NA   NA   NA

Let's generalize it into a function that can extract from any dimension, not necessarily the first one:

extract <- function(A, .dim, .value) {
    idx.list <- lapply(dim(A), seq_len)
    idx.list[[.dim]] <- .value
    do.call(`[`, c(list(A), idx.list))
}

Example:

A <- array(data=1:32, dim=c(2,4,4),
           dimnames=list(c("x","y"), LETTERS[1:4], letters[1:4]))

extract(A, 1, "x")
extract(A, 2, "D")
extract(A, 3, "b")

Upvotes: 2

flodel
flodel

Reputation: 89097

I wrote this general function. Not necessarily super fast but a nice application for arrayInd and matrix indexing:

extract <- function(A, .dim, .value) {

    val.idx  <- match(.value, dimnames(A)[[.dim]])
    all.idx  <- arrayInd(seq_along(A), dim(A))
    keep.idx <- all.idx[all.idx[, .dim] == val.idx, , drop = FALSE]
    array(A[keep.idx], dim = dim(A)[-.dim], dimnames = dimnames(A)[-.dim])

}

Example:

A <- array(data=1:32, dim=c(2,4,4),
           dimnames=list(c("x","y"), LETTERS[1:4], letters[1:4]))

extract(A, 1, "x")
extract(A, 2, "D")
extract(A, 3, "b")

Upvotes: 5

Related Questions