Чому rbindlist "кращий", ніж rbind?


135

Я переглядаю документацію, data.tableа також помітив із деяких розмов тут на ПС, що rbindlist, мабуть, буде кращим, ніж rbind.

Мені хотілося б знати, чому rbindlistкраще rbindі в яких сценаріях rbindlistсправді перевершується rbind?

Чи є якась перевага щодо використання пам'яті?

Відповіді:


155

rbindlistє оптимізованою версією do.call(rbind, list(...)), яка відома тим, що вона повільно користуєтьсяrbind.data.frame


Де воно справді перевершує

Деякі питання, які показують, де rbindlistсяє

Швидке векторизоване злиття списку кадрів даних за рядками

Проблема при перетворенні довгого списку фреймів 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 (аларми): NA, введені примусово: можлива помилка у data.table? ( Помилка № 2384 )


Назви рядків rbind.data.frame можуть неприємно

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.


1
FYI 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вона може збігатися за іменами ( use.names=TRUE), а також заповнювати пропущені стовпці ( fill=TRUE). Я оновив це , це та це повідомлення. Ви не проти відредагувати цю або це добре, якщо я це роблю? Будь-який спосіб мені добре.
Арун

1
dplyr::rbind_listтакож досить схоже
hadley

48

До того ж 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аргумент, який дозволяє прив'язувати, заповнюючи пропущені стовпці, реалізовані в Р.

Зараз у 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також може бути повільнішою, коли для даних dataframe є багато стовпців і існує багато таких фреймів data.frames (як показано на еталоні нижче).

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

Ось орієнтир, який підкреслює ефективне прив'язування під час узгодження за назвами стовпців, а також за допомогою функції rbindlist's use.namesвід v1.9.3. Набір даних складається з 10000 кадрів data.fram розміром 10 * 500.

Примітка: цей показник було оновлено, щоб включати порівняння з dplyr"s"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у версії.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.