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 74

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 вывод:

enter image description here

Примечание выше, в настоящее время работает только на маках, насколько мне известно. Также, к сожалению, 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)