Как повысить эффективность с помощью group by и mutate

#r #tidyverse

#r #tidyverse

Вопрос:

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

Рассмотрим следующую структуру (упрощенную):

 data <- data.frame(name = c("a","b","c","d","e","f"),
              ID =c(1,1,1,2,2,2),
              sales = c(100, 250, 300, 50, 600, 390),
              t   = c(0.1,0.3,0.4,0.05,0.15,0.2),
              n=c(1,2,3,1,2,3),
              correct_result = c(-221.4,-27.8,69.1,-143.71,-19.11,43.19))
  

data$ID <- как целое число (data $ID)

Я обнаружил, что более эффективно группировать по идентификатору как целое число, а не как фактор.

Формула, которую я пытаюсь вычислить, подразумевает, что для данного имени, скажем, «a», я хочу взять сумму продаж всех других связанных имен (по их идентификатору) и разделить на 1-t для соответствующих имен. Чтобы получить представление о том, что я пытаюсь вычислить для каждого идентификатора и имени:

 (data$sales[2]/(1-data$t[2]))*(data$t[1]-data$t[2])   (data$sales[3]/(1-data$t[3]))*(data$t[1]-data$t[3])
(data$sales[1]/(1-data$t[1]))*(data$t[2]-data$t[1])   (data$sales[3]/(1-data$t[3]))*(data$t[2]-data$t[3])
(data$sales[1]/(1-data$t[1]))*(data$t[3]-data$t[1])   (data$sales[1]/(1-data$t[1]))*(data$t[3]-data$t[1])
  
 library(tidyverse)
  
 # The Model:
    data <- data %>%
  mutate(ovt=sales/(1-t))

sumforgoup1 <-function(forname , groupid){   # Create the function: 
  
  key_t <- dplyr::filter(data,
                         ID == groupid,
                         name==forname) %>% pull(t)
  
  temp <- dplyr::filter(data,
                        ID == groupid,
                        name!=forname) %>% mutate(diff_key_t=
                                                    key_t - t)
  
  sum(temp$ovt*temp$diff_key_t)
}

mutate(rowwise(data),
       result = sumforgoup1(name,ID))          # Store result in a new column.
  

Итак, функция отлично работает в этом наборе данных. Однако, когда я применяю эту функцию к большему набору данных, скажем, с 300 строками, формула занимает примерно 6 секунд. Увеличение количества строк еще на 300 (т. Е. На 600 строк) занимает около 35 секунд..
У меня около 30.000 строк, так что это займет несколько часов..

В полном наборе данных я преобразовал ID в factor, чтобы вы могли получить представление об уровнях (sub здесь = name):

 $ ID   : Factor w/ 9097 levels "1","2","3","4",..: 1 2 2 3 4 5 5 5 5 5 ...
$ sub  : Factor w/ 40 levels "1","2","3","4",..: 1 1 2 1 1 1 2 3 4 5 ...
  

Приветствуются любые рекомендации / советы,
Спасибо!

Комментарии:

1. Еще один хороший совет — использовать dtplyr (есть ли причина, по которой люди продолжают использовать dplyr вместо dtplyr?) Или data.table . Улучшение скорости реально.

Ответ №1:

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

 library(dplyr)
library(purrr)

data %>%
  group_by(ID) %>%
  mutate(result = map_dbl(seq_along(ID), ~ sum((sales[-.x] / (1 - t[-.x]) * (t[.x] - t[-.x])))))

# A tibble: 6 x 8
# Groups:   ID [2]
  name     ID sales     t     n correct_result   ovt result
  <chr> <dbl> <dbl> <dbl> <dbl>          <dbl> <dbl>  <dbl>
1 a         1   100  0.1      1         -221.  111.  -221. 
2 b         1   250  0.3      2          -27.8 357.   -27.8
3 c         1   300  0.4      3           69.1 500     69.0
4 d         2    50  0.05     1         -144.   52.6 -144. 
5 e         2   600  0.15     2          -19.1 706.   -19.1
6 f         2   390  0.2      3           43.2 488.    43.2
  

Комментарии:

1. Это сработало! Спасибо! После нескольких недель разочарования было приятно получить ответ.