Почему rbindlist «лучше», чем rbind?

135

Я просматриваю документацию data.tableи также заметил из некоторых разговоров здесь о SO, который rbindlistдолжен быть лучше, чем rbind.

Я хотел бы знать, почему rbindlistлучше rbindи в каких сценариях rbindlistдействительно лучше rbind?

Есть ли какое-то преимущество с точки зрения использования памяти?

Чинмай Патил
источник

Ответы:

155

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): NA введены по принуждению: возможная ошибка в data.table? ( Ошибка № 2384 )


Имена строк в rbind.data.frame могут быть неприятными

rbindlistможет обрабатывать lists data.framesи data.tables, и вернет data.table без имен строк

Вы можете получить в кучу имен строк, используя do.call(rbind, list(...)) см.

Как избежать переименования строк при использовании rbind внутри do.call?


Эффективность памяти

С точки зрения памяти rbindlistреализована C, поэтому она эффективна для памяти, она использует setattrдля установки атрибутов по ссылке

rbind.data.frameреализован R, он выполняет множество назначений и использует attr<-( class<-и rownames<-все это (внутренне) создает копии созданного data.frame.

mnel
источник
1
К вашему сведению attr<-, class<-и (я думаю) rownames<-все модифицируется на месте.
Хэдли
5
@hadley Ты уверен? Попробуй DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Мэтт Доул
4
rbind.data.frameимеет специальную логику "угона" - когда его первый аргумент - a data.table, он вызывает .rbind.data.tableвместо этого, что делает небольшую проверку, а затем вызывает rbindlistвнутри. Так что, если у вас уже есть data.tableобъекты для привязки, вероятно, разница между производительностью rbindи rbindlist.
Кен Уильямс
6
Mnel, этот пост, возможно, нуждается в редактировании, теперь rbindlistон может соответствовать по names ( use.names=TRUE), а также заполнить отсутствующие столбцы ( fill=TRUE). Я обновил этот , этот и этот пост. Вы не против отредактировать это или все в порядке, если я это сделаю? В любом случае меня устраивает.
Арун
1
dplyr::rbind_listтоже очень похоже
хадли
48

By v1.9.2, rbindlistэволюционировал совсем немного, реализовав множество функций, в том числе:

  • Выбор самого высокого SEXPTYPEстолбца при привязке - реализовано при v1.9.2закрытии FR # 2456 и Bug # 4981 .
  • factorПравильная обработка столбцов - сначала реализована в v1.8.10закрытии ошибки # 2650 и расширена для тщательного связывания упорядоченных факторов v1.9.2, закрывая FR # 4856 и ошибку № 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также может замедлиться, если на data.frame много столбцов и есть много таких data.frames для привязки (как показано в тесте ниже).

Однако rbindlistотсутствие определенных функций (таких как проверка уровней факторов или сопоставление имен) имеет очень крошечный (или нулевой) вес для того, чтобы он работал быстрее, чем rbind.data.frame. Это потому, что они были тщательно реализованы в C, оптимизированы для скорости и памяти.

Вот тест , который подчеркивает эффективное связывание, подбирая по именам столбцов , а также с помощью rbindlist«s 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версии.

Arun
источник