Почему 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.