Data.table vs dplyr: может ли что-то сделать хорошо, а другое плохо или плохо?
обзор
Я относительно знаком с data.table
, не так много с dplyr
. Я прочитал некоторые виньетки и примеры dplyr
на SO, и пока мои выводы таковы:
-
data.table
иdplyr
сопоставимы по скорости, за исключением случаев, когда существует много (то есть> 10-100 КБ) групп, и в некоторых других обстоятельствах (см. контрольные показатели ниже) -
dplyr
более доступный синтаксис -
dplyr
рефераты (или будут) потенциальные взаимодействия с БД - Есть некоторые незначительные функциональные различия (см. "Примеры/Использование" ниже)
На мой взгляд, 2. не имеет большого веса, потому что я достаточно хорошо знаком с этим data.table
, хотя я понимаю, что для пользователей, data.table
знакомых с data.table
, это будет большой фактор. Я хотел бы избежать спора, который является более интуитивным, поскольку он не имеет отношения к моему конкретному вопросу, data.table
с точки зрения кого-то, уже знакомого с data.table
. Я также хотел бы избежать дискуссии о том, как "более интуитивный" ведет к более быстрому анализу (конечно, правда, но опять же, не то, что меня больше всего интересует здесь).
Вопрос
То, что я хочу знать, это:
- Существуют ли аналитические задачи, которые намного проще кодировать с тем или иным пакетом для людей, знакомых с этими пакетами (т.е. Требуется некоторая комбинация нажатий клавиш в сравнении с требуемым уровнем эзотерики, где меньше каждого из них - хорошая вещь).
- Существуют ли аналитические задачи, которые выполняются существенно (т.е. более чем в 2 раза) более эффективно в одном пакете по сравнению с другим.
Один недавний вопрос 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
. Тем не менее, хорошие решения для data.table
также довольно хороши (спасибо Жан-Роберту, Аруну, и обратите внимание, здесь я предпочел одно утверждение перед строго самым оптимальным решением):
setDT(dat)[,
.SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)],
by=list(id, job)
]
Синтаксис последнего может показаться очень эзотерическим, но на самом деле он довольно прост, если вы привыкли к data.table
(т.е. не используете некоторые из более эзотерических приемов).
В идеале, я хотел бы увидеть несколько хороших примеров, где dplyr
или data.table
значительно более лаконичны или 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
, безусловно, возможно, просто требует тщательного, замена/цитирование, и т.д., по крайней мере, насколько мне известно )
- Я запустил свои собственные тесты и обнаружил, что оба пакета сопоставимы в анализе стиля "разделить и применить объединение", за исключением случаев, когда существует очень большое количество групп (> 100
data.table
), в которыхdata.table
становится значительно быстрее. - @Arun провел несколько тестов для объединений, показав, что
data.table
масштабируется лучше, чемdplyr
с увеличением числа групп (обновлено сdplyr
последних улучшений в обоих пакетах и последней версии R). Кроме того, тест при попытке получить уникальные значения имеетdata.table
~ в 6 раз быстрее. - (Неподтвержденный) имеет
data.table
75% быстрее на больших версиях группы /apply/sort, в то время какdplyr
был на 40% быстрее на меньших ( еще один вопрос SO из комментариев, спасибо данам). - Мэтт, главный автор
data.table
, имеет протестированные группировка операций поdata.table
,dplyr
и питонаpandas
на срок до 2 миллиардов строк (~ 100GB в оперативной памяти). - В более старом тесте для групп из
data.table
~ в 8 раз быстрее
Данные
Это первый пример, который я показал в разделе вопросов.
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))
Нам необходимо охватить, по крайней мере, следующие аспекты, чтобы предоставить исчерпывающий ответ/сравнение (без особой важности): Speed
, Memory usage
, Syntax
и Features
.
Мое намерение состоит в том, чтобы охватить каждый из них как можно более четко с точки зрения таблицы данных.
Примечание: если явно не указано иное, обращаясь к dplyr, мы ссылаемся на интерфейс dplyr data.frame, внутреннее содержимое которого находится в C++ с использованием Rcpp.
Синтаксис data.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. Использование памяти
-
Операции, включающие
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, которая предоставит пользователю обе возможности. Например, если желательно не изменять входной data.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, включая (sub) -assignment по ссылке.
-
Агрегировать при присоединении:
Предположим, у вас есть две таблицы данных:
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
вам придется сначала объединять и агрегировать или агрегировать, а затем объединять, но ни один из них не является настолько эффективным с точки зрения памяти (что, в свою очередь, приводит к скорости). -
-
Обновление и присоединения:
Рассмотрим код data.table, показанный ниже:
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)
-
Основные операции агрегации/обновления.
# 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 компактен и довольно многословен. Вещи более или менее эквивалентны в случае (а).
-
В случае (b) мы должны были использовать
filter()
в dplyr при суммировании. Но при обновлении нам пришлось переместить логику вmutate()
. Однако в data.table мы выражаем обе операции с одной и той же логикой - работаем со строками, гдеx > 2
, но в первом случае получаемsum(y)
, тогда как во втором случае обновляем эти строки дляy
с его накопленной суммой.Это то, что мы имеем в виду, когда говорим, что форма
DT[i, j, by]
непротиворечива. -
Аналогично в случае (c), когда у нас есть условие
if-else
, мы можем выразить логику "как есть" как в data.table, так и в dplyr. Однако, если мы хотим вернуть только те строки, в которых выполняется условиеif
и пропустить иначе, мы не можем напрямую использовать summazesummarise()
(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))
-
В случае (а) коды более или менее эквивалентны. data.table использует знакомую базовую функцию
lapply()
, тогда какdplyr
вводит*_each()
вместе с*_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()
которая похожа на base Р. -
Однако соединения dplyr делают именно это. Ничего более. Не меньше.
-
data.tables может выбирать столбцы при присоединении (2), и в dplyr вам нужно сначала
select()
на обоих data.frames, прежде чем присоединиться, как показано выше. В противном случае вы бы материализовали объединение ненужными столбцами, чтобы потом удалить их, а это неэффективно. -
data.tables может объединяться при присоединении (3), а также обновляться при присоединении (4), используя функцию
by =.EACHI
. Зачем использовать весь результат объединения, чтобы добавить/обновить всего несколько столбцов? -
data.table может катить соединения (5) - крен вперед, LOCF, крен назад, NOCB, ближайший.
-
data.table также имеет аргумент mult
mult =
который выбирает первое, последнее или все совпадения (6). -
data.table имеет аргумент
allow.cartesian = TRUE
для защиты от случайных недопустимых объединений.
-
Еще раз, синтаксис согласуется с
DT[i, j, by]
с дополнительными аргументами, позволяющими дополнительно контролировать вывод.
-
do()
...dplyr summaze специально разработан для функций, которые возвращают одно значение. Если ваша функция возвращает несколько/неравных значений, вам придется прибегнуть к
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 по сравнению с таблицей данных.
4. Особенности
Я указал на большинство функций здесь, а также в этом посте. К тому же:
-
fread - быстрый файловый ридер уже давно доступен.
-
fwrite - теперь доступен параллельный быстрый файловый редактор. См. Этот пост для подробного объяснения реализации и № 1664 для отслеживания дальнейших событий.
-
Автоматическая индексация - еще одна удобная функция для оптимизации базового синтаксиса R как таковая.
-
Специальная группировка:
dplyr
автоматически сортирует результаты, группируя переменные во времяdplyr
summarise()
, что может быть не всегда желательно. -
Многочисленные преимущества в соединениях data.table (для скорости/эффективности памяти и синтаксиса), упомянутые выше.
-
Неэквивалентные объединения: Позволяет объединениям использовать другие операторы
<=, <, >, >=
вместе со всеми другими преимуществами соединений 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 не может предоставить аналогичный интерфейс, но сейчас это не является приоритетом. Это может быть усилено, если пользователям очень понравится эта функция... не уверен.
-
На параллелизме - все сложно, пока кто-то не пойдет и не сделает это. Конечно, это потребует усилий (будучи потокобезопасным).
- В настоящее время достигнут прогресс (в версии v1.9.7) в направлении распараллеливания известных трудоемких частей для увеличения производительности с использованием
OpenMP
.
- В настоящее время достигнут прогресс (в версии v1.9.7) в направлении распараллеливания известных трудоемких частей для увеличения производительности с использованием
Здесь моя попытка получить исчерпывающий ответ с точки зрения dplyr, следуя широкому плану ответа Аруна (но несколько перестроенного на основе разных приоритетов).
Синтаксис
В синтаксисе есть некоторая субъективность, но я придерживаюсь своего утверждения, что сжатие data.table усложняет изучение и чтение. Это отчасти потому, что 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, такие как упорядочение по осям и использование того же индекса для объединений и фильтров. Мы также работаем над распараллеливанием, чтобы мы могли использовать преимущества нескольких ядер.
Характеристики
Несколько вещей, над которыми мы планируем работать в 2015 году:
-
пакет
readr
, облегчающий получение файлов с диска и в память, аналогичноfread()
. -
Более гибкие объединения, в том числе поддержка неэквивалентных.
-
Более гибкая группировка, такая как образцы начальной загрузки, накопительные пакеты и многое другое
Я также вкладываю время в улучшение коннекторов базы данных R, в возможность общения с веб-интерфейсом API и упрощение очистки HTML-страниц.
В прямом ответе на заголовок вопроса...
dplyr
определенно делает то, что data.table
не может.
Ваша точка № 3
dplyr рефераты (или будут) потенциальные взаимодействия с БД
является прямым ответом на ваш собственный вопрос, но не повышен до достаточно высокого уровня. dplyr
действительно расширяемый интерфейс для нескольких механизмов хранения данных, где data.table
является расширением для одного.
Посмотрите на dplyr
как на внутренний интерфейс, независимый от всех целей, использующих один и тот же грамматик, где вы можете по желанию расширять цели и обработчики. data.table
, с dplyr
зрения dplyr
, является одной из этих целей.
Вы никогда (я надеюсь) никогда не увидите, чтобы data.table
пытался перевести ваши запросы для создания операторов SQL, которые работают с дисковыми или сетевыми хранилищами данных.
dplyr
возможно, может делать то, что data.table
не будет или не может делать так же хорошо.
Основываясь на дизайне работы в памяти, data.table
может иметь гораздо более сложное время для параллельной обработки запросов, чем dplyr
.
В ответ на вопросы в теле...
использование
Существуют ли аналитические задачи, которые намного проще кодировать с тем или иным пакетом для людей, знакомых с этими пакетами (т.е. Требуется некоторая комбинация нажатий клавиш в сравнении с требуемым уровнем эзотерики, где меньше каждого из них - хорошая вещь).
Это может показаться пунтом, но реальный ответ - нет. Люди, знакомые с инструментами, похоже, используют либо тот, который им наиболее знаком, либо тот, который на самом деле подходит для данной работы. С учетом вышесказанного, иногда вы хотите представить определенную читабельность, иногда уровень производительности, а когда вам нужен достаточно высокий уровень обоих, вам может просто понадобиться другой инструмент, чтобы идти вместе с тем, что у вас уже есть, чтобы сделать более четкие абстракции,
Спектакль
Существуют ли аналитические задачи, которые выполняются существенно (т.е. более чем в 2 раза) более эффективно в одном пакете по сравнению с другим.
Опять нет. data.table
отличается эффективностью во всем, что он делает, когда dplyr
получает бремя ограничения в некоторых отношениях базового хранилища данных и зарегистрированных обработчиков.
Это означает, что когда вы сталкиваетесь с проблемой производительности с data.table
вы можете быть уверены, что она есть в вашей функции запроса, и если это на самом деле узкое место в data.table
то вы выиграли радость подачи отчета. Это также верно, когда dplyr
использует data.table
в качестве серверной части; Вы можете увидеть некоторые издержки от dplyr
но есть вероятность, что это ваш запрос.
Когда у dplyr
есть проблемы с производительностью с dplyr
вы можете обойти их, зарегистрировав функцию для гибридной оценки или (в случае баз данных) манипулируя сгенерированным запросом перед выполнением.
Также смотрите принятый ответ, когда plyr лучше, чем data.table?
Посмотрите другие вопросы по меткам r data.table dplyr или Задайте вопрос