Рассчитать rowSums для трехмерного массива без цикла for / apply - PullRequest
0 голосов
/ 25 сентября 2018

Взять массив b_array:

set.seed(123)
a_mtx = matrix(1:15,ncol=5)
b_mtx = matrix(seq(1,5,length.out=30),ncol=5)

b_array = 
  array(
    b_mtx,
    dim = 
      c(
        nrow(b_mtx),
        ncol(b_mtx), 
        nrow(a_mtx)
      )
    )

Если я хочу вычислить сумму каждого столбца каждого "среза" или "листа" в b_array, я могу использовать colSums с егоАргумент измерения:

colSums(b_array, dim = 1)
#          [,1]      [,2]      [,3]
#[1,]  8.068966  8.068966  8.068966
#[2,] 13.034483 13.034483 13.034483
#[3,] 18.000000 18.000000 18.000000
#[4,] 22.965517 22.965517 22.965517
#[5,] 27.931034 27.931034 27.931034

Чтобы сделать то же самое для сумм строк, я не могу использовать аргумент измерения rowSums, так как он обрабатывается по-другому, поэтому я прибегаю к apply:

apply(b_array, 3, rowSums)
#         [,1]     [,2]     [,3]
#[1,] 13.27586 13.27586 13.27586
#[2,] 13.96552 13.96552 13.96552
#[3,] 14.65517 14.65517 14.65517
#[4,] 15.34483 15.34483 15.34483
#[5,] 16.03448 16.03448 16.03448
#[6,] 16.72414 16.72414 16.72414

Я хочу выполнить те же вычисления для массива с гораздо большей размерностью, чтобы apply и другие методы цикла for были неэффективными.

Существуют ли альтернативные, действительно векторизованные методы?

Ответы [ 2 ]

0 голосов
/ 25 сентября 2018

Другой вариант с использованием aperm

t(colSums(aperm(b_array, perm = c(2, 3, 1))))
#         [,1]     [,2]     [,3]
#[1,] 13.27586 13.27586 13.27586
#[2,] 13.96552 13.96552 13.96552
#[3,] 14.65517 14.65517 14.65517
#[4,] 15.34483 15.34483 15.34483
#[5,] 16.03448 16.03448 16.03448
#[6,] 16.72414 16.72414 16.72414
0 голосов
/ 25 сентября 2018

Мнение по умолчанию (я полагаю) в отношении MARGIN= (второго) аргумента для apply заключается в том, что оно означает «уменьшенную ось» (при агрегировании ... здесь упрощается для эффекта).Однако, с другой стороны, все остальные измерения остаются неизменными.

Эффективный эквивалент colSums(ary), например, равен apply(ary, 2, sum), что означает «сохранить ось 1 неизменной».(colSums фактически выполняется внутренне, а не с apply.) Итак, чтобы расширить логику «все оси , кроме », давайте осознаем, что для вашей b_array вы хотите, чтобы 1-я и 3-я оси осталисьТаким образом,

apply(b_array, c(1,3), sum)
#          [,1]     [,2]     [,3]
# [1,] 13.27586 13.27586 13.27586
# [2,] 13.96552 13.96552 13.96552
# [3,] 14.65517 14.65517 14.65517
# [4,] 15.34483 15.34483 15.34483
# [5,] 16.03448 16.03448 16.03448
# [6,] 16.72414 16.72414 16.72414

настолько эффективен, насколько вы можете (я думаю) получить сумму «столбца» с n -мерным массивом.

Edit :

@ markus Использование aperm быстрее в широком диапазоне размеров матриц, хотя, по-видимому, оно сходится при больших матрицах.

ns <- c(10,50,100,1000)
set.seed(123)
arrays <- lapply(ns, function(n) array(runif(3*n*n), dim=c(n,n,3)))

mapply(identical,
       lapply(arrays, function(a) t(colSums(aperm(a, perm = c(2, 3, 1))))),
       lapply(arrays, function(a) apply(a, c(1,3), sum)))
# [1] TRUE TRUE TRUE TRUE

library(microbenchmark)
microbenchmark(
  aperm10 = t(colSums(aperm(arrays[[1]], perm = c(2, 3, 1)))),
  aperm50 = t(colSums(aperm(arrays[[2]], perm = c(2, 3, 1)))),
  aperm100 = t(colSums(aperm(arrays[[3]], perm = c(2, 3, 1)))),
  aperm1000 = t(colSums(aperm(arrays[[4]], perm = c(2, 3, 1)))),
  apply10 = apply(arrays[[1]], c(1,3), sum),
  apply50 = apply(arrays[[2]], c(1,3), sum),
  apply100 = apply(arrays[[3]], c(1,3), sum),
  apply1000 = apply(arrays[[4]], c(1,3), sum),
  times=10
)
# Unit: microseconds
#       expr     min      lq     mean   median      uq     max neval
#    aperm10    19.1    25.5    46.74    39.55    59.2   105.8    10
#    aperm50    55.7    77.2    96.36    94.30   115.6   149.8    10
#   aperm100   231.2   247.2   267.14   258.35   295.5   301.8    10
#  aperm1000 47282.5 47568.4 49235.19 49581.85 50118.4 52034.4    10
#    apply10    53.7    59.1    78.42    63.15   105.6   123.5    10
#    apply50   263.9   282.3   318.08   306.60   366.4   383.0    10
#   apply100   637.7   686.6   712.65   710.75   741.5   799.7    10
#  apply1000 40173.7 52735.7 52170.08 54349.65 55692.9 57375.9    10

(Я не проверял использование памяти.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...