Data.table vs dplyr: может ли что-то сделать хорошо, а другое плохо или плохо?

Обзор

Я относительно знаком с data.table, а не с dplyr. Я прочитал некоторые dplyr виньетки и примеры, появившиеся на SO, и до сих пор мои выводы заключаются в следующем:

  • data.table и dplyr сравнимы по скорости, за исключением случаев, когда имеется много групп (т.е. > 10-100K) и в некоторых других обстоятельствах (см. ниже контрольные показатели).
  • dplyr имеет более доступный синтаксис
  • dplyr тезисы (или будут) потенциальные взаимодействия с БД
  • Есть некоторые незначительные различия в функциональности (см. ниже "Примеры/использование" )

В моем сознании 2. не имеет большого веса, потому что я довольно хорошо знаком с ним data.table, хотя я понимаю, что для пользователей, новых для обоих, это будет большой фактор. Я хотел бы избежать аргумента, который более интуитивно понятен, поскольку это не имеет значения для моего конкретного вопроса, заданного с точки зрения кого-то, уже знакомого с data.table. Я также хотел бы избежать дискуссии о том, как "более интуитивно понятный" ведет к более быстрому анализу (конечно, правда, но опять же, не то, что меня больше всего интересует).

Вопрос

Что я хочу знать:

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

Один недавний вопрос SO заставлял меня думать об этом немного больше, потому что до этого момента я не думал, что dplyr будет предлагать намного больше, чем я могу уже в data.table. Вот решение dplyr (данные в конце Q):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

Это было намного лучше, чем моя попытка взлома в решении data.table. Тем не менее, хорошие решения data.table также очень хороши (спасибо Жан-Роберту, Арун, и обратите внимание, что я предпочитал одно утверждение строго строго оптимального решения):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

Синтаксис последнего может показаться очень эзотерическим, но на самом деле это довольно просто, если вы привыкли к data.table (т.е. не используете некоторые из более эзотерических трюков).

В идеале, что бы я хотел увидеть, это несколько хороших примеров: путь dplyr или data.table значительно более краток или значительно лучше.

Примеры

Применение
  • dplyr не позволяет сгруппированным операциям возвращать произвольное количество строк (из eddi question, обратите внимание: похоже, что это будет быть реализовано в dplyr 0.5, также, @beginneR показывает потенциальный обход, используя do в ответе на @eddi).
  • data.table поддерживает скользящие соединения (спасибо @dholstius), а также перекрытие соединений
  • data.table внутренне оптимизирует выражения формы DT[col == value] или DT[col %in% values] для скорости с помощью автоматической индексации, которая использует бинарный поиск при использовании того же базового синтаксиса R. Смотрите здесь для получения более подробной информации и крошечного теста.
  • dplyr предлагает стандартные оценочные версии функций (например, regroup, summarize_each_), которые могут упростить программное использование dplyr (обратите внимание, что программное использование data.table, безусловно, возможно, просто требует некоторой осторожности, замена/цитирование и т.д., по крайней мере, насколько мне известно).
Ориентиры

Данные

Это для первого примера, который я показал в разделе вопросов.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))
dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))
474
задан 29 янв. '14 в 18:21
источник поделиться
3 ответов

Мы должны охватить по крайней мере эти аспекты, чтобы обеспечить всесторонний ответ/сравнение (в каком-то конкретном порядке): Speed, Memory usage, Syntax и Features.

Мое намерение состоит в том, чтобы максимально четко охватить каждую из них с точки зрения данных.

Примечание: если явно не указано иначе, ссылаясь на dplyr, мы ссылаемся на интерфейс dplyr data.frame, внутренние элементы которого находятся на С++, используя Rcpp.


Синтаксис data.table согласован в своей форме - DT[i, j, by]. Чтобы сохранить i, j и by вместе, по дизайну. Сохраняя связанные операции вместе, он позволяет легко оптимизировать операции для скорости и, что важнее, использования памяти, а также предоставить некоторые мощные функции, при этом сохраняя согласованность в синтаксисе.

1. Скорость

