Если я использую синтаксис dplyr поверх datatable , получу ли я все преимущества скорости от datatable, по-прежнему используя синтаксис dplyr? Другими словами, могу ли я неправильно использовать datatable, если я запрашиваю его с синтаксисом dplyr? Или мне нужно использовать чистый синтаксис datatable, чтобы использовать всю его мощь.
Заранее благодарю за любой совет. Пример кода:
library(data.table)
library(dplyr)
diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut)
diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count))
Полученные результаты:
# cut AvgPrice MedianPrice Count
# 1 Ideal 3457.542 1810.0 21551
# 2 Premium 4584.258 3185.0 13791
# 3 Very Good 3981.760 2648.0 12082
# 4 Good 3928.864 3050.5 4906
Вот эквивалентность, которую я придумал. Не уверен, что это соответствует хорошей практике DT. Но мне интересно, действительно ли код более эффективен, чем синтаксис dplyr за сценой:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
r
data.table
dplyr
Полимераза
источник
источник
dplyr
методы для таблиц данных, но в таблице данных также есть свои собственные сопоставимые методыdplyr
используется дляdata.frame
s и соответствующихdata.table
s, см. Здесь (и ссылки в нем).Ответы:
Нет однозначного / простого ответа, потому что философия обоих этих пакетов различается в определенных аспектах. Так что некоторые компромиссы неизбежны. Вот некоторые из проблем, которые вам, возможно, придется решить / рассмотреть.
Операции с участием
i
(==filter()
иslice()
в dplyr)Допустим,
DT
например, 10 столбцов. Рассмотрим эти выражения data.table:DT[a > 1, .N] ## --- (1) DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1) дает количество строк в
DT
столбце wherea > 1
. (2) возвращаетmean(b)
сгруппированные поc,d
для того же выражения вi
as (1).Обычно используемые
dplyr
выражения:DT %>% filter(a > 1) %>% summarise(n()) ## --- (3) DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
Очевидно, что коды data.table короче. Кроме того, они более эффективны с точки зрения памяти 1 . Зачем? Поскольку и в (3), и в (4) сначала
filter()
возвращает строки для всех 10 столбцов , тогда как в (3) нам просто нужно количество строк, а в (4) нам просто нужны столбцыb, c, d
для последовательных операций. Чтобы преодолеть это, мы должныselect()
априори столбцы:DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5) DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)
Обратите внимание, что в (5) и (6) мы по-прежнему подмножество столбцов,
a
которые нам не нужны. Но я не знаю, как этого избежать. Если бы уfilter()
функции был аргумент для выбора возвращаемых столбцов, мы могли бы избежать этой проблемы, но тогда функция не будет выполнять только одну задачу (что также является выбором дизайна dplyr).Подзадача по ссылке
Например, в data.table вы можете:
DT[a %in% some_vals, a := NA]
который обновляет столбец
a
по ссылке только для тех строк, которые удовлетворяют условию. На данный момент dplyr глубоко копирует всю таблицу data.table внутри, чтобы добавить новый столбец. @BrodieG уже упоминал об этом в своем ответе.Но глубокая копия может быть заменена мелкой копией при реализации FR # 617 . Также актуально: dplyr: FR # 614 . Обратите внимание, что изменяемый столбец всегда будет скопирован (поэтому он будет работать медленнее / с меньшим потреблением памяти). Обновить столбцы по ссылке не будет.
Прочие функции
В data.table вы можете агрегировать при объединении, и это более понятно для понимания и эффективно с точки зрения памяти, поскольку промежуточный результат соединения никогда не материализуется. Посмотрите этот пост для примера. Вы не можете (в настоящий момент?) Сделать это, используя синтаксис dplyr data.table / data.frame.
Функция скользящих соединений data.table также не поддерживается в синтаксисе dplyr.
Недавно мы реализовали перекрывающиеся соединения в data.table для соединения по диапазонам интервалов ( вот пример ), которые
foverlaps()
на данный момент являются отдельной функцией и поэтому могут использоваться с операторами конвейера (magrittr / pipeR? - сам никогда не пробовал).Но в конечном итоге наша цель - интегрировать его,
[.data.table
чтобы мы могли использовать другие функции, такие как группировка, агрегирование при присоединении и т. Д., Которые будут иметь те же ограничения, что описаны выше.Начиная с 1.9.4, data.table реализует автоматическое индексирование с использованием вторичных ключей для быстрого двоичного поиска на основе подмножеств с обычным синтаксисом R. Пример:
DT[x == 1]
иDT[x %in% some_vals]
автоматически создаст индекс при первом запуске, который затем будет использоваться для последовательных подмножеств из одного столбца в быстрое подмножество с использованием двоичного поиска. Эта функция будет развиваться и дальше. Проверьте эту суть, чтобы получить краткий обзор этой функции.От способа
filter()
реализуется для data.tables, не воспользоваться этой функцией.Особенностью dplyr является то, что он также предоставляет интерфейс для баз данных с использованием того же синтаксиса, которого нет в data.table на данный момент.
Итак, вам придется взвесить эти (и, возможно, другие моменты) и решить, исходя из того, приемлемы ли для вас эти компромиссы.
HTH
(1) Обратите внимание, что эффективность использования памяти напрямую влияет на скорость (особенно при увеличении объема данных), поскольку узким местом в большинстве случаев является перемещение данных из основной памяти в кеш (и максимальное использование данных в кеше - уменьшите количество промахов в кеше). - чтобы уменьшить доступ к основной памяти). Не будем вдаваться в подробности.
источник
filter()
плюс,summarise()
используя тот же подход, который dplyr использует для SQL, то есть создание выражения и последующее выполнение только один раз по запросу. Маловероятно, что это будет реализовано в ближайшем будущем, потому что dplyr для меня достаточно быстр, а реализовать планировщик / оптимизатор запросов относительно сложно.Просто попробуйте.
library(rbenchmark) library(dplyr) library(data.table) benchmark( dplyr = diamondsDT %>% filter(cut != "Fair") %>% group_by(cut) %>% summarize(AvgPrice = mean(price), MedianPrice = as.numeric(median(price)), Count = n()) %>% arrange(desc(Count)), data.table = diamondsDT[cut != "Fair", list(AvgPrice = mean(price), MedianPrice = as.numeric(median(price)), Count = .N), by = cut][order(-Count)])[1:4]
По этой проблеме кажется, что data.table в 2,4 раза быстрее, чем dplyr с использованием data.table:
test replications elapsed relative 2 data.table 100 2.39 1.000 1 dplyr 100 5.77 2.414
Отредактировано на основе комментария Полимеразы.
источник
microbenchmark
пакет, я обнаружил, что выполнениеdplyr
кода OP в исходной (фрейм данных) версииdiamonds
заняло среднее время 0,012 секунды, в то время как среднее время потребовалось 0,024 секунды после преобразованияdiamonds
в таблицу данных. Выполнениеdata.table
кода Г. Гротендика заняло 0,013 секунды. По крайней мере, в моей системе он выглядит примерно так жеdplyr
иdata.table
имеет примерно такую же производительность. Но почему будетdplyr
медленнее, если фрейм данных сначала преобразуется в таблицу данных?Чтобы ответить на ваши вопросы:
data.table
data.table
синтаксисомВо многих случаях это будет приемлемым компромиссом для тех, кому нужен
dplyr
синтаксис, хотя он, возможно, будет медленнее, чемdplyr
с простыми кадрами данных.Похоже, что одним важным фактором является то
dplyr
, чтоdata.table
по умолчанию при группировании будет скопировано . Рассмотрим (используя микробенчмарк):Unit: microseconds expr min lq median diamondsDT[, mean(price), by = cut] 3395.753 4039.5700 4543.594 diamondsDT[cut != "Fair"] 12315.943 15460.1055 16383.738 diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price)) 9210.670 11486.7530 12994.073 diamondsDT %>% filter(cut != "Fair") 13003.878 15897.5310 17032.609
Скорость фильтрации сопоставима, а вот группировка - нет. Я считаю, что виновата эта строка в
dplyr:::grouped_dt
:if (copy) { data <- data.table::copy(data) }
где по
copy
умолчаниюTRUE
(и не может быть легко изменен на FALSE, как я вижу). Скорее всего, это не составляет 100% разницы, но одни только общие накладные расходы на то, что,diamonds
скорее всего, не являются полной разницей.Проблема в том, что для получения единообразной грамматики
dplyr
группировка выполняется в два этапа. Сначала он устанавливает ключи на копии исходной таблицы данных, которые соответствуют группам, и только позже группирует.data.table
просто выделяет память для самой большой группы результатов, которая в данном случае представляет собой всего одну строку, так что это имеет большое значение в том, сколько памяти необходимо выделить.FYI, если кому-то интересно, я нашел это с помощью
treeprof
(install_github("brodieg/treeprof")
), экспериментального (и все еще очень альфа) просмотрщика дерева дляRprof
вывода:Обратите внимание, что приведенное выше в настоящее время работает только на Mac AFAIK. Кроме того, к сожалению, такие
Rprof
вызовы записываютсяpackagename::funname
как анонимные, так что на самом деле это могут быть любые и всеdatatable::
вызовы внутриgrouped_dt
, которые ответственны за это, но при быстром тестировании это выглядело какdatatable::copy
большое.Тем не менее, вы можете быстро увидеть, что накладных расходов вокруг
[.data.table
вызова не так много , но есть также совершенно отдельная ветвь для группировки.РЕДАКТИРОВАТЬ : для подтверждения копирования:
> tracemem(diamondsDT) [1] "<0x000000002747e348>" > diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price)) tracemem[0x000000002747e348 -> 0x000000002a624bc0]: <Anonymous> grouped_dt group_by_.data.table group_by_ group_by <Anonymous> freduce _fseq eval eval withVisible %>% Source: local data table [5 x 2] cut AvgPrice 1 Fair 4358.758 2 Good 3928.864 3 Very Good 3981.760 4 Premium 4584.258 5 Ideal 3457.542 > diamondsDT[, mean(price), by = cut] cut V1 1: Ideal 3457.542 2: Premium 4584.258 3: Good 3928.864 4: Very Good 3981.760 5: Fair 4358.758 > untracemem(diamondsDT)
источник
Теперь вы можете использовать dtplyr , который является частью тидиверсии . Он позволяет вам использовать операторы стиля dplyr как обычно, но использует ленивую оценку и переводит ваши операторы в код data.table под капотом. Накладные расходы на перевод минимальны, но вы получаете все, если не большинство преимуществ data.table. Более подробная информация в официальном репозитории git здесь и на странице tidyverse .
источник