ThePlowKing
ThePlowKing

Reputation: 341

Keeping some array elements fixed in R

I'm sure this is a fairly straightforward question but I'm not sure how to do this. I have given an example array with dimensions (4,4,5) as follows:

     [,1] [,2] [,3] [,4]
[1,]    1    0    5    0
[2,]    0   NA    0    6
[3,]    0    0    0    0
[4,]    0    0    0    0

     [,1] [,2] [,3] [,4]
[1,]    1    0    10   0
[2,]    0   NA    0    12
[3,]    0    0    0    0
[4,]    0    0    0    0

     [,1] [,2] [,3] [,4]
[1,]    1    0    15   0
[2,]    0   NA    0    18
[3,]    0    0    0    0
[4,]    0    0    0    0

     [,1] [,2] [,3] [,4]
[1,]    1    0    20   0
[2,]    0   NA    0    24
[3,]    0    0    0    0
[4,]    0    0    0    0

     [,1] [,2] [,3] [,4]
[1,]    1    0    25   0
[2,]    0   NA    0    30
[3,]    0    0    0    0
[4,]    0    0    0    0

Basically, for this example array I'd like the elements at [1,3] and [2,4] to change along the 3rd dimension but I'm not sure how to write this code in R. I've tried using variations of the code array(c(1,0,0,0,0,NA,0,0,5,0,0,0,0,6,0,0), dim=c(4,4,3)) and I tried checking online but I can't seem to find anything which helps with this issue, so any help I can get would be greatly appreciated, thanks in advance.

Upvotes: 1

Views: 64

Answers (3)

Cole
Cole

Reputation: 11255

If you're updating based on values, you apply a condition:

arr[arr > 4 ] <- 100

What's happening is that the inside arr > 4 is generating an array:

, , 1

      [,1]  [,2]  [,3]  [,4]
[1,] FALSE FALSE  TRUE FALSE
[2,] FALSE    NA FALSE  TRUE
[3,] FALSE FALSE FALSE FALSE
[4,] FALSE FALSE FALSE FALSE

, , 2

      [,1]  [,2]  [,3]  [,4]
[1,] FALSE FALSE  TRUE FALSE
[2,] FALSE    NA FALSE  TRUE
[3,] FALSE FALSE FALSE FALSE
[4,] FALSE FALSE FALSE FALSE

, , 3

      [,1]  [,2]  [,3]  [,4]
[1,] FALSE FALSE  TRUE FALSE
[2,] FALSE    NA FALSE  TRUE
[3,] FALSE FALSE FALSE FALSE
[4,] FALSE FALSE FALSE FALSE

And then we're just saying assign a value to the conditions which are true. We can also use which(arr > 4, arr.ind = T) to return a matrix similar to @thelatemail's solution without the typing. This allows us to get to your original post answer:

which_cond <- which(arr>4, arr.ind = T) 
arr[which_cond] <- arr[which_cond] * which_cond[, 3]
arr
, , 1

     [,1] [,2] [,3] [,4]
[1,]    1    0    5    0
[2,]    0   NA    0    6
[3,]    0    0    0    0
[4,]    0    0    0    0

, , 2

     [,1] [,2] [,3] [,4]
[1,]    1    0   10    0
[2,]    0   NA    0   12
[3,]    0    0    0    0
[4,]    0    0    0    0

, , 3

     [,1] [,2] [,3] [,4]
[1,]    1    0   15    0
[2,]    0   NA    0   18
[3,]    0    0    0    0
[4,]    0    0    0    0

which_cond
     dim1 dim2 dim3
[1,]    1    3    1
[2,]    2    4    1
[3,]    1    3    2
[4,]    2    4    2
[5,]    1    3    3
[6,]    2    4    3

Performance:

#4x4x3 array
Unit: microseconds
                 expr    min      lq    mean  median      uq    max neval
        maur_improved    2.4    3.55    5.42    4.90    5.95   24.4   100
 latemail_all_at_once    6.4    8.70   14.00   15.20   18.40   25.3   100
        maur_for_loop 3280.0 3510.00 3810.00 3630.00 3770.00 6430.0   100
      cole_subset_mat    2.0    3.05    4.71    4.05    6.50   10.2   100
           cole_which   27.9   34.50   47.70   45.40   54.80  228.0   100

