как ускорить расчет кросс-столбца в сгруппированном кадре данных - PullRequest
1 голос
/ 07 апреля 2019

--- Кросспостинг с форума сообщества rstudio для потенциальных решений за пределами tidyverse.

Основная ситуация заключается в том, что расчеты независимы между группами, но каждой группе необходимо предоставить некоторые аргументы, рассчитанные из ее самого. Тривиальный пример - найти индекс первого элемента, который составляет менее половины максимумов столбца. Единственный поворот заключается в том, что в одном столбце X необходимо использовать максимумы, рассчитанные по другим A, B, C.

У меня есть решение, использующее group_map (аналогично do) для моего вопроса по групповой расчет . Но производительность не кажется оптимальной. Кажется, что summarise_at занимает гораздо больше времени при использовании с group_map (по сравнению с таймингами без него)

library(tidyverse)

times <- 1e5
cols <- 4
df3 <- as.data.frame(x = matrix(rnorm(times * cols, mean = 5), ncol = cols)) %>% 
   rename(A = V1, B = V2, C = V3, X = V4)

df3 <- cbind(grp = rep(seq_len(1e3), each = 100), df3) %>% 
   group_by(grp)

system.time(
  df3 %>% 
    group_map(~
    { 
      all_max <- summarise_at(., vars(A:C), max) %>% mutate(X = rowMeans(.))
      map2_df(., all_max, ~match(TRUE, .x < 0.5 * .y))
    }
    )
)
#>    user  system elapsed 
#>    3.87    0.00    3.98

system.time(
  df3 %>% summarise_at(vars(A:C), max) %>% mutate(X = rowMeans(.))
)
#>    user  system elapsed 
#>    0.02    0.00    0.01

system.time(
  df3 %>% summarise_at(vars(A:X), ~match(TRUE, . < 0.5 * max(.)))
)  
#>    user  system elapsed 
#>    0.25    0.02    0.26

Создано в 2019-04-05 пакетом представить (v0.2.1)

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

1 Ответ

2 голосов
/ 08 апреля 2019

Из того, что я могу сказать, это завершается так же, как ваш код менее чем за полсекунды на моей машине:

library(data.table)
DT = as.data.table(matrix(rnorm(times * cols, mean = 5), times, cols))
setnames(DT, c('A', 'B', 'C', 'X'))
DT[ , grp := rep(seq_len(1e3), each = 100)]

setkey(DT, grp)

DT[DT[ , lapply(.SD, max), keyby = grp, .SDcols = !'X'
       ][ , X := Reduce(`+`, .SD)/ncol(.SD), .SDcols = !'grp'], {
  i.A; i.B; i.C; i.X
  lapply(names(.SD), function(j) 
    which.max(eval(as.name(j)) < .5 * eval(as.name(paste0('i.', j)))))
}, on = 'grp', by = .EACHI, .SDcols = !'grp']
#        grp V1 V2 V3 V4
#    1:    1  3 30  1  4
#    2:    2  6 15  4 10
#    3:    3  2  5  7  2
#    4:    4  8 16  5  8
#    5:    5 10  3  1  7
#   ---                 
#  996:  996 11  5  3 13
#  997:  997  3  3  3 11
#  998:  998 14 21  2 10
#  999:  999 18  2  1 41
# 1000: 1000  8  7  3  3

По сути, вы создаете справочную таблицу с соответствующими заглавными буквами и присоединяетесь обратно.

Вы можете отделить это, написав:

lookup = 
  DT[ , lapply(.SD, max), keyby = grp, .SDcols = !'X'
     ][ , X := Reduce(`+`, .SD)/ncol(.SD), .SDcols = !'grp']
DT[lookup, on = 'grp', {
  i.A; i.B; i.C; i.X
  lapply(names(.SD), function(j) 
    which.max(eval(as.name(j)) < .5 * eval(as.name(paste0('i.', j)))))
}, by = .EACHI, .SDcols = !'grp']

После того, как он отделен, вы также получаете гибкость получения get (что, по моему опыту, медленнее, чем eval(as.name())):

DT[lookup, on = 'grp', {
  lapply(names(.SD), function(j) 
    which.max(eval(as.name(j)) < .5 * get(paste0('i.', j))))
}, by = .EACHI, .SDcols = !'grp']
#        grp V1 V2 V3 V4
#    1:    1  1  5 26  3
#    2:    2  6  7  3  4
#    3:    3  2  6  1 13
#    4:    4  5  2 12  5
#    5:    5  9 12  2  4
#   ---                 
#  996:  996  1  3  4  1
#  997:  997  1  6  3 13
#  998:  998 10 13  9  8
#  999:  999  2  4 13  4
# 1000: 1000  7 30 19 14
...