Ответ 1
Хороший вопрос. В общем, я бы оценил размер данных, который достаточно велик, чтобы полностью (полностью) не входить в кеш. Посмотрите здесь в разделе "Начальная настройка". Не имеет смысла сравнивать инструменты, разработанные для (в памяти) больших данных для запуска задач, выполняемых в миллисекундах. Мы планируем провести сравнительный анализ относительно более крупных данных в будущем.
Кроме того, если вы намерены выяснить, выполняет ли mutate
копию, то все, что вам нужно сделать, это проверить address
до и после (это можно сделать с помощью .Internal(inspect(.))
в базе R
или используя функцию changes()
в dplyr
).
В том, делается ли копия или нет:
Здесь есть две разные вещи. A) создание нового столбца и B) изменение существующего столбца.
A) Создание нового столбца:
require(dplyr)
require(data.table)
df <- tbl_df(data.frame(x=1:5, y=6:10))
df2 <- mutate(df, z=1L)
changes(df, df2)
# Changed variables:
# old new
# z 0x105ec36d0
Он сообщает вам, что никаких изменений в адресах x
и y
нет, и указывает z
, который мы только что добавили. Что здесь происходит?
dplyr
неглубоко копирует data.frame
, а затем добавляет новый столбец. Неглубокая копия в отличие от глубокой копии просто копирует вектор указателей столбцов, а не самих данных. Поэтому он должен быть быстрым. В основном df2
создается с тремя столбцами, где первые два столбца указывают на то же адресное местоположение, что и df
, а третий столбец только что был создан.
С другой стороны, data.table
не имеет мелкой копии, так как он изменяет столбец по ссылке (на месте). data.table
также (умно) перераспределяет список векторов столбцов, который позволяет быстро добавлять (новые) столбцы по ссылке.
Не должно быть большой разницы во времени до мелкой копии, если у вас слишком много столбцов. Вот небольшой ориентир на 5000 столбцов с 1e4 строками:
require(data.table) # 1.8.11
require(dplyr) # latest commit from github
dt <- as.data.table(lapply(1:5e3, function(x) sample(1e4)))
ans1 <- sapply(1:1e2, function(x) {
dd <- copy(dt) # so as to create the new column each time
system.time(set(dd, i=NULL, j="V1001", value=1L))['elapsed']
# or equivalently of dd[, V1001 := 1L]
})
df <- tbl_df(as.data.frame(dt))
ans2 <- sapply(1:1e2, function(x) {
system.time(mutate(df, V1001 = 1L))['elapsed']
})
> summary(ans1) # data.table
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.00000 0.00000 0.00100 0.00061 0.00100 0.00100
> summary(ans2) # dplyr
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.03800 0.03900 0.03900 0.04178 0.04100 0.07900
Здесь вы можете увидеть разницу в "среднем времени" (0.00061 против 0.04178).
B) Изменить существующий столбец:
df2 <- mutate(df, y=1L)
changes(df, df2)
# Changed variables:
# old new
# y 0x105e5a850 0x105e590e0
Он сообщает вам, что y
был изменен - была сделана копия столбца y
. Для изменения значений y
необходимо было создать новое место памяти, поскольку оно указывало на то же место, что и раньше, на df
y
.
Однако, поскольку data.table
изменяется на месте, в случае (B) копия не будет сделана. Он изменит df
на месте. Поэтому, если вы изменяете столбцы, вы должны увидеть разницу в производительности.
Это одно из фундаментальных различий в философии между двумя пакетами.
dplyr
не нуждается в модификации на месте и, следовательно, отторжения путем копирования при изменении существующих столбцов.
И из-за этого было бы невозможно изменить значения некоторых строк определенного столбца data.frame без глубокой копии. То есть:
DT[x >= 5L, y := 1L] # y is an existing column
Это невозможно сделать без полной копии data.frame с использованием базовых R
и dplyr
, насколько мне известно.
Кроме того, рассмотрите набор данных из двух столбцов размером 20 ГБ (два столбца каждый 10 ГБ) на машине с 32 ГБ ОЗУ. Философия data.table
заключается в том, чтобы предоставить способ изменить подмножество этих столбцов 10GB по ссылке, не копируя ни одного столбца один раз. Копия одного столбца потребует дополнительных 10 ГБ и может выйти из строя с out-out-memory, не говоря уже о быстроте или нет. Эта концепция (:=
) аналогична UPDATE в SQL.