Почему dplyr такой медленный? - PullRequest
0 голосов
/ 23 января 2019

Как и большинство людей, на меня произвела впечатление Хэдли Уикхем и то, что он сделал для R - так что я решил, что я перенесу некоторые функции в его tidyverse ... сделав так, что мне стало интереснов чем смысл всего этого?

Мои новые dplyr функции намного медленнее , чем их базовые эквиваленты - я надеюсь, что я делаю что-то не так.Я особенно хотел бы получить некоторую отдачу от усилий, необходимых для понимания non-standard-evaluation.

Итак, что я делаю не так?Почему dplyr такой медленный?

Пример:

require(microbenchmark)
require(dplyr)

df <- tibble(
             a = 1:10,
             b = c(1:5, 4:0),
             c = 10:1)

addSpread_base <- function() {
    df[['spread']] <- df[['a']] - df[['b']]
    df
}

addSpread_dplyr <- function() df %>% mutate(spread := a - b)

all.equal(addSpread_base(), addSpread_dplyr())

microbenchmark(addSpread_base(), addSpread_dplyr(), times = 1e4)

Результаты синхронизации:

Unit: microseconds
              expr     min      lq      mean median      uq       max neval
  addSpread_base()  12.058  15.769  22.07805  24.58  26.435  2003.481 10000
 addSpread_dplyr() 607.537 624.697 666.08964 631.19 636.291 41143.691 10000

Поэтому использование dplyr функций для преобразования данных занимает около30 раз дольше - конечно, это не намерение?

Я подумал, что, возможно, это слишком простой случай - и что dplyr действительно бы блестел, если бы у нас был более реалистичный случай, когда мы добавляем столбец и поднабор данных - но это былохуже.Как видно из приведенного ниже времени, это примерно в 70 раз медленнее, чем базовый подход.

# mutate and substitute
addSpreadSub_base <- function(df, col1, col2) {
    df[['spread']] <- df[['a']] - df[['b']]
    df[, c(col1, col2, 'spread')]
}

addSpreadSub_dplyr <- function(df, col1, col2) {
    var1 <- as.name(col1)
    var2 <- as.name(col2)
    qq <- quo(!!var1 - !!var2)
    df %>% 
        mutate(spread := !!qq) %>% 
        select(!!var1, !!var2, spread)
}

all.equal(addSpreadSub_base(df, col1 = 'a', col2 = 'b'), 
          addSpreadSub_dplyr(df, col1 = 'a', col2 = 'b'))

microbenchmark(addSpreadSub_base(df, col1 = 'a', col2 = 'b'), 
               addSpreadSub_dplyr(df, col1 = 'a', col2 = 'b'), 
               times = 1e4)

Результаты:

Unit: microseconds
                                           expr      min       lq      mean   median       uq      max neval
  addSpreadSub_base(df, col1 = "a", col2 = "b")   22.725   30.610   44.3874   45.450   53.798  2024.35 10000
 addSpreadSub_dplyr(df, col1 = "a", col2 = "b") 2748.757 2837.337 3011.1982 2859.598 2904.583 44207.81 10000

1 Ответ

0 голосов
/ 23 января 2019

Это микросекунды, ваш набор данных имеет 10 строк, если только вы не планируете циклически выполнять миллионы наборов данных из 10 строк, ваш эталонный тест в значительной степени не имеет значения (и в этом случае я не могу представить ситуацию, когда это не будет разумно связать их вместе как первый шаг).

Давайте сделаем это с большим набором данных, например, в миллион раз больше:

df <- tibble(
  a = 1:10,
  b = c(1:5, 4:0),
  c = 10:1)

df2 <- bind_rows(replicate(1000000,df,F))

addSpread_base <- function(df) {
  df[['spread']] <- df[['a']] - df[['b']]
  df
}
addSpread_dplyr  <- function(df) df %>% mutate(spread = a - b)

microbenchmark::microbenchmark(
  addSpread_base(df2), 
  addSpread_dplyr(df2),
  times = 100)
# Unit: milliseconds
#                 expr      min       lq     mean   median       uq      max neval cld
# addSpread_base(df2) 25.85584 26.93562 37.77010 32.33633 35.67604 170.6507   100   a
# addSpread_dplyr(df2) 26.91690 27.57090 38.98758 33.39769 39.79501 182.2847   100   a

Все еще довольно быстро и не большая разница.

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

Комментаторы отмечают, что dplyr не слишком старается, чтобы быть быстрым, и, возможно, это правда, когда вы сравниваете с data.table, и интерфейс - это первая проблема, но авторы также усердно работают над скоростью. Например, гибридная оценка позволяет (если я правильно понял) выполнять код C непосредственно на сгруппированных данных при объединении с общими функциями, что может быть намного быстрее, чем базовый код, но простой код всегда будет работать быстрее с простыми функциями.

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