В вопрос, уже показывающий данные, добавлено несколько тестов (хотя в основном по операциям группировки), таблица становится быстрее, чем dplyr, поскольку количество групп и/или строк группируется по возрастанию, включая тесты Matt по группировке от 10 миллионов до 2 миллиардов строк (100 ГБ в ОЗУ) на 100 - 10 миллионов групп и различные столбцы группировки, которые также сравнивают pandas.

В тестах было бы неплохо также охватить эти оставшиеся аспекты:

  • Операции группировки, включающие подмножество строк - т.е. операции DT[x > val, sum(y), by = z].

  • Контролируйте другие операции, такие как обновление и объединение.

  • Кроме того, сравнивается объем памяти для каждой операции в дополнение к времени выполнения.

2. Использование памяти

  • Операции с участием filter() или slice() в dplyr могут быть неэффективными в памяти (как для data.frames, так и для data.tables). См. это сообщение.

    Обратите внимание, что комментарий Хэдли говорит о скорости (что dplyr для него очень много), тогда как главная проблема здесь - память.

  • интерфейс data.table на данный момент позволяет изменять/обновлять столбцы по ссылке (обратите внимание, что нам не нужно повторно назначать результат обратно переменной).

    # sub-assign by reference, updates 'y' in-place
    DT[x >= 1L, y := NA]
    

    Но dplyr никогда не будет обновляться по ссылке. Эквивалент dplyr был бы (обратите внимание, что результат нужно переназначить):

    # copies the entire 'y' column
    ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
    

    Для этого существует проблема ссылочная прозрачность. Обновление объекта data.table по ссылке, особенно внутри функции, может быть не всегда желательным. Но это невероятно полезная функция: см. этот и этот для интересных случаев. И мы хотим сохранить его.

    Поэтому мы работаем над экспортом функции shallow() в data.table, которая предоставит пользователю обе возможности. Например, если желательно не изменять входные данные .table внутри функции, тогда можно сделать:

    foo <- function(DT) {
        DT = shallow(DT)          ## shallow copy DT
        DT[, newcol := 1L]        ## does not affect the original DT 
        DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
        DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                                  ## also get modified.
    }
    

    Не используя shallow(), сохраняется прежняя функциональность:

    bar <- function(DT) {
        DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
        DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
    }
    

    Создав мелкую копию с помощью shallow(), мы понимаем, что вы не хотите изменять исходный объект. Мы заботимся обо всем внутренне, чтобы гарантировать, что при одновременном обеспечении копирования столбцов вы изменяете только тогда, когда это абсолютно необходимо. Когда это реализовано, это должно полностью решить проблему ссылочной прозрачности, предоставляя пользователю обе возможности.

    Кроме того, после экспорта shallow() dplyr data.table интерфейс должен избегать почти всех копий. Поэтому те, кто предпочитает синтаксис dplyr, могут использовать его с data.tables.

    Но у него по-прежнему не будет много функций, которые предоставляет data.table, включая (под) назначение по ссылке.

  • Совокупность при присоединении:

    Предположим, что у вас есть два data.tables следующим образом:

    DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
    #    x y z
    # 1: 1 a 1
    # 2: 1 a 2
    # 3: 1 b 3
    # 4: 1 b 4
    # 5: 2 a 5
    # 6: 2 a 6
    # 7: 2 b 7
    # 8: 2 b 8
    DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
    #    x y mul
    # 1: 1 a   4
    # 2: 2 b   3
    

    И вы хотите получить sum(z) * mul для каждой строки в DT2 при соединении столбцами x,y. Мы можем либо:

    • 1) aggregate DT1, чтобы получить sum(z), 2) выполнить соединение и 3) умножить (или)

      # data.table way
      DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
      
      # dplyr equivalent
      DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
          right_join(DF2) %>% mutate(z = z * mul)
      
    • 2) сделайте все за один раз (используя by = .EACHI):

      DT1[DT2, list(z=sum(z) * mul), by = .EACHI]
      

    В чем преимущество?

    • Нам не нужно выделять память для промежуточного результата.

    • Нам не нужно группировать/хэш дважды (один для агрегации и другого для присоединения).

    • И что более важно, операция, которую мы хотели выполнить, понятна, посмотрев j в (2).

    Отметьте этот постдля детального объяснения by = .EACHI. Никаких промежуточных результатов не материализуется, а объединение + агрегат выполняется за один раз.

    Посмотрите этот, этот и этот для реальных сценариев использования.

    В dplyr вам нужно объединиться и агрегировать или скомпилировать сначала, а затем присоединиться, ни одно из которых не будет столь эффективным, с точки зрения память (которая, в свою очередь, переводит на скорость).

  • Обновление и объединение:

    Рассмотрим приведенный ниже код данных.

    DT1[DT2, col := i.mul]
    

    добавляет/обновляет DT1 столбец col с помощью mul из DT2 в те строки, где DT2 ключевой столбец соответствует DT1. Я не думаю, что существует точный эквивалент этой операции в dplyr, т.е. Не избегая операции *_join, которая должна была бы скопировать весь DT1 только для добавления к нему нового столбца, что не является необходимым.

    Отметьте этот пост для реального сценария использования.

Подводя итог, важно понимать, что каждый бит оптимизации имеет значение. Поскольку Grace Hopper сказал бы, Назовите свои наносекунды!

3. Синтаксис

Теперь рассмотрим синтаксис. Хэдли прокомментировал здесь:

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

Я нахожу это замечание бессмысленным, потому что оно очень субъективно. Возможно, мы попытаемся противопоставить последовательность синтаксиса. Мы сравним синтаксис data.table и dplyr бок о бок.

Мы будем работать с фиктивными данными, приведенными ниже:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  • Основные операции агрегации/обновления.

    # case (a)
    DT[, sum(y), by = z]                       ## data.table syntax
    DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
    DT[, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
    
    # case (b)
    DT[x > 2, sum(y), by = z]
    DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
    DT[x > 2, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
    
    # case (c)
    DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
    DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
    DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
    DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
    
    • Синтаксис data.table является компактным и dplyr довольно подробным. Вещи более или менее эквивалентны в случае (а).

    • В случае (b) нам пришлось использовать filter() в dplyr при подведении итогов. Но при обновлении нам пришлось переместить логику внутри mutate(). Однако в data.table мы выражаем обе операции с одной и той же логикой - работаем с строками, где x > 2, но в первом случае получаем sum(y), тогда как во втором случае обновляем эти строки для y своей суммарной суммой.

      Это то, что мы имеем в виду, когда говорим, что форма DT[i, j, by] непротиворечива.

    • Аналогично в случае (c), когда мы имеем условие if-else, мы можем выразить логику "как есть" как в data.table, так и в dplyr. Однако, если мы хотим вернуть только те строки, где условие if удовлетворяет и пропускает в противном случае, мы не можем напрямую использовать summarise() (AFAICT). Сначала мы должны filter(), а затем суммируем, потому что summarise() всегда ожидает одно значение.

      Пока он возвращает тот же результат, использование filter() делает действительную операцию менее очевидной.

      В первом случае вполне возможно использовать filter() (для меня это не кажется очевидным), но я хочу сказать, что нам не нужно.

  • Агрегация/обновление для нескольких столбцов

    # case (a)
    DT[, lapply(.SD, sum), by = z]                     ## data.table syntax
    DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
    DT[, (cols) := lapply(.SD, sum), by = z]
    ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
    
    # case (b)
    DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
    DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
    
    # case (c)
    DT[, c(.N, lapply(.SD, sum)), by = z]     
    DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
    
    • В случае (a) коды более или менее эквивалентны. data.table использует знакомую базовую функцию lapply(), тогда как dplyr вводит *_each() вместе с набором функций funs().

    • data.table := требуется указать имена столбцов, тогда как dplyr автоматически генерирует их.

    • В случае (b) синтаксис dplyr относительно прост. Улучшение агрегации/обновлений для нескольких функций содержится в списке data.table.

    • В случае (c) dplyr возвратит n() столько раз, сколько столбцов, а не только один раз. В data.table все, что нам нужно сделать, это вернуть список в j. Каждый элемент списка станет столбцом в результате. Итак, мы можем снова использовать знакомую базовую функцию c() для объединения .N в list, которая возвращает list.

    Примечание. Еще раз, в data.table, все, что нам нужно сделать, это вернуть список в j. Каждый элемент списка станет столбцом в результате. Вы можете использовать c(), as.list(), lapply(), list() и т.д.... базовые функции для этого, не изучая никаких новых функций.

    Вам нужно будет узнать как минимум специальные переменные - .N и .SD. Эквивалент в dplyr равен n() и .

  • Присоединяется

    dplyr предоставляет отдельные функции для каждого типа соединения, где, поскольку data.table позволяет объединениям использовать один и тот же синтаксис DT[i, j, by] (и по причине). Он также обеспечивает эквивалентную функцию merge.data.table() в качестве альтернативы.

    setkey(DT1, x, y)
    
    # 1. normal join
    DT1[DT2]            ## data.table syntax
    left_join(DT2, DT1) ## dplyr syntax
    
    # 2. select columns while join    
    DT1[DT2, .(z, i.mul)]
    left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    
    # 3. aggregate while join
    DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
    DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
        inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
    
    # 4. update while join
    DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
    ??
    
    # 5. rolling join
    DT1[DT2, roll = -Inf]
    ??
    
    # 6. other arguments to control output
    DT1[DT2, mult = "first"]
    ??
    
    • Некоторые могут найти отдельную функцию для каждого объединения намного лучше (левый, правый, внутренний, анти, полу и т.д.), тогда как другим может понравиться data.table DT[i, j, by] или merge(), который похож на основание R.

    • Однако соединения dplyr делают именно это. Больше ничего. Не меньше.

    • data.tables могут выбирать столбцы при соединении (2), а в dplyr вам нужно select() сначала на обоих файлах данных перед тем, как присоединиться, как показано выше. В противном случае вы могли бы связать соединение с ненужными столбцами, чтобы удалить их позже, и это неэффективно.

    • data.tables могут объединяться при объединении (3), а также обновлять при соединении (4) с использованием функции by = .EACHI. Почему materialse весь результат объединения для добавления/обновления всего нескольких столбцов?

    • data.table способен катить соединения (5) - roll вперед, LOCF, откат назад, NOCB, ближайший.

    • data.table также имеет аргумент mult =, который выбирает первое, последнее или все совпадения (6).

    • data.table имеет allow.cartesian = TRUE аргумент для защиты от случайных недопустимых объединений.

И снова синтаксис совместим с DT[i, j, by] с дополнительными аргументами, позволяющими дополнительно контролировать вывод.

  1. do()...

    dplyr summary специально разработан для функций, возвращающих одно значение. Если ваша функция возвращает несколько/неравных значений, вам придется прибегнуть к do(). Вы должны заранее знать обо всех возвращаемых значениях функций.

    DT[, list(x[1], y[1]), by = z]                 ## data.table syntax
    DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
    DT[, list(x[1:2], y[1]), by = z]
    DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
    
    DT[, quantile(x, 0.25), by = z]
    DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
    DT[, quantile(x, c(0.25, 0.75)), by = z]
    DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
    
    DT[, as.list(summary(x)), by = z]
    DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
    
    • .SD эквивалент .

    • В data.table вы можете бросить почти что-нибудь в j - единственное, что нужно запомнить, - это вернуть список, чтобы каждый элемент списка преобразовывался в столбец.

    • В dplyr не может этого сделать. Приходится прибегать к do() в зависимости от того, насколько вы уверены в том, всегда ли ваша функция возвращает одно значение. И это довольно медленно.

И снова синтаксис data.table соответствует DT[i, j, by]. Мы можем просто оставлять выражения в j, не беспокоясь об этом.

Посмотрите этот вопрос SO и этот. Интересно, можно ли выразить ответ так же просто, используя синтаксис dplyr...

Подводя итог, я особо выделил несколько случаев, когда синтаксис dplyr либо неэффективен, либо ограничен, либо не позволяет сделать операции простыми. Это особенно связано с тем, что data.table получает довольно немного отзыва о синтаксисе "труднее читать/учиться" (например, тот, который вставлен/связан выше). Большинство сообщений, которые охватывают dplyr, говорят о самых простых операциях. И это здорово. Но важно также реализовать свои синтаксические и функциональные ограничения, и я еще не видел сообщение об этом.

data.table также имеет свои причуды (некоторые из которых я указал, что мы пытаемся исправить). Мы также пытаемся улучшить соединение data.table, поскольку я выделил здесь.

Но следует также учитывать количество функций, которые не хватает dplyr по сравнению с data.table.

4. Особенности

Я отметил большинство функций здесь, а также в этом сообщении. Кроме того:

  • fread - быстрый просмотрщик файлов доступен уже давно.

  • fwrite - NEW в текущем девеле, v1.9.7, теперь имеется доступ к параллельному быстрому файловому архиву. См. этот пост для подробного объяснения реализации и # 1664 для отслеживания дальнейших разработок.

  • Автоматическая индексация - еще одна удобная функция для оптимизации синтаксиса базы R, как есть, внутри.

  • Специальная группировка: dplyr автоматически сортирует результаты, группируя переменные во время summarise(), что может быть не всегда желательно.

  • Многочисленные преимущества в соединениях data.table(для эффективности и синтаксиса скорости/памяти), упомянутых выше.

  • Неравновесные соединения: это НОВАЯ функция, доступная в версии v.1.9.7+. Он позволяет объединять другие операторы <=, <, >, >= вместе со всеми другими преимуществами соединений data.table.

  • Объединение диапазонов перекрытия было реализовано в data.table в последнее время. Проверьте этот пост для обзора с помощью тестов.

  • setorder() функция в data.table, которая позволяет действительно быстро переупорядочить data.tables по ссылке.

  • dplyr предоставляет интерфейс к базам данных с использованием того же синтаксиса, который в настоящее время не используется в таблице данных.

  • data.table обеспечивает более быстрые эквиваленты заданных операций из v1.9.7 + (написанные Яном Горецким) - fsetdiff, fintersect, funion и fsetequal с дополнительным аргументом all (as в SQL).

  • data.table загружается без предупреждения маскировки и имеет механизм, описанный здесь для [.data.frame совместимости при передаче в любой пакет R. dplyr изменяет базовые функции filter, lag и [, что может вызвать проблемы; например здесь и здесь.


Наконец:

  • В базах данных нет причин, по которым data.table не может предоставить аналогичный интерфейс, но теперь это не приоритет. Это может привести к сбою, если пользователям очень понравится эта функция. Не уверен.

  • В parallelism - Все сложно, пока кто-то не идет вперед и не делает этого. Конечно, это потребует усилий (быть потокобезопасным).

    • В настоящее время выполняется прогресс (в версии v.9.9 devel), направленный на параллелизацию известных частей времени для увеличения прироста производительности с помощью OpenMP.
315
ответ дан 31 дек. '15 в 11:27
источник

Здесь моя попытка всестороннего ответа с точки зрения dplyr, следуя широкому описанию ответа Аруна (но несколько перестроен основанные на разных приоритетах).

Синтаксис

Существует некоторая субъективность синтаксиса, но я согласен с тем, что приведение в соответствие таблицы данных затрудняет изучение и усложнение чтения. Это отчасти потому, что dplyr решает гораздо более легкую проблему!

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

Вы создаете сложность, прокладывая эти простые операции вместе с %>%. Вот пример из одного из сообщений Arun связанных к:

diamonds %>%
  filter(cut != "Fair") %>%
  group_by(cut) %>%
  summarize(
    AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = n()
  ) %>%
  arrange(desc(Count))

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

Здесь эквивалентный код данных.

diamondsDT <- data.table(diamonds)
diamondsDT[
  cut != "Fair", 
  .(AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = .N
  ), 
  by = cut
][ 
  order(-Count) 
]

Сложнее выполнить этот код, если вы уже не знакомы с Таблица данных. (Я также не мог понять, как сделать отступ повторным [ таким образом, который хорошо выглядит на мой взгляд). Лично, когда я смотрю код I написал 6 месяцев назад, ему нравится смотреть на код, написанный незнакомцем, поэтому я пришел, чтобы предпочесть простой, если подробный код.

Два других незначительных фактора, которые, по моему мнению, несколько уменьшают читаемость:

  • Поскольку почти каждая операция таблицы данных использует [, вам нужно дополнительное контекст, чтобы выяснить, что происходит. Например, x[y] объединение двух таблиц данных или извлечение столбцов из фрейма данных? Это всего лишь небольшая проблема, потому что в хорошо написанном коде имена переменных должны указывать на то, что происходит.

  • Мне нравится, что group_by() - отдельная операция в dplyr. Это коренным образом меняет вычисления, поэтому я считаю, что должно быть очевидно при уменьшении кода, и легче определить group_by(), чем аргумент by для [.data.table.

Мне также нравится, что труба не ограничивается только одним пакетом. Вы можете начать с данные с tidyr и закончите с сюжетом в ggvis. И вы не ограничиваясь пакетами, которые я пишу - любой может написать функцию который образует бесшовную часть канала обработки данных. На самом деле, я скорее предпочитают предыдущий код данных. таблица, переписанная с помощью %>%:

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

И идея трубопровода с %>% не ограничивается только кадрами данных и легко обобщается на другие контексты: интерактивная сеть графика, web соскоб, gists, время выполнения контракты,...)

Память и производительность

Я собрал их вместе, потому что для меня это не так важно. Большинство пользователей R работают с более чем 1 миллионом строк данных, а dplyr - достаточно быстро для такого размера данных, о которых вы не знаете время обработки. Мы оптимизируем dplyr для выразительности на средних данных; не стесняйтесь использовать data.table для сырой скорости при больших данных.

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

Все, что сказал, производительность dplyr улучшится в долгосрочной перспективе. Что ж определенно реализовать некоторые из великих идей data.table, таких как radix упорядочивание и использование одного и того же индекса для объединений и фильтров. Мы также работая над параллелизацией, чтобы мы могли использовать несколько ядер.

Функции

Несколько вещей, над которыми мы планируем работать в 2015 году:

  • пакет fastread, чтобы упростить загрузку файлов с диска и в к памяти, аналог к ​​ fread().

  • Более гибкие соединения, включая поддержку неравномерных объединений.

  • Более гибкая группировка, например, образцы бутстрапов, накопительные пакеты и многое другое

Я также инвестирую время в улучшение R database разъемы, возможность разговаривать с web apis, и это облегчает scrape html pages.

264
ответ дан 08 янв. '15 в 15:39
источник

В прямой ответ на заголовок вопроса...

dplyr определенно делает то, что data.table не может.

Ваша точка № 3

dplyr тезисы (или будут) потенциальные взаимодействия с БД

является прямым ответом на ваш собственный вопрос, но не поднимается до достаточно высокого уровня. dplyr - действительно расширяемый интерфейс для нескольких механизмов хранения данных, где data.table является расширением до одного.

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

Вы никогда не захотите (я надеюсь) увидеть день, когда data.table пытается перевести ваши запросы на создание операторов SQL, работающих с дисковыми или сетевыми хранилищами данных.

dplyr может делать вещи data.table не будет или может не работать.

Основываясь на дизайне рабочей памяти, data.table может иметь гораздо более трудное время, расширяя себя на параллельную обработку запросов, чем dplyr.


В ответ на вопросы тела...

Использование

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

Это может показаться пунтом, но реальный ответ - нет. Люди, знакомые с инструментами, похоже, используют либо наиболее знакомый им, либо тот, который на самом деле является правильным для этой работы. С учетом сказанного иногда вы хотите представить определенную читаемость, иногда уровень производительности, и когда вам нужно иметь достаточно высокий уровень, вам может понадобиться еще один инструмент, который поможет вам сделать более четкие абстракции.

Производительность

Существуют ли аналитические задачи, которые выполняются по существу (то есть более чем 2x) более эффективно в одном пакете по сравнению с другим.

Опять же, нет. data.table превосходит эффективность во всем, что он делает, где dplyr получает бремя ограниченности в некотором отношении базовому хранилищу данных и зарегистрированным обработчикам.

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

Когда dplyr имеет проблемы с производительностью с обратными концами, вы можете обойти их, зарегистрировав функцию для гибридной оценки или (в случае баз данных), обрабатывая сгенерированный запрос до выполнения.

Также см. принятый ответ когда он лучше, чем data.table?

39
ответ дан 17 нояб. '14 в 1:39
источник

Другие вопросы по меткам или Задайте вопрос