data.table vs dplyr: чи можна зробити щось добре, а інший не може чи погано?


758

Огляд

Я відносно знайомий data.table, не так вже й багато dplyr. Я прочитав кілька dplyrвіньєт та прикладів, які з’явились на SO, і поки що мої висновки:

  1. data.tableі dplyrвони порівнянні за швидкістю, за винятком випадків, коли існує багато (тобто> 10-100 К) груп, а також за інших обставин (див. орієнтири нижче)
  2. dplyr має більш доступний синтаксис
  3. dplyr конспекти (або заповіти) потенційних взаємодій з БД
  4. Існують деякі незначні відмінності функціональності (див. "Приклади / використання" нижче)

На мій погляд 2. не має великої ваги, тому що я досить добре знайомий з цим data.table, хоча я розумію, що для нових користувачів це буде великим фактором. Я хотів би уникнути аргументації щодо того, який є більш інтуїтивним, оскільки це не має значення для мого конкретного питання, заданого з точки зору когось уже знайомого data.table. Я також хотів би уникнути дискусії про те, як "більш інтуїтивно зрозумілий" призводить до більш швидкого аналізу (безумовно, правда, але знову ж таки, не те, що мене найбільше цікавить тут).

Питання

Що я хочу знати:

  1. Чи є аналітичні завдання, які набагато простіше кодувати тим чи іншим пакетом для людей, знайомих з пакунками (тобто, якась комбінація натискань клавіш, необхідна в порівнянні з необхідним рівнем езотерики, де менше кожного - це добре).
  2. Чи є аналітичні завдання, які виконуються суттєво (тобто більше ніж 2 рази) ефективніше в одному пакеті проти іншого.

Одне з останніх запитань про так змусило мене подумати про це трохи більше, тому що до цього моменту я не думав, 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 , зверніть увагу: це виглядає так, що він буде реалізований у 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))

9
Рішення, подібне до читання, - dplyrце:as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
Едді,

7
Для №1 dplyrі data.tableкоманди, і команди працюють над еталонами, тому відповідь буде в якийсь момент. # 2 (синтаксис) imO суворо помилково, але це явно заходить на територію думок, тому я голосую також за те, щоб закрити.
Едді

13
ну, знову ж таки, імО, набір проблем, які більш чітко виражені в, (d)plyrмає міру 0
Едді,

28
@BrodieG Єдина річ, яка насправді клопоче про мене dplyrі plyrщодо синтаксису, і в основному є основною причиною, чому мені не подобається їх синтаксис, - це те, що мені доведеться навчитися занадто багато (читати більше 1) додаткових функцій (з іменами, які все ще не майте сенсу для мене), пам’ятайте, що вони роблять, які аргументи вони приймають тощо. Це завжди було величезним відключенням для мене від філософії plyr.
eddi

43
@eddi [мова-в-щоку] - одне, що насправді заважає мені про синтаксис data.table - це те, що я повинен дізнатися, як взаємодіє занадто багато аргументів функції, і що означають загадкові ярлики (наприклад .SD). [серйозно] Я думаю, що це законні відмінності дизайну, які сподобаються різним людям
Хадлі

Відповіді:


532

Ми повинні охоплювати принаймні , ці аспекти , щоб забезпечити всебічний відповідь / порівняння (у довільному порядку важливості): Speed, Memory usage, Syntaxі Features.

Моя мета - висвітлити кожну з них якомога чіткіше з точки зору даних.table.

Примітка: якщо явно не вказано інше, посилаючись на dplyr, ми посилаємось на інтерфейс data.frame dplyr, внутрішні компоненти якого знаходяться в C ++ за допомогою Rcpp.


Синтаксис даних.table відповідає своїй формі - DT[i, j, by]. Зберігати i, jа byразом - за задумом. Зберігаючи пов'язані операції разом, це дозволяє легко оптимізувати операції для швидкості та важливішого використання пам'яті , а також надає деякі потужні функції , все зберігаючи послідовність у синтаксисі.

1. Швидкість

До питання вже додано декілька орієнтирів (хоча в основному щодо операцій з групуванням), де вже показано, що data.table стає швидше dplyr, оскільки кількість груп та / або рядків до групи збільшується, включаючи еталони від Matt за групуванням від 10 мільйонів до 2 мільярди рядків (100 Гб оперативної пам’яті) на 100 - 10 мільйонів груп та різні групування стовпців, що також порівнює pandas. Дивіться також оновлені орієнтири , які включають Sparkі pydatatableтакож.

