dplyr по данным.таблица, я действительно использую данные.стол?
Если я использую 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
вот эквивалентность datatable, которую я придумал. Не уверен, что он соответствует хорошей практике DT. Но мне интересно, действительно ли код более эффективен, чем синтаксис dplyr за сценой:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
3 ответа:
нет прямого / простого ответа, потому что философия обоих этих пакетов отличается в определенных аспектах. Так что некоторые компромиссы неизбежны. Вот некоторые из проблем, которые вам, возможно, потребуется решить/рассмотреть.
операции
i
( = =filter()
иslice()
в dplyr)предположим
DT
скажем, 10 столбцов. Рассмотрим эти данные.табличные выражения:DT[a > 1, .N] ## --- (1) DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1) дает количество строк в
DT
где колонкиa > 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)
очевидно, что данные.табличные коды короче. Кроме того, они также более эффективная память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)
важно подчеркнуть важное философское различие между двумя пакетами:
In
data.table
, мы хотели бы сохранить эти операции вместе, и это позволяет взглянуть наj-expression
(из того же вызова функции) и понимают, что нет необходимости в каких-либо Столбцах в (1). Выражение вi
получает компьютерная, и.N
- это просто сумма того логического вектора, который дает количество строк; все подмножество никогда не реализуется. В (2), просто колонкиb,c,d
материализуются в подмножестве, другие столбцы игнорируются.а в
dplyr
, философия состоит в том, чтобы иметь функцию делать именно одну вещь хорошо. Существует (по крайней мере в настоящее время) нет способа сказать, если операция послеfilter()
нужны все те столбцы, которые мы фильтровали. Вам нужно будет думать наперед, если вы хочется выполнять такие задачи качественно. Я лично считаю это контринтуитивным в данном случае.обратите внимание, что в (5) и (6), мы все еще подмножество столбец
a
который нам не нужен. Но я не знаю, как этого избежать. Еслиfilter()
функция имела аргумент для выбора столбцов для возврата, мы могли бы избежать этой проблемы, но тогда функция не будет выполнять только одну задачу (что также является выбором дизайна dplyr).суб-назначить по ссылка
dplyr будет никогда обновление ПО ССЫЛКЕ. Это еще одно огромное (философское)различие между двумя пакетами.
например, в данные.таблица вы можете сделать:
DT[a %in% some_vals, a := NA]
который обновляет столбец
a
по ссылке только на тех строках, которые удовлетворяют условию. На данный момент dplyr глубоко копирует все данные.таблица внутренне, чтобы добавить новый столбец. @BrodieG уже упоминал об этом в своем ответе.но глубокая копия может быть заменена мелкой копией, когда FR #617 есть. Также актуально:dplyr: FR#614. Обратите внимание, что по-прежнему столбец, который вы изменяете, всегда будет скопирован (поэтому немного медленнее / менее эффективен в памяти). Там не будет никакого способа обновить столбцы по ссылке.
другие функции
в данных.таблица, вы можете агрегировать при соединении, и это больше straightfoward для понимания и эффективной памяти, так как промежуточный результат соединения никогда не материализуется. Проверьте этот пост для примера. Вы не можете (на данный момент?) сделайте это, используя данные dplyr.табличные данные.синтаксис кадра.
данные.стол-это подвижного соединения функция также не поддерживается в синтаксисе dplyr.
мы недавно реализовали перекрытие соединений в данных.таблица для объединения по интервальным диапазонам (вот пример), что является отдельной функцией
foverlaps()
на данный момент, и поэтому может использоваться с операторами труб (magrittr / pipeR? - никогда не пробовал сам).но в конечном счете, наша цель-интегрировать его в
[.data.table
чтобы мы могли собирать другие функции, такие как группировка, агрегирование при присоединении и т. д.. которые будут иметь те же ограничения, описанные выше.начиная с 1.9.4, данные.стол реализует автоматический индексирование с использованием вторичных ключей для быстрого бинарного поиска на основе подмножеств с регулярным синтаксисом R. Например:
DT[x == 1]
иDT[x %in% some_vals]
автоматически создаст индекс при первом запуске, который затем будет использоваться для последовательных подмножеств из одного столбца для быстрого подмножества с использованием двоичного поиска. Эта функция будет продолжать развиваться. Проверьте в этом суть для краткого обзора этой функции.от
filter()
реализовано для данных.таблицы, он не использует это в своих интересах особенность.функция dplyr заключается в том, что она также обеспечивает интерфейс к базам данных используя тот же синтаксис, что данные.на данный момент таблица не работает.
таким образом, вам придется взвесить эти (и, вероятно, другие точки) и решить, на основе того, являются ли эти компромиссы приемлемыми для вас.
HTH
(1) Обратите внимание, что эффективность памяти напрямую влияет на скорость (особенно в качестве данных становится больше), поскольку узким местом в большинстве случаев является перемещение данных из основной памяти в кэш (и использование данных в кэше как можно больше - уменьшить пропуски кэша - чтобы уменьшить доступ к основной памяти). Не вдаваясь в подробности здесь.
просто попробуйте.
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]
на эту проблему представляется данные.таблица в 2,4 раза быстрее, чем dplyr с использованием данных.таблица:
test replications elapsed relative 2 data.table 100 2.39 1.000 1 dplyr 100 5.77 2.414
пересмотренный на основе комментария полимеразы.
чтобы ответить на ваши вопросы:
- Да, вы используете
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
просто выделяет память для самой большой группы результатов, которая в этом случае является только одной строкой, так что имеет большое значение, сколько памяти должно быть выделено.к вашему сведению, если кто-то заботится, я нашел это с помощью
treeprof
(install_github("brodieg/treeprof")
), экспериментальный (и все еще очень Альфа) просмотрщик дерева дляRprof
вывод:Примечание выше, в настоящее время работает только на маках, насколько мне известно. Также, к сожалению,
Rprof
записывает вызовы типаpackagename::funname
как анонимный, так что на самом деле это может быть любой и всеdatatable::
звонки внутриgrouped_dt
это ответственно, но от быстрого тестирования это выглядело какdatatable::copy
большой.тем не менее, вы можете быстро увидеть, как не так много накладных расходов вокруг
[.data.table
вызов, но есть и полностью отдельная ветка для группировки.
EDIT: для подтверждения копирования:
> 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)