#4x4x3E6 array
Unit: milliseconds
                 expr   min    lq  mean median    uq  max neval
        maur_improved  82.9  84.8  89.7   85.8  87.4  165   100
 latemail_all_at_once 347.0 361.0 391.0  378.0 417.0  564   100
        maur_for_loop 422.0 432.0 462.0  451.0 486.0  721   100
      cole_subset_mat 304.0 330.0 369.0  354.0 395.0  527   100
           cole_which 783.0 842.0 899.0  878.0 928.0 1370   100

And code:

arr <- array(c(1,0,0,0,0,NA,0,0,5,0,0,0,0,6,0,0), dim=c(4,4,3))

library(microbenchmark)

x = microbenchmark(
  maur_improved = {
    arr[1,3, ] <- 100
    arr[2, 4, ] <- 100
  },
  latemail_all_at_once = {
    arr[cbind(c(1,2),c(3,4),rep(seq_len(dim(arr)[[3]]), each=2))] <- c(80,100)
  },
  maur_for_loop = {
    for (i in seq_len(dim(arr)[3])) {
      arr[1, 3, i] <- 100;       # Change entry (1, 3) of every 2d matrix 
      arr[2, 4, i] <- 100;       # Change entry (2, 4) of every 2d matrix
    }
  },
  cole_subset_mat = {
    arr[arr > 4] <- 100
  }
  , cole_which = {
    which_cond <- which(arr>4, arr.ind = T) 
    arr[which_cond] <- arr[which_cond] * which_cond[, 3]
  }
)
print(x, signif = 3)

Upvotes: 1

Maurits Evers
Maurits Evers

Reputation: 50678

I'm not entirely sure on your expected output, but perhaps something like this using a for loop?

arr <- array(c(1,0,0,0,0,NA,0,0,5,0,0,0,0,6,0,0), dim=c(4,4,3))

for (i in seq_len(dim(arr)[3])) {
    arr[1, 3, i] <- 100;       # Change entry (1, 3) of every 2d matrix 
    arr[2, 4, i] <- 100;       # Change entry (2, 4) of every 2d matrix
}
arr
#, , 1
#
#     [,1] [,2] [,3] [,4]
#[1,]    1    0  100    0
#[2,]    0   NA    0  100
#[3,]    0    0    0    0
#[4,]    0    0    0    0
#
#, , 2
#
#     [,1] [,2] [,3] [,4]
#[1,]    1    0  100    0
#[2,]    0   NA    0  100
#[3,]    0    0    0    0
#[4,]    0    0    0    0
#
#, , 3
#
#     [,1] [,2] [,3] [,4]
#[1,]    1    0  100    0
#[2,]    0   NA    0  100
#[3,]    0    0    0    0
#[4,]    0    0    0    0

As pointed out by @Cole, in this (simple) case there's no need for a for loop

arr[1, 3, ] <- 100
arr[2, 4, ] <- 100

is much faster than.

Upvotes: 2

thelatemail
thelatemail

Reputation: 93813

You can do it with one assignment too using matrix indexing:

arr[cbind(c(1,2),c(3,4),rep(seq_len(dim(arr)[[3]]), each=2))] <- c(80,100)
arr
#, , 1
# 
#     [,1] [,2] [,3] [,4]
#[1,]    1    0   80    0
#[2,]    0   NA    0  100
#[3,]    0    0    0    0
#[4,]    0    0    0    0
# 
#, , 2
#
#    [,1] [,2] [,3] [,4]
#[1,]    1    0   80    0
#[2,]    0   NA    0  100
#[3,]    0    0    0    0
#[4,]    0    0    0    0
# 
#, , 3
#
#     [,1] [,2] [,3] [,4]
#[1,]    1    0   80    0
#[2,]    0   NA    0  100
#[3,]    0    0    0    0
#[4,]    0    0    0    0

The part inside [] gives the indexes row/col/strata for each value to replace:

cbind(c(1,2),c(3,4),rep(seq_len(dim(arr)[[3]]), each=2))
#     row  col  strata
#     [,1] [,2] [,3]
#[1,]    1    3    1
#[2,]    2    4    1
#[3,]    1    3    2
#[4,]    2    4    2
#[5,]    1    3    3
#[6,]    2    4    3

Upvotes: 2

Related Questions