Щодо еталонів, було б чудово охопити і наступні аспекти:

  • Групування операцій, що включають підмножину рядків - тобто DT[x > val, sum(y), by = z]операції типу.

  • Визначення інших операцій, таких як оновлення та приєднання .

  • Також орієнтир пам’яті пам’яті для кожної операції, крім часу виконання.

2. Використання пам'яті

  1. Операції, що включають filter()або slice()в dplyr, можуть бути неефективними в пам'яті (і в data.frames, і в data.tables). Дивіться цю публікацію .

    Зауважте, що коментар Хедлі говорить про швидкість (цей диплир для нього вдосталь швидкий), тоді як головна проблема тут - пам'ять .

  2. Інтерфейс 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()експорту інтерфейсу data.table dplyr слід уникати майже всіх копій. Тож ті, хто віддає перевагу синтаксису dplyr, можуть використовувати його з data.tables.

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

  3. Об’єднайте під час приєднання:

    Припустимо, у вас є два 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) сукупність 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ви повинні приєднатися і агрегат або агрегат першої , а потім приєднатися , ні один з яких є ефективними, з точки зору пам'яті (що , в свою чергу , призводить до швидкості).

  4. Оновлення та приєднання:

    Розглянемо код, наведений нижче:

    DT1[DT2, col := i.mul]

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

    Перевірте цю публікацію на предмет справжнього сценарію використання.

Підсумовуючи, важливо усвідомити, що кожен шматочок оптимізації має значення. Як сказала б Грейс Хоппер , пам'ятай про свої наносекунди !

3. Синтаксис

Давайте тепер розглянемо синтаксис . Хедлі прокоментував тут :

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

Я вважаю це зауваження безглуздим, оскільки воно дуже суб'єктивне. Що ми можемо спробувати, це протиставити послідовність у синтаксисі . Ми порівняємо синтаксис data.table та dplyr одночасно.

Ми будемо працювати з наведеними нижче даними:

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

    # 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()в першому випадку (це не здається мені очевидним), але моя думка полягає в тому, що ми не повинні цього робити.

  2. Агрегація / оновлення у кількох стовпцях

    # 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))
    • У випадку (а) коди є більш-менш еквівалентними. data.table використовує звичну базову функцію lapply(), тоді як dplyrвводить *_each()поряд з купою функцій для funs().

    • data.table’s :=вимагає надання імен стовпців, тоді як dplyr генерує їх автоматично.

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

    • У випадку, якщо (c), dplyr поверне n()стільки ж разів, скільки стовпців, а не один раз. У data.table все, що нам потрібно зробити, це повернути список у j. Кожен елемент списку стане результатом у стовпці. Отже, ми знову можемо використовувати звичну базову функцію c()для приєднання .Nдо a, listяка повертає a list.

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

    Вам потрібно буде вивчити лише спеціальні змінні - .Nі .SDпринаймні. Еквівалентом у dplyr є n()і.

  3. Приєднується

    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.frames, перш ніж приєднатися, як показано вище. В іншому випадку ви зможете матеріалізувати з'єднання непотрібними стовпцями, щоб видалити їх пізніше, а це неефективно.

    • data.tables можуть агрегуватися під час приєднання (3), а також оновлюватися під час приєднання (4), використовуючи by = .EACHIфункцію. Навіщо матеріали використовувати весь результат з'єднання, щоб додати / оновити лише кілька стовпців?

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

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

    • data.table має allow.cartesian = TRUEаргумент для захисту від випадкових недійсних приєднань.

Ще раз, синтаксис узгоджується DT[i, j, by]з додатковими аргументами, що дозволяють додатково контролювати вихід.

  1. do()...

    підсумок dplyr спеціально розроблений для функцій, які повертають єдине значення. Якщо ваша функція повертає кілька / неоднакових значень, вам доведеться вдатися 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не турбуючись про ці речі.

Подивіться на це так , питання і цей . Цікаво, чи можна було б відповісти прямо, використовуючи синтаксис dplyr ...

Підводячи підсумок, я особливо виділив декілька випадків, коли синтаксис dplyr або неефективний, обмежений або не може зробити операції простими. Це особливо пов'язано з тим, що data.table отримує досить сильний люфт щодо синтаксису "важче читати / вивчати" (як той, який вставлений / пов'язаний вище). Більшість дописів, які висвітлюють dplyr, розповідають про найпростіші операції. І це чудово. Але важливо також усвідомити його синтаксис та обмеження в характеристиках, і я ще не побачу публікацію в ньому.

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

Але також слід врахувати кількість функцій, яких не вистачає dplyr порівняно з data.table.

4. Особливості

Я вказав на більшість особливостей тут, а також у цій публікації. На додачу:

  • fread - швидкий зчитувач файлів доступний вже давно.

  • fwrite - тепер доступний паралельний швидкий запис файлів. Дивіться цю публікацію для детального пояснення щодо впровадження та № 1664 для відстеження подальших розробок.

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

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

  • Численні переваги в data.table приєднується (для швидкості / ефективності пам'яті та синтаксису), згаданих вище.

  • <=, <, >, >=Об'єднання без еквівалентів : Дозволяє приєднуватися до інших операторів, а також до всіх інших переваг приєднання даних.

  • Нещодавно в data.table було реалізовано з'єднання діапазонів, що перекриваються. Перегляньте цю публікацію для ознайомлення з орієнтирами.

  • setorder() функція в data.table, яка дозволяє дуже швидко переупорядкувати data.tables за посиланням.

  • dplyr надає інтерфейс до баз даних, використовуючи той самий синтаксис, якого data.table зараз не має.

  • data.tableзабезпечує більш еквіваленти множинних операцій (написаний Ян Горецький) - fsetdiff, fintersect, funionі fsetequalз додатковим allаргументом (як в SQL).

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


Нарешті:

  • У базах даних - немає жодної причини, чому data.table не може забезпечити подібний інтерфейс, але це зараз не є пріоритетним. Це може зіткнутися, якщо користувачам дуже сподобається ця функція .. не впевнений.

  • Про паралелізм - Все важко, поки хтось не вийде наперед і не зробить це. Звичайно, це потребуватиме зусиль (будучи безпечними нитками).

    • В даний час досягається прогрес (у версії 1.9.9.7) у напрямку паралельного використання відомих за часом трудомістких частин для використання додаткового збільшення продуктивності OpenMP.

9
@bluefeet: Я не думаю, що ти зробив решті нам чудову послугу, перемістивши цю дискусію в чат. У мене було враження, що Арун був одним із розробників, і це, можливо, призвело б до корисних відомостей.
IRTFM

2
Коли я пішов в чат за вашим посиланням, виявилося, що весь матеріал після коментаря, що починається з "Ви повинні використовувати фільтр" .. зник. Я щось бракую про механізм SO-чату?
IRTFM

6
Я думаю, що кожен, де ви використовуєте завдання за посиланням ( :=), dplyrеквівалент повинен також використовуватись <-як DF <- DF %>% mutate...замість простоDF %>% mutate...
Девід Аренбург

4
Щодо синтаксису. Я вважаю, що dplyrможе бути простішим для користувачів, які звикли до plyrсинтаксису, але data.tableможе бути простішим для користувачів, які звикали запитувати такі синтаксиси мов, як SQLі реляційна алгебра за ним, що стосується табличного перетворення даних. @Arun, ви повинні зауважити, що встановлені оператори дуже легко виконувати data.tableфункцію обгортання, і, звичайно, приносить значну швидкість.
jangorecki

9
Я читав цю публікацію стільки разів, і це мені дуже допомогло в розумінні data.table та можливості його кращого використання. Я, в більшості випадків, віддаю перевагу таблиці даних над dplyr або пандами або PL / pgSQL. Однак я не міг перестати думати, як це висловити. Синтаксис НЕ просто, ясно або багатослівним. Насправді, навіть після того, як я багато використовував data.table, я часто все ще намагаюся зрозуміти власний код, я писав буквально тиждень тому. Це життєвий приклад мови лише для письма. en.wikipedia.org/wiki/Write-only_language Отже, сподіваємось, одного разу ми зможемо використовувати dplyr на data.table.
Ufos

385

Ось моя спроба вичерпної відповіді з точки зору dplyr, слідуючи широкому контуру відповіді Аруна (але дещо переставленої на основі різних пріоритетів).

Синтаксис

Синтаксису є деяка суб'єктивність, але я витримую своє твердження про те, що стислість даних.tatable ускладнює навчання та важче читати. Почасти це тому, що dplyr вирішує набагато простішу проблему!

Одне дуже важливе, що робить для вас dplyr, це те, що воно обмежує ваші варіанти. Я стверджую, що більшість проблем з однією таблицею можна вирішити лише за допомогою п’яти ключових дієслів, які фільтрують, вибирають, мутують, упорядковують та узагальнюють разом із прислівником «за групою». Це обмеження - це велика допомога, коли ви навчаєтесь маніпулюванню даними, оскільки допомагає впорядкувати своє мислення щодо проблеми. У dplyr кожне з цих дієслів відображається на одну функцію. Кожна функція виконує одну роботу, і її легко зрозуміти ізольовано.

Ви створюєте складність, проводячи ці прості операції разом з %>%. Ось приклад з одного з дописів, з якими пов'язаний Арун :

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

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

Ось еквівалентний код data.table:

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

Дотримуватися цього коду важче, якщо ви вже не знайомі з data.table. (Я також не міг зрозуміти, як відступати повторення [ так, як це добре виглядає моїм оком). Особисто, коли я дивлюсь на код, який я написав 6 місяців тому, це схоже на перегляд коду, написаного незнайомцем, тож я віддаю перевагу прямому тексту, якщо багатослівний, код.

Ще два незначні фактори, які, на мою думку, трохи знижують читабельність:

  • Оскільки майже для кожної операції з таблицею даних використовується [додатковий контекст, щоб зрозуміти, що відбувається. Наприклад, це x[y] об'єднання двох таблиць даних або вилучення стовпців з фрейму даних? Це лише невелика проблема, оскільки в добре написаному коді імена змінних повинні підказувати, що відбувається.

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

Мені також подобається, що труба не обмежується лише одним пакетом. Почати можна, прив’язавши свої дані до tidyr , і закінчити сюжет на ggvis . І ви не обмежуєтесь пакетами, які я пишу - кожен може написати функцію, яка утворює безшовну частину каналу обробки даних. Насправді я віддаю перевагу попередньому коду data.table, переписаному на %>%:

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

І ідея прошивки %>%не обмежується лише кадрами даних і легко узагальнюється в інших контекстах: інтерактивна веб-графіка , веб-скребкування , суть , контракти на виконання , ...)

Пам'ять та продуктивність

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

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

Все, що сказано, продуктивність dplyr покращиться в довгостроковій перспективі. Ми неодмінно реалізуємо деякі чудові ідеї data.table, такі як упорядкування в radix та використання того ж індексу для приєднання та фільтрів. Ми також працюємо над паралелізацією, щоб ми могли скористатися декількома ядрами.

Особливості

Кілька речей, над якими ми плануємо працювати у 2015 році:

  • readrпакет, щоб зробити його легко отримати файли з диска , так і в пам'яті, аналогічно fread().

  • Більш гнучкі приєднання, включаючи підтримку не-equi-приєднань.

  • Більш гнучка групування, як зразки завантажувального завантаження, збірки та інше

Я також вкладаю час на вдосконалення роз'ємів бази даних R , можливість спілкування з веб-apis та полегшення скребки HTML-сторінок .


27
Лише бічна зауваження, я погоджуюся з багатьма вашими аргументами (хоча я сам віддаю перевагу data.tableсинтаксису), але ви можете легко використовувати %>%для того, щоб передаватиdata.table операції, якщо вам не подобається [стиль. %>%не є специфічним dplyr, скоріше виходить з окремого пакету (який, можливо, ви теж є співавтором), тому я не впевнений, що я розумію, що ви намагаєтесь сказати в більшості своїх абзаців синтаксису .
Девід Аренбург

11
@DavidArenburg хороший момент. Я переписав синтаксис, щоб, сподіваючись, зробити більш зрозумілим, які мої основні моменти, і виділити, що ви можете використовувати %>%з data.table
hadley

5
Дякую Хедлі, це корисна перспектива. Повторне відступ я зазвичай роблю DT[\n\texpression\n][\texpression\n]( суть ), що насправді працює досить добре. Я тримаю відповідь Аруна як відповідь, оскільки він прямо відповідає на мої конкретні запитання, які стосуються не стільки доступності синтаксису, але я вважаю, що це хороша відповідь для людей, які намагаються отримати загальне відчуття відмінностей / спільності міжdplyr і data.table.
BrodieG

33
Навіщо працювати на швидкій читанні, коли вона вже є fread()? Не вдалося б витратити час на поліпшення fread () або роботу над іншими (нерозвиненими) речами?
EDi

10
API data.tableзаснований на масовому зловживанні []позначеннями. Це його найбільша сила і найбільша слабкість.
Пол

65

У відповідь на назву питання ...

dplyr однозначно робить речі, які data.tableне може.

Ваша точка №3

dplyr тези (або будуть) потенційні взаємодії з БД

це пряма відповідь на ваше власне запитання, але не піднятий на достатньо високий рівень. dplyrє справді розширюваним переднім кінцем до декількох механізмів зберігання даних, де data.tableце - розширення до одного.

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

Ви ніколи (я сподіваюся) не побачите день, який data.tableнамагається перевести ваші запити для створення операторів SQL, які працюють з дисковими або мережевими сховищами даних.

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

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


У відповідь на внутрішні запитання ...

Використання

Чи є аналітичні завдання, які набагато простіше кодувати тим чи іншим пакетом для людей, знайомих з пакунками (тобто, якась комбінація натискань на клавіші, необхідна порівняно з необхідним рівнем езотерики, де менше кожного - це добре).

Це може здатися мовкою, але справжня відповідь - ні. Люди, знайомі з інструментами, здається, використовують або найвідоміший для них, або той, який насправді є правильним для роботи. Зважаючи на це, іноді ви хочете представити певну читабельність, іноді рівень продуктивності, і коли вам потрібен достатньо високий рівень обох, вам може просто знадобитися інший інструмент, щоб разом з тим, що вам вже потрібно зробити більш чіткими абстракціями. .

Продуктивність

Чи є аналітичні завдання, які виконуються суттєво (тобто більше ніж 2 рази) ефективніше в одному пакеті проти іншого.

Знову ні. data.tableдосягає ефективності у всьому, що робиться там, де dplyrнакладається тягар обмеження в деяких відношеннях до базового сховища даних та зареєстрованих обробників.

Це означає , що при запуску в питання продуктивності з data.tableви можете бути впевнені , що у функції запиту та , якщо він є на самому ділі є вузьким місцем з , data.tableто ви виграли собі радість подачі звіту. Це також справедливо, коли dplyrвикористовується data.tableяк бек-енд; ви можете побачити деякі накладні звідтиdplyr але шанси - це ваш запит.

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

Також дивіться прийняту відповідь на те, коли plyr краще, ніж data.table?


3
Не можете обміняти таблицю даних із таблицею даних tbl_dt? Чому б просто не отримати найкраще з обох світів?
aaa90210

22
Ви забудете згадати зворотне твердження "data.table, безумовно, робить те, що dplyr не може", що також відповідає дійсності.
jangorecki

25
Відповідь Аруна це добре пояснює. Найважливішим (з точки зору продуктивності) буде fread, оновлення за посиланням, прокатні з'єднання, з'єднання, що перекриваються. Я вважаю, що немає жодного пакету (не лише dplyr), який би міг конкурувати з цими можливостями. Гарний приклад може бути останнім слайдом з цієї презентації.
jangorecki

15
Загалом, data.table тому я все ще використовую R. Інакше я б використовував панди. Це навіть краще / швидше, ніж панди.
Марбель

8
Мені подобається data.table через його простоту та схожість із структурою синтаксису SQL. Моя робота передбачає щоденний дуже інтенсивний аналіз даних та графіки для статистичного моделювання, і мені дуже потрібен інструмент, простий для виконання складних речей. Тепер я можу скоротити свій набір інструментів до лише data.table для даних та решітки для графіка у своїй щоденній роботі. Наведіть приклад, що я навіть можу робити такі операції: $ DT [group == 1, y_hat: = predict (fit1, data = .SD),] $, що є дійсно акуратним, і я вважаю це великою перевагою від SQL в класичне R середовище.
xappppp
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.