Dlaczego rbindlist jest „lepszy” niż rbind?

135

Przeglądam dokumentację data.tablei zauważyłem z niektórych rozmów tutaj na SO, który rbindlistpowinien być lepszy niż rbind.

Chciałbym wiedzieć, dlaczego jest rbindlistlepszy niż rbindiw których scenariuszach rbindlistnaprawdę się sprawdza rbind?

Czy jest jakaś korzyść pod względem wykorzystania pamięci?

Chinmay Patil
źródło

Odpowiedzi:

155

rbindlistjest zoptymalizowaną wersją programu do.call(rbind, list(...)), która jest znana z tego, że działa wolno podczas używaniarbind.data.frame


Gdzie to naprawdę się wyróżnia

Kilka pytań, które pokazują, gdzie rbindlistsą błyski

Szybkie wektoryzowane łączenie list danych.frames według wiersza

Problem z konwersją długiej listy data.frames (~ 1 milion) do pojedynczej data.frame przy użyciu do.call i ldply

Mają testy porównawcze, które pokazują, jak szybko może to być.


rbind.data.frame jest powolny z jakiegoś powodu

rbind.data.framedużo sprawdza i dopasowuje według nazwy. (tzn. rbind.data.frame uwzględni fakt, że kolumny mogą być w różnych rzędach i pasować według nazwy), rbindlistnie wykonuje tego rodzaju kontroli i łączy się według pozycji

na przykład

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

Inne ograniczenia rbindlist

Jest używany do walki do czynienia z factorsuwagi na błąd, który został już ustalony:

rbindlist two data.tables, gdzie jeden ma współczynnik, a drugi ma typ znaku dla kolumny ( Bug # 2650 )

Ma problemy ze zduplikowanymi nazwami kolumn

zobacz Komunikat ostrzegawczy: w rbindlist (allargs): NA wprowadzone przez wymuszenie: możliwy błąd w data.table? ( Błąd # 2384 )


rownames rbind.data.frame mogą być frustrujące

rbindlistmoże obsłużyć lists data.framesi data.tables, i zwróci dane.table bez nazw wywołań

możesz dostać się w chaos rownames używając do.call(rbind, list(...)) see

Jak uniknąć zmiany nazw wierszy podczas używania rbind wewnątrz do.call?


Wydajność pamięci

Jeśli chodzi o pamięć, rbindlistjest zaimplementowana w C, więc jest wydajna, używa setattrdo ustawiania atrybutów przez odniesienie

rbind.data.framejest zaimplementowany w programie R, wykonuje wiele przypisań i zastosowań attr<-( class<-i rownames<-wszystko to (wewnętrznie) utworzy kopie utworzonej ramki data.frame.

mnel
źródło
1
FYI attr<-, class<-i (chyba) rownames<-wszystko modyfikować w miejscu.
hadley,
5
@hadley Czy na pewno? Spróbuj DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle,
4
rbind.data.framema specjalną logikę „przechwytywania” - gdy jej pierwszym argumentem jest a data.table, wywołuje .rbind.data.tablezamiast tego, co trochę sprawdza, a następnie wywołuje rbindlistwewnętrznie. Więc jeśli masz już data.tableobiekty do powiązania, prawdopodobnie różnica w wydajności między rbindi rbindlist.
Ken Williams,
6
mnel, ten post być może wymaga edycji, teraz rbindlistjest w stanie dopasowywać według nazw ( use.names=TRUE), a także wypełniać brakujące kolumny ( fill=TRUE). Zaktualizowałem ten , ten i ten post. Czy masz coś przeciwko edycji tego, czy mogę to zrobić? Tak czy inaczej jest dla mnie w porządku.
Arun
1
dplyr::rbind_listjest również dość podobny
Hadley
48

Przez v1.9.2, rbindlistewoluował dość mocno, wdrażając wiele funkcji, w tym:

  • Wybieranie najwyższej SEXPTYPEz kolumn podczas wiązania - zaimplementowane w v1.9.2zamykaniu FR # 2456 i Bug # 4981 .
  • factorPrawidłowa obsługa kolumn - najpierw zaimplementowana w v1.8.10zamykaniu Bug # 2650 i rozszerzona o dokładne wiązanie uporządkowanych czynników v1.9.2, zamykając FR # 4856 i Bug # 5019 .

Ponadto, w v1.9.2, rbind.data.tablerównież zyskał fillargument, który pozwala do wiązania poprzez wypełnienie brakujących kolumn, realizowany w R.

Teraz w v1.9.3tych istniejących funkcjach wprowadzono jeszcze więcej ulepszeń:

  • rbindlistotrzymuje argument use.names, który domyślnie służy FALSEdo wstecznej kompatybilności.
  • rbindlistrównież zyskuje argument fill, który domyślnie służy również FALSEkompatybilności wstecznej.
  • Wszystkie te funkcje są zaimplementowane w C i starannie napisane, aby nie zmniejszać szybkości podczas dodawania funkcjonalności.
  • Ponieważ rbindlistmożna teraz dopasowywać według nazw i wypełniać brakujące kolumny, rbind.data.tablepo prostu wywołuje rbindlistteraz. Jedyną różnicą jest to, że use.names=TRUEdomyślnie for rbind.data.table, w celu zapewnienia zgodności wstecznej.

rbind.data.framespowalnia całkiem sporo, głównie z powodu kopii (na co wskazuje @mnel), których można by uniknąć (przechodząc do C). Myślę, że to nie jedyny powód. Implementacja sprawdzania / dopasowywania nazw kolumn w programie rbind.data.framemoże również działać wolniej, gdy jest wiele kolumn na ramkę data.frame i jest wiele takich ramek danych do powiązania (jak pokazano w poniższym benchmarku).

Jednak rbindlistbrak niektórych funkcji (takich jak sprawdzanie poziomów czynników lub pasujących nazw) ma bardzo małą (lub żadną) wagę, ponieważ jest szybszy niż rbind.data.frame. To dlatego, że zostały starannie zaimplementowane w C, zoptymalizowane pod kątem szybkości i pamięci.

Oto punkt odniesienia, który podkreśla, że skuteczne wiązanie, dopasowując przez nazwy kolumn, jak również przy użyciu rbindlist„s use.namesfunkcję od v1.9.3. Zestaw danych składa się z 10000 ramek danych, każda o rozmiarze 10 * 500.

Uwaga: ten wzorzec został zaktualizowany do obejmować porównanie do dplyr„sbind_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

Wiązanie kolumn jako takich bez sprawdzania nazw zajęło tylko 1,3, podczas gdy sprawdzenie nazw kolumn i odpowiednie powiązanie zajęło tylko 1,5 sekundy więcej. W porównaniu do rozwiązania podstawowego jest to 14x szybsze i 18x szybsze niż dplyrwersja.

Bieg
źródło