Почему rbindlist "лучше", чем rbind?
Я просматриваю документацию data.table
, а также заметил из некоторых разговоров здесь о SO, что rbindlist
должен быть лучше, чем rbind
.
Я хотел бы знать, почему rbindlist
лучше, чем rbind
и в каких сценариях rbindlist
действительно превосходит rbind
?
Есть ли преимущества в использовании памяти?
Ответы
Ответ 1
rbindlist
- оптимизированная версия do.call(rbind, list(...))
, которая, как известно, медленна при использовании rbind.data.frame
Где это действительно превосходит
Некоторые вопросы, которые показывают, где rbindlist
светит,
Быстрое векторное слияние списка data.frames по строке
Не удалось преобразовать длинный список data.frames(~ 1 миллион) в один файл data.frame с помощью do.call и ldply
У них есть контрольные показатели, которые показывают, насколько быстро это может быть.
rbind.data.frame медленный, по причине
rbind.data.frame
выполняет множество проверок и будет соответствовать по имени. (т.е. rbind.data.frame будет учитывать тот факт, что столбцы могут быть в разных порядках и совпадать по имени), rbindlist
не выполняет этот вид проверки и будет присоединяться по позиции
например,
do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
## a b
## 1 1 2
## 2 2 3
## 3 2 1
## 4 3 2
rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
## a b
## 1: 1 2
## 2: 2 3
## 3: 1 2
## 4: 2 3
Некоторые другие ограничения rbindlist
Он используется для борьбы с factors
, из-за ошибки, которая с тех пор исправлена:
rbindlist два data.tables, где один имеет коэффициент, а другой - тип символа для столбца (Ошибка № 2650)
У него есть проблемы с именами повторяющихся столбцов
см
Предупреждающее сообщение: в rbindlist (allargs): NAs, введенные принуждением: возможная ошибка в data.table? (Ошибка № 2384)
rbind.data.frame rownames может расстраивать
rbindlist
может обрабатывать lists
data.frames
и data.tables
, и вернет таблицу данных без имен ростов
вы можете получить путаницу с именами ростов с помощью do.call(rbind, list(...))
см
Как избежать переименования строк при использовании rbind внутри do.call?
Эффективность памяти
В терминах памяти rbindlist
реализовано в C
, так что память эффективна, она использует setattr
для установки атрибутов по ссылке
rbind.data.frame
реализован в R
, он много назначает и использует attr<-
(и class<-
и rownames<-
, все из которых будут (внутренне) создавать копии созданного data.frame.
Ответ 2
В v1.9.2
rbindlist
развился довольно много, реализовав множество функций, в том числе:
- Выбор наивысшего
SEXPTYPE
при связывании - реализован в v1.9.2
закрывающей FR # 2456 и Bug # 4981. -
v1.8.10
столбцы factor
обработки - сначала реализованы в v1.8.10
закрывая Bug # 2650, а также тщательно v1.9.2
упорядоченные факторы в v1.9.2
, закрыв FR # 4856 и Bug # 5019.
Кроме того, в v1.9.2
rbind.data.table
также получил аргумент fill
, который позволяет связывать, заполняя отсутствующие столбцы, реализованные в R.
Теперь в v1.9.3
есть еще больше улучшений в этих существующих функциях:
-
rbindlist
получает аргумент use.names
, который по умолчанию FALSE
для обратной совместимости. -
rbindlist
также получает аргумент fill
, который по умолчанию также является FALSE
для обратной совместимости. - Все эти функции реализованы на C и тщательно написаны, чтобы не ухудшать скорость при добавлении функций.
- Поскольку
rbindlist
теперь может совпадать по именам и заполнять отсутствующие столбцы, rbind.data.table
просто вызывает rbindlist
. Единственное различие заключается в том, что use.names=TRUE
по умолчанию для rbind.data.table
для обратной совместимости.
rbind.data.frame
замедляется в основном из-за копий (что также указывает @mnel), чего можно было бы избежать (перейдя на C). Я думаю, что это не единственная причина. Реализация для проверки/сопоставления имен столбцов в rbind.data.frame
также может rbind.data.frame
при наличии большого количества столбцов на data.frame, и есть много таких привязок data.frames (как показано в сравнительном примере ниже).
Тем не менее, что rbindlist
отсутствие ( под ред) некоторые функции (например, проверка уровней фактора или совпадающих имен) имеет очень маленькие (или нет) веса в сторону причем быстрее, чем rbind.data.frame
. Это потому, что они были тщательно реализованы на C, оптимизированы для скорости и памяти.
Здесь используется эталон, который подчеркивает эффективную привязку при сопоставлении по именам столбцов, а также с использованием функции rbindlist
use.names
из v1.9.3
. Набор данных состоит из 10000 данных. Каждый размер 10 * 500.
NB: этот тест был обновлен, чтобы включить сравнение с dplyr
bind_rows
library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
set.seed(1L)
names = paste0("V", 1:500)
cols = 500L
foo <- function() {
data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
setnames(data, sample(names))
}
n = 10e3L
ll = vector("list", n)
for (i in 1:n) {
.Call("Csetlistelt", ll, i, foo())
}
system.time(ans1 <- rbindlist(ll))
# user system elapsed
# 1.226 0.070 1.296
system.time(ans2 <- rbindlist(ll, use.names=TRUE))
# user system elapsed
# 2.635 0.129 2.772
system.time(ans3 <- do.call("rbind", ll))
# user system elapsed
# 36.932 1.628 38.594
system.time(ans4 <- bind_rows(ll))
# user system elapsed
# 48.754 0.384 49.224
identical(ans2, setDT(ans3))
# [1] TRUE
identical(ans2, setDT(ans4))
# [1] TRUE
Связывание столбцов как таковых без проверки имен потребовало всего 1,3, когда проверка имен столбцов и привязки соответственно занимала всего 1,5 секунды. По сравнению с базовым решением, это на 14 раз быстрее и на 18 раз быстрее, чем версия dplyr
.