Dynamically access array dimension
Asked Answered
A

5

5

is there a way to dynamically access an array dimension (ideally without reshaping)? I have an n-dimensional array arr as well as a named order of dimensions vals. Both the number of dimensions for arr and the order of vals may change. I always want to access the first entry of arr for the dimension where vals equals B. In the following I have provided three examples demonstrating what I want:

#Example 1
vals = c("A","B","C")
idx = grep("B",vals)
arr = array(runif(5^3), dim = c(5,5,5))

arr[,1,] # B is in second dimension

#Example 2
vals = c("A","C","B","D")
idx = grep("B",vals)
arr = array(runif(5^4), dim = c(5,5,5,5))

arr[,,1,] #B is in third dimension

#Example 3
vals = c("B","A","C","D","E")
idx = grep("B",vals)
arr = array(runif(5^5), dim = c(5,5,5,5,5))

arr[1,,,,] #B is in first dimension

I'm absolutely clueless how to approach this.

Arrogance answered 31/10 at 10:15 Comment(1)
possibly relevant: #53014462Canoodle
C
5

I think the simplest way is using asplit (but seems not as efficient as eval approach)

asplit(arr, idx)[[1]]

Other options


You can also try apply like below

array(apply(arr, idx, c)[, 1], dim(arr)[-idx])

and verify it, for example

> set.seed(0)

> vals <- c("B", "A", "C", "D", "E")

> idx <- grep("B", vals)

> arr <- array(runif(5^5), dim = c(5, 5, 5, 5, 5))

> a <- arr[1, , , , ] # B is in first dimension

> b <- array(apply(arr, idx, c)[, 1], dim(arr)[-idx])

> all.equal(a, b)
[1] TRUE

If you are bothered by apply and its indexing logic, you can parse the string like

b <- eval(str2lang(sprintf("arr[%s]", toString(replace(rep("", length(dim(arr))), idx, "1")))))

and you can obtain

> all.equal(a, b)
[1] TRUE
Courtney answered 31/10 at 10:37 Comment(2)
asplit is very concise but the eval solution here seems to run faster.Amentia
@G.Grothendieck yes, I am aware of that too, even though the expression string might be long :)Courtney
L
2

something like this (using vals and arr from your example)?


    the_letter <- 'B'
    
    
    picklist <- Map(vals, f = \(val) ifelse(val == the_letter, 1, TRUE))
    do.call(`[`, c(list(x = arr), picklist))

Lander answered 31/10 at 10:47 Comment(0)
A
2

1) apply Use apply over the indexes that do not correspond to B taking the first of each. This is concise, does not involve explicit reshaping and uses only base R.

apply(arr, which(vals != "B"), head, 1)

Tests

set.seed(123)
vals = c("A","B","C")
arr = array(runif(5^3), dim = c(5,5,5))
identical(apply(arr, which(vals != "B"), head, 1), arr[,1,])
## [1] TRUE

set.seed(123)
vals = c("A","C","B","D")
arr = array(runif(5^4), dim = c(5,5,5,5))
identical(apply(arr, which(vals != "B"), head, 1), arr[,,1,])
## [1] TRUE

set.seed(123)
vals = c("B","A","C","D","E")
arr = array(runif(5^5), dim = c(5,5,5,5,5))
identical(apply(arr, which(vals != "B"), head, 1), arr[1,,,,])
## [1] TRUE

2) extract_dim The listarrays package has a function to do this.

library(listarrays)
extract_dim(arr, which(vals == "B"), 1)

Tests

library(listarrays)

set.seed(123)
vals = c("A","B","C")
arr = array(runif(5^3), dim = c(5,5,5))
identical(extract_dim(arr, which(vals == "B"), 1) , arr[,1,])
## [1] TRUE

set.seed(123)
vals = c("A","C","B","D")
arr = array(runif(5^4), dim = c(5,5,5,5))
identical(extract_dim(arr, which(vals == "B"), 1), arr[,,1,])
## [1] TRUE

set.seed(123)
vals = c("B","A","C","D","E")
arr = array(runif(5^5), dim = c(5,5,5,5,5))
identical(extract_dim(arr, which(vals == "B"), 1), arr[1,,,,])
## [1] TRUE

3) aperm If we knew that arr has at most 5 dimensions we could make each number of dimensions a separate case. Extend to the maximum if there can be more than 5. This only uses base R.

get_dim <- function(arr, vals, val = "B", i = 1) {
  a <- aperm(arr, order(vals != val))
  switch(length(dim(arr)), a[i], a[i,], a[i,,], a[i,,,], a[i,,,,], a[i,,,,,])
}

set.seed(123)
vals = c("A","B","C")
arr = array(runif(5^3), dim = c(5,5,5))
identical(get_dim(arr, vals) , arr[,1,])
## [1] TRUE

set.seed(123)
vals = c("A","C","B","D")
arr = array(runif(5^4), dim = c(5,5,5,5))
identical(get_dim(arr, vals), arr[,,1,])
## [1] TRUE

set.seed(123)
vals = c("B","A","C","D","E")
arr = array(runif(5^5), dim = c(5,5,5,5,5))
identical(get_dim(arr, vals), arr[1,,,,])
## [1] TRUE
Amentia answered 1/11 at 17:4 Comment(0)
A
1

You can create a text and evaluate it: e.g.

cmd <- sprintf("arr[%s]", paste(ifelse(vals == "B", "1", ""), collapse = ","))
cmd
[1] "arr[,1,]"
eval(parse(text = cmd))
Awesome answered 31/10 at 10:34 Comment(1)
Ahh, thanks! I've tried eval() before but I think that I used it wrongly (without the parse() function).Arrogance
R
1

With do.call:

do.call(`[`, c(list(arr), replace(as.list(rep(TRUE, length(dim(arr)))),
                                    grep("B", vals), 1)))

#Example 1
vals = c("A","B","C")
idx = grep("B",vals)
arr = array(runif(5^3), dim = c(5,5,5))
identical(
  do.call(`[`, c(list(arr), replace(as.list(rep(TRUE, length(dim(arr)))),
                                    grep("B", vals), 1))),
  arr[,1,] # B is in second dimension
)
#> [1] TRUE


#Example 2
vals = c("A","C","B","D")
idx = grep("B",vals)
arr = array(runif(5^4), dim = c(5,5,5,5))
identical(
  do.call(`[`, c(list(arr), replace(as.list(rep(TRUE, length(dim(arr)))),
                                    grep("B", vals), 1))),
  arr[,,1,] #B is in third dimension
)
#> [1] TRUE

#Example 3
vals = c("B","A","C","D","E")
idx = grep("B",vals)
arr = array(runif(5^5), dim = c(5,5,5,5,5))
identical(
  do.call(`[`, c(list(arr), replace(as.list(rep(TRUE, length(dim(arr)))),
                                    grep("B", vals), 1))),
  arr[1,,,,] #B is in first dimension
)
#> [1] TRUE
Rhythmics answered 31/10 at 13:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.