data.table vs dplyr: czy jeden może zrobić coś dobrze, a drugi nie potrafi lub robi słabo?

Przegląd

Jestem stosunkowo zaznajomiony z data.table, nie za bardzo z dplyr. Przeczytałem kilka dplyr winiet i przykłady, które pojawiły się na SO, i jak dotąd moje wnioski są takie, że:

  1. data.table i dplyr są porównywalne pod względem szybkości, z wyjątkiem sytuacji, gdy istnieje wiele grup (tj. > 10-100 tys.) i w niektórych innych okolicznościach (patrz testy porównawcze poniżej)
  2. dplyr ma bardziej przystępną składnię
  3. dplyr abstrahuje (lub będzie) potencjalnymi interakcjami DB
  4. Istnieją pewne drobne różnice w funkcjonalności (patrz „Przykłady/Zastosowanie” poniżej)

Moim zdaniem punkt 2. nie ma większego znaczenia, ponieważ dość dobrze go znam data.table, choć rozumiem, że dla użytkowników, którzy nie znają obu rozwiązań, będzie to miało duże znaczenie. Chciałbym uniknąć sporu o to, co jest bardziej intuicyjne, ponieważ nie ma to znaczenia dla mojego konkretnego pytania zadawanego z perspektywy osoby już zaznajomionej z data.table. Chciałbym też uniknąć dyskusji na temat tego, jak „bardziej intuicyjne” prowadzi do szybszej analizy (z pewnością to prawda, ale znowu nie to mnie tutaj najbardziej interesuje).

Pytanie

Chcę wiedzieć:

  1. Czy istnieją zadania analityczne, które są o wiele łatwiejsze do kodowania przy użyciu jednego lub drugiego pakietu dla osób zaznajomionych z pakietami (tj. pewna kombinacja wymaganych naciśnięć klawiszy w porównaniu z wymaganym poziomem ezoteryki, gdzie mniej każdego z nich jest dobrą rzeczą).
  2. Czy istnieją zadania analityczne, które w jednym pakiecie są wykonywane znacznie (tj. ponad 2 razy) wydajniej niż w innym?

Jedno ostatnie pytanie SO skłoniło mnie do myślenia to trochę więcej, ponieważ do tego momentu nie sądziłem, że dplyr zaoferuje znacznie więcej niż to, co mogę już zrobić w data.table. Oto rozwiązanie dplyr (dane na końcu Q):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

Co było znacznie lepsze niż moja próba włamania się do rozwiązania data.table. To powiedziawszy, dobre rozwiązania data.table są również całkiem dobre (dzięki Jean-Robertowi, Arunowi i zauważcie, że wolałem pojedyncze stwierdzenie od ściśle najbardziej optymalnego rozwiązania):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

Składnia tego ostatniego może wydawać się bardzo ezoteryczna, ale w rzeczywistości jest całkiem prosta, jeśli jesteś przyzwyczajony do data.table (tj. nie używasz niektórych bardziej ezoterycznych sztuczek).

Idealnie byłoby, gdybym zobaczył kilka dobrych przykładów, w których sposób dplyr lub data.table jest znacznie bardziej zwięzły lub działa znacznie lepiej.

Przykłady

Usage
  • dplyr nie pozwala na grupowane operacje, które zwracają dowolną liczbę wierszy (z pytanie Eddiego, uwaga: wygląda na to, że zostanie zaimplementowane w dplyr 0.5, @beginneR pokazuje także potencjalne obejście, używając do w odpowiedzi na pytanie @eddiego).
  • data.table obsługuje złączenia kroczące (dzięki @dholstius) a także Połączenia nakładające się
  • data.table wewnętrznie optymalizuje wyrażenia w postaci DT[col == value] lub DT[col %in% values] pod kątem szybkości poprzez automatyczne indeksowanie, które wykorzystuje wyszukiwanie binarne przy użyciu tej samej podstawowej składni R. Tutaj znajdziesz więcej szczegółów i mały test porównawczy.
  • dplyr oferuje standardowe wersje ewaluacyjne funkcji (np. regroup, summarize_each_), które mogą uprościć programowe użycie dplyr (uwaga: programistyczne użycie data.table jest zdecydowanie możliwe, wymaga jedynie dokładnego przemyślenia, podstawienia/cytowania itp., przynajmniej według mojej wiedzy)
Benchmarks

Dane

To jest pierwszy przykład, który pokazałem w sekcji pytań.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))

person BrodieG    schedule 29.01.2014    source źródło
comment
Rozwiązanie podobne w czytaniu do dplyr to: as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]   -  person eddi    schedule 29.01.2014
comment
W przypadku #1 zespoły dplyr i data.table pracują nad testami porównawczymi, więc odpowiedź pojawi się w pewnym momencie. #2 (składnia) imO jest całkowicie fałszywe, ale wyraźnie wkracza na terytorium opinii, więc również głosuję za zamknięciem.   -  person eddi    schedule 29.01.2014
comment
@eddi, ale czy są problemy, które można wyrazić czysto w jednym, ale nie w drugim? A może Twoim zdaniem jest to wyłącznie kwestia stylu?   -  person BrodieG    schedule 29.01.2014
comment
cóż, znowu imO, zbiór problemów, które są bardziej przejrzyście wyrażone w (d)plyr, ma miarę 0   -  person eddi    schedule 29.01.2014
comment
@eddi, to także moje ogólne podejście, chociaż nie znam się dplyr na tyle dobrze, aby to wykluczyć i byłem ciekawy, czy ktoś wymyśli dobry kontrprzykład. Również ładne uproszczenie sformułowania data.table.   -  person BrodieG    schedule 29.01.2014
comment
Jest to bardzo podobne do stackoverflow.com/q/16153947/892313, w którym pytano o plyr i data.table i również zostało zamknięte. Moja odpowiedź tam wyjaśnia, dlaczego to więcej.   -  person Brian Diggs    schedule 29.01.2014
comment
@eddi IMHO zestaw problemów wyraźniej wyrażony w data.table ma miarę 0, ale nie jest to sprzeczne z twoim przekonaniem;)   -  person hadley    schedule 29.01.2014
comment
@hadley :) jest to spójne tylko wtedy, gdy uważasz, że żadne z nich nie obejmuje zestawu problemów o niezerowej mierze; mówiąc poważniej, myślę, że winieta obejmuje 99% pytań grupowych/podsumowujących/etc, które pojawiają się w SO (które są bardzo bliskimi reinkarnacjami około 5-10 różnych problemów), ze wszystkimi różnymi implementacjami i ich punktami odniesienia jest już dawno spóźnione   -  person eddi    schedule 29.01.2014
comment
@eddi jest to również spójne, jeśli wierzysz, że data.table i dplyr są równie wyraziste dla każdego problemu. Nie sądzę, że testy porównawcze są aż tak interesujące, ale z mojej perspektywy cran.r-project.org/web/packages/dplyr/vignettes/ omawia najciekawsze (dla mnie) typy problemów.   -  person hadley    schedule 30.01.2014
comment
@hadley tak, to prawdopodobnie obejmuje większość problemów. Dwa krótkie komentarze – brak dołączeń data.table na końcu? I (prawdopodobnie powtarzając oczywistość stwierdzoną w innym miejscu) co z bardziej interesującymi rozmiarami danych (gdzie zdefiniowałbym interesujące jako coś, co zajmuje więcej niż powiedzmy minutę w bazie - co jest generalnie głównym powodem, dla którego ludzie zaczynają badać testy porównawcze pakietów)?   -  person eddi    schedule 30.01.2014
comment
@BrodieG jedyną rzeczą, która naprawdę niepokoi mnie zarówno w dplyr, jak i plyr w odniesieniu do składni i jest w zasadzie głównym powodem, dla którego nie lubię ich składni, jest to, że muszę nauczyć się zbyt wielu (czytaj więcej niż 1) dodatkowych funkcji (z nazwami które nadal nie mają dla mnie sensu), pamiętajcie, co robią, jakie przyjmują argumenty itp. To zawsze było dla mnie ogromnym odejściem od filozofii plyr.   -  person eddi    schedule 30.01.2014
comment
@eddi Po prostu zmęczyłem się pisaniem konwersji data.table. Z radością przyjmę żądanie ściągnięcia, które dodałoby odpowiedniki data.table. Utknąłem przy małych rozmiarach danych, ponieważ większość algorytmów to O(n), ale chętnie zobaczę przykłady przeciwne.   -  person hadley    schedule 30.01.2014
comment
@eddi [z przymrużeniem oka] Jedyną rzeczą, która naprawdę mnie denerwuje w składni data.table, jest to, że muszę się nauczyć, jak zbyt wiele argumentów funkcji wchodzi w interakcję i co oznaczają tajemnicze skróty (np. .SD). [poważnie] Myślę, że są to uzasadnione różnice w projekcie, które przypadną do gustu różnym osobom   -  person hadley    schedule 30.01.2014
comment
@hadley re .SD i in. - to prawda - .SD zajęło mi trochę czasu zrozumienie, ale zanim tam dotarłem, byłem już w stanie wiele zrobić, podczas gdy (d)plyr przedstawia ci dużą barierę od razu.   -  person eddi    schedule 30.01.2014
comment
Wydaje się, że jest to przykład, w którym data.table (obecnie) zapewnia prostsze rozwiązanie: stackoverflow.com/questions/21295936/   -  person Vincent    schedule 31.01.2014
comment
Tutaj [http://stackoverflow.com/questions/21477525/fast-frequency-and-percentage-table-with-dplyr/][1] to jeden z przykładów porównania dplyr i data.table.   -  person danas.zuokas    schedule 31.01.2014
comment
Oto to aspekt porównania, który właśnie zrobiłem, aby dać dodatkową perspektywę ostatnim tweetom na temat dołączeń na dplyr /Tabela danych. HTH.   -  person Arun    schedule 07.05.2014
comment
@Arun, ciekawe. Zanotowałem to w poście.   -  person BrodieG    schedule 07.05.2014
comment
Myślałeś, że to pytanie zostało zamknięte, ponieważ nie jest dozwolone w S.O.? Jeśli ma być tutaj utrzymywana, to może zrób z niej wiki społeczności – jestem za tym. Obecnie do porównania brakuje sporo; np. zamówione przyłącza. Czy możemy dodawać odpowiedzi?   -  person Matt Dowle    schedule 24.10.2014
comment
@MattDowle, było zamknięte, ale ktoś je ponownie otworzył. Prawdopodobnie odpowiedzi są dozwolone? Z pewnością można je przekształcić w wiki społeczności (nie wiem jak).   -  person BrodieG    schedule 24.10.2014
comment
Rolling łączy. data.table to robi. dplyr nie (jeszcze).   -  person dholstius    schedule 05.11.2014
comment
@dholstius, teraz zauważyłem, dziękuję za zwrócenie uwagi, nie pomyślałem o tym. Doprowadziło mnie to również do odkrycia implementacji połączeń nakładających się.   -  person BrodieG    schedule 05.11.2014
comment
Moja rada dla kogoś, kto zaczyna od jednego z nich, takiego jak ja, poleciłbym nauczyć się data.table, że jest znacznie mniej szczegółowy niż standardowa ramka danych, a większość operacji jest szybsza. Zmusza cię do myślenia wektorowego (który ma lepszą wydajność). Uważam to za dobry zamiennik ramki danych. Składnia na początku nie jest intuicyjna, ale gdy już się jej nauczysz, łatwo ją zapamiętać. Postrzegam dplyrjako zestaw funkcji, ale data.tablejako nowy obiekt klasy o znacznie lepszej wydajności i zwięzłej składni niż tradycyjna ramka danych.   -  person David Leal    schedule 15.03.2017
comment
@eddi Dlaczego piszesz całe to wyrażenie po stronie j, a nie niektóre po stronie i?   -  person skan    schedule 16.07.2017
comment
@skan Nie pamiętam już kontekstu tego, prawdopodobnie chodziło o osiągnięcie podobnego odczytu.   -  person eddi    schedule 17.07.2017


Odpowiedzi (4)


Musimy uwzględnić przynajmniej te aspekty, aby zapewnić kompleksową odpowiedź/porównanie (w dowolnej kolejności): Speed, Memory usage, Syntax i Features.

Moim zamiarem jest omówienie każdego z nich tak wyraźnie, jak to możliwe, z perspektywy data.table.

Uwaga: jeśli wyraźnie nie zaznaczono inaczej, odnosząc się do dplyr, mamy na myśli interfejs data.frame dplyr, którego elementy wewnętrzne są w C++ i używają Rcpp.


Składnia data.table jest spójna w swojej formie - DT[i, j, by]. Trzymanie i, j i by razem jest zgodne z projektem. Łącząc powiązane operacje, pozwala łatwo zoptymalizować operacje pod kątem szybkości i, co ważniejsze, wykorzystania pamięci, a także zapewnia pewne zaawansowane funkcje , a wszystko to przy zachowaniu spójności składni.

1. Prędkość

Do pytania dodano sporo testów porównawczych (choć głównie dotyczących operacji grupowania), które już pokazują, że data.table staje się szybsza niż dplyr wraz ze wzrostem liczby grup i/lub wierszy do grupowania, w tym testy porównawcze Matta dotyczące grupowania od 10 milionów do 2 miliardów wiersze (100 GB pamięci RAM) w 100–10 milionach grup i różne kolumny grupujące, co również porównuje pandas. Zobacz też zaktualizowane testy porównawcze, które obejmują również Spark i pydatatable.

Jeśli chodzi o benchmarki, świetnie byłoby uwzględnić również pozostałe aspekty:

  • Grupowanie operacji obejmujących podzbiór wierszy – tj. operacje typu DT[x > val, sum(y), by = z].

  • Porównaj inne operacje, takie jak aktualizacja i łączenie.

  • Oprócz czasu wykonania porównaj także zajętość pamięci dla każdej operacji.

2. Wykorzystanie pamięci

  1. #P10#
    #P11#
  2. #P12#
     # sub-assign by reference, updates 'y' in-place
     DT[x >= 1L, y := NA]
    
    #P13#
     # copies the entire 'y' column
     ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
    
    #P14# #P15#
     foo <- function(DT) {
         DT = shallow(DT)          ## shallow copy DT
         DT[, newcol := 1L]        ## does not affect the original DT 
         DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
         DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                                   ## also get modified.
     }
    
    #P16#
     bar <- function(DT) {
         DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
         DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
     }
    
    #P17#
    #P18#
    #P19#
  3. Agreguj podczas dołączania:

    Załóżmy, że masz dwie tabele danych w następujący sposób:

     DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
     #    x y z
     # 1: 1 a 1
     # 2: 1 a 2
     # 3: 1 b 3
     # 4: 1 b 4
     # 5: 2 a 5
     # 6: 2 a 6
     # 7: 2 b 7
     # 8: 2 b 8
     DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
     #    x y mul
     # 1: 1 a   4
     # 2: 2 b   3
    

    I chciałbyś otrzymać sum(z) * mul za każdy wiersz w DT2 podczas łączenia za pomocą kolumn x,y. Możemy:

      1. zagreguj DT1, aby uzyskać sum(z), 2) wykonaj połączenie i 3) pomnóż (lub)

        sposób na tabelę danych

        DT1[, .(z = suma(z)), keyby = .(x,y)][DT2][, z := z*mul][]

        odpowiednik dplyr

        DF1 %›% group_by(x, y) %›% podsumowania(z = suma(z)) %›% Right_join(DF2) %›% mutate(z = z * mul)

      1. zrób to wszystko za jednym razem (używając funkcji by = .EACHI):

        DT1[DT2, lista(z=suma(z) * mul), by = .EACHI]

    Jaka jest zaleta?

    • Nie musimy przydzielać pamięci dla wyniku pośredniego.

    • Nie musimy dwukrotnie grupować/haszować (jeden do agregacji, drugi do łączenia).

    • A co ważniejsze, operacja, którą chcieliśmy wykonać, jest jasna, patrząc na j w (2).

    Sprawdź ten post, aby uzyskać szczegółowe wyjaśnienie by = .EACHI. Nie powstają żadne wyniki pośrednie, a łączenie i agregacja są wykonywane za jednym razem.

    Spójrz na to, to i to posty dotyczące rzeczywistych scenariuszy użycia.

    W dplyr musiałbyś dołączyć i najpierw agregować lub agregować, a następnie dołączać, przy czym żadne z nich nie jest tak wydajny pod względem pamięci (co z kolei przekłada się na szybkość).

  4. Aktualizacja i dołączenie:

    Rozważ kod data.table pokazany poniżej:

     DT1[DT2, col := i.mul]
    

    dodaje/aktualizuje kolumnę col DT1 za pomocą mul z DT2 w tych wierszach, w których kluczowa kolumna DT2 odpowiada DT1. Nie sądzę, że istnieje dokładny odpowiednik tej operacji w dplyr, tj. bez unikania operacji *_join, która musiałaby skopiować cały DT1 tylko po to, aby dodać do niego nową kolumnę, co jest niepotrzebne.

    Sprawdź ten post, aby zapoznać się z prawdziwym scenariuszem użycia.

Podsumowując, ważne jest, aby zdać sobie sprawę, że każdy szczegół optymalizacji ma znaczenie. Jak powiedziałaby Grace Hopper: Pilnuj swoich nanosekund!

3. Składnia

Przyjrzyjmy się teraz składni. Hadley skomentował tutaj:

Tabele danych są niezwykle szybkie, ale myślę, że ich zwięzłość trudniej się ich uczy, a kod, który z nich korzysta, jest trudniejszy do odczytania po napisaniu...

Uważam tę uwagę za bezsensowną, ponieważ jest ona bardzo subiektywna. Być może możemy spróbować porównać spójność składni. Porównamy składnię data.table i dplyr obok siebie.

Będziemy pracować z fikcyjnymi danymi pokazanymi poniżej:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. Podstawowe operacje agregacji/aktualizacji.

     # case (a)
     DT[, sum(y), by = z]                       ## data.table syntax
     DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
     DT[, y := cumsum(y), by = z]
     ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
    
     # case (b)
     DT[x > 2, sum(y), by = z]
     DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
     DT[x > 2, y := cumsum(y), by = z]
     ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
    
     # case (c)
     DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
     DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
     DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
     DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
    
    • Składnia data.table jest zwarta, a dplyr dość gadatliwy. Rzeczy są mniej więcej równoważne w przypadku (a).

    • W przypadku (b) podczas podsumowywania musieliśmy użyć filter() w dplyr. Jednak podczas aktualizacji musieliśmy przenieść logikę do środka mutate(). Jednakże w data.table wyrażamy obie operacje w oparciu o tę samą logikę - operujemy na wierszach, gdzie x > 2, ale w pierwszym przypadku otrzymujemy sum(y), natomiast w drugim przypadku aktualizujemy te wiersze o y z jego skumulowaną sumą.

      To właśnie mamy na myśli, gdy mówimy, że DT[i, j, by] forma jest spójna.

    • Podobnie w przypadku (c), gdy mamy warunek if-else, jesteśmy w stanie wyrazić logikę tak jak jest zarówno w data.table, jak i dplyr. Jeśli jednak chcielibyśmy zwrócić tylko te wiersze, w których warunek if jest spełniony, a w przeciwnym razie pominąć, nie możemy bezpośrednio użyć summarise() (AFAICT). Musimy najpierw filter() a potem podsumować, ponieważ summarise() zawsze oczekuje pojedynczej wartości.

      Chociaż zwraca ten sam wynik, użycie tutaj filter() sprawia, że ​​faktyczna operacja jest mniej oczywista.

      Równie dobrze byłoby możliwe użycie filter() również w pierwszym przypadku (nie wydaje mi się to oczywiste), ale chodzi mi o to, że nie powinniśmy tego robić.

  2. Agregacja/aktualizacja w wielu kolumnach

     # case (a)
     DT[, lapply(.SD, sum), by = z]                     ## data.table syntax
     DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
     DT[, (cols) := lapply(.SD, sum), by = z]
     ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
    
     # case (b)
     DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
     DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
    
     # case (c)
     DT[, c(.N, lapply(.SD, sum)), by = z]     
     DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
    
    • W przypadku (a) kody są mniej więcej równoważne. data.table wykorzystuje znaną funkcję bazową lapply(), podczas gdy dplyr wprowadza *_each() wraz z wieloma funkcjami do funs().

    • data.table := wymaga podania nazw kolumn, podczas gdy dplyr generuje je automatycznie.

    • W przypadku (b) składnia dplyr jest stosunkowo prosta. Ulepszanie agregacji/aktualizacji wielu funkcji znajduje się na liście data.table.

    • Jednak w przypadku (c) dplyr zwróci n() tyle samo kolumn, a nie tylko raz. W data.table wystarczy zwrócić listę w j. Każdy element listy stanie się w wyniku kolumną. Możemy więc ponownie użyć znanej funkcji bazowej c(), aby połączyć .N z list, która zwraca list.

    Uwaga: ponownie w data.table wystarczy zwrócić listę w j. Każdy element listy stanie się w rezultacie kolumną. Aby to osiągnąć, możesz użyć funkcji podstawowych c(), as.list(), lapply(), list() itd., bez konieczności uczenia się nowych funkcji.

    Będziesz musiał nauczyć się tylko zmiennych specjalnych - przynajmniej .N i .SD. Odpowiednikami w dplyr są n() i .

  3. Dołącza

    dplyr udostępnia osobne funkcje dla każdego typu łączenia, gdzie data.table umożliwia łączenie przy użyciu tej samej składni DT[i, j, by] (i nie bez powodu). Alternatywnie udostępnia także równoważną funkcję merge.data.table().

     setkey(DT1, x, y)
    
     # 1. normal join
     DT1[DT2]            ## data.table syntax
     left_join(DT2, DT1) ## dplyr syntax
    
     # 2. select columns while join    
     DT1[DT2, .(z, i.mul)]
     left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    
     # 3. aggregate while join
     DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
     DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
         inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
    
     # 4. update while join
     DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
     ??
    
     # 5. rolling join
     DT1[DT2, roll = -Inf]
     ??
    
     # 6. other arguments to control output
     DT1[DT2, mult = "first"]
     ??
    
  • Niektórzy mogą uznać, że osobna funkcja dla każdego łączenia jest o wiele ładniejsza (lewe, prawe, wewnętrzne, anty, pół itp.), podczas gdy innym może się spodobać DT[i, j, by] lub merge() w data.table, które są podobne do podstawowego R.

  • Jednak połączenia dplyr właśnie to robią. Nic więcej. Nic mniej.

  • data.tables mogą wybierać kolumny podczas łączenia (2), a w dplyr będziesz musiał najpierw select() na obu data.frames przed dołączeniem, jak pokazano powyżej. W przeciwnym razie zmaterializowałbyś połączenie z niepotrzebnymi kolumnami tylko po to, aby je później usunąć, a to jest nieefektywne.

  • data.tables może agregować podczas dołączania przy użyciu by = .EACHI funkcji (3), a także aktualizować podczas dołączania (4). Po co materializować cały wynik łączenia, aby dodać/zaktualizować tylko kilka kolumn?

  • data.table umożliwia złączenia kroczące (5) - przewijanie do przodu, LOCF, przewiń do tyłu, NOCB, najbliższy.

  • data.table ma również argument mult =, który wybiera pierwsze, ostatnie lub wszystkie dopasowania (6).

  • data.table zawiera argument allow.cartesian = TRUE chroniący przed przypadkowymi nieprawidłowymi połączeniami.

Po raz kolejny składnia jest zgodna z DT[i, j, by] z dodatkowymi argumentami pozwalającymi na dalszą kontrolę wyjścia.

  1. do()...

    Podsumowanie dplyr zostało specjalnie zaprojektowane dla funkcji zwracających pojedynczą wartość. Jeśli Twoja funkcja zwróci wiele/nierównych wartości, będziesz musiał skorzystać z do(). Musisz wcześniej wiedzieć o wszystkich wartościach zwracanych przez funkcje.

     DT[, list(x[1], y[1]), by = z]                 ## data.table syntax
     DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
     DT[, list(x[1:2], y[1]), by = z]
     DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
    
     DT[, quantile(x, 0.25), by = z]
     DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
     DT[, quantile(x, c(0.25, 0.75)), by = z]
     DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
    
     DT[, as.list(summary(x)), by = z]
     DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
    
  • Odpowiednik .SD to .

  • W data.table możesz wrzucić praktycznie wszystko do j - jedyną rzeczą, o której należy pamiętać, jest zwrócenie listy, dzięki czemu każdy element listy zostanie przekonwertowany na kolumnę.

  • W dplyr nie można tego zrobić. Musisz skorzystać z do() w zależności od tego, jak pewny jesteś, czy Twoja funkcja zawsze zwróci pojedynczą wartość. I to jest dość powolne.

Po raz kolejny składnia data.table jest zgodna z DT[i, j, by]. Możemy po prostu wrzucać wyrażenia w j, nie martwiąc się o te rzeczy.

Spójrz na to pytanie SO i ten. Zastanawiam się, czy możliwe byłoby wyrażenie odpowiedzi tak prosto, używając składni dplyr ...

Podsumowując, szczególnie podkreśliłem kilka przypadków, w których składnia dplyr jest albo nieefektywna, ograniczona, albo nie ułatwia wykonywania operacji. Dzieje się tak szczególnie dlatego, że data.table spotyka się z dużą krytyką trudniejszej do odczytania/nauki składni (jak ta wklejona/linkowana powyżej). Większość postów dotyczących dplyr mówi o najprostszych operacjach. I to jest świetne. Ale ważne jest, aby zdać sobie sprawę z jego ograniczeń składniowych i funkcjonalnych, a nie widziałem jeszcze postu na ten temat.

data.table ma również swoje dziwactwa (niektóre z nich, jak wskazałem, staramy się naprawić). Próbujemy także ulepszyć łączenia data.table, jak podkreśliłem tutaj.

Ale należy również wziąć pod uwagę liczbę funkcji, których brakuje dplyr w porównaniu z data.table.

4. Funkcje

Większość funkcji wskazałem tutaj, a także w tym poście. Ponadto:

  • fread - szybki czytnik plików jest dostępny już od dłuższego czasu.

  • fwrite — dostępny jest teraz równoległy szybki zapis plików. Zobacz ten post, aby uzyskać szczegółowe wyjaśnienie dotyczące implementacji oraz #1664 do śledzenia dalszego rozwoju sytuacji.

  • Automatyczne indeksowanie — kolejna przydatna funkcja służąca do wewnętrznej optymalizacji podstawowej składni języka R.

  • Grupowanie ad hoc: dplyr automatycznie sortuje wyniki, grupując zmienne podczas summarise(), co nie zawsze jest pożądane.

  • Liczne zalety złączeń data.table (pod względem szybkości, wydajności pamięci i składni), o których mowa powyżej.

  • Złączenia nierówne: umożliwia złączenia przy użyciu innych operatorów <=, <, >, >= wraz ze wszystkimi innymi zaletami złączy data.table.

  • Nakładające się łączenia zakresów zostały niedawno zaimplementowane w data.table. Sprawdź ten post, aby zapoznać się z omówieniem testów porównawczych.

  • setorder() funkcja w data.table, która pozwala na naprawdę szybką zmianę kolejności data.tables przez odwołanie.

  • dplyr zapewnia interfejs z bazami danych przy użyciu tej samej składni, która data.table obecnie tego nie robi.

  • data.table zapewnia szybsze odpowiedniki operacji na setach (napisane przez Jana Góreckiego) - fsetdiff, fintersect, funion i fsetequal z dodatkowym argumentem all (jak w SQL).

  • data.table ładuje się czysto, bez ostrzeżeń maskujących i ma mechanizm opisany tutaj dla [.data.frame zgodności po przekazaniu do dowolnego pakietu R. dplyr zmienia podstawowe funkcje filter, lag i [, co może powodować problemy; np. tutaj i tutaj.


Wreszcie:

  • W przypadku baz danych - nie ma powodu, dla którego data.table nie mogłaby zapewnić podobnego interfejsu, ale nie jest to teraz priorytetem. Może się to pogorszyć, jeśli użytkownikom bardzo spodoba się ta funkcja… nie jestem pewien.

  • O równoległości - Wszystko jest trudne, dopóki ktoś nie pójdzie dalej i tego nie zrobi. Oczywiście będzie to wymagało wysiłku (bezpieczeństwo wątków).

    • Progress is being made currently (in v1.9.7 devel) towards parallelising known time consuming parts for incremental performance gains using OpenMP.
person Community    schedule 31.12.2014
comment
... Zatem to, co robisz (w sposób dorozumiany), to podzbiór wierszy i do tego właśnie służy filter w dplyr. - person talat; 31.12.2014
comment
@docendodiscimus, przepraszam, ale jaki jest idiomatyczny sposób zrobienia DT[, if(any(x>1)) y[1], by=z] ponownie? Jestem zmieszana. Czy powinienem, czy nie powinienem używać tutaj filtra? - person Arun; 31.12.2014
comment
summarise() nie ma jeszcze kolumn listy, ale mutate() ma i ten sam problem z . istnieje (tak, to nie jest idiomatyczne, ale błąd to błąd). - person Arun; 31.12.2014
comment
Powinieneś użyć filtru, ponieważ dzielisz wiersze na podzbiory. (W twoim przykładzie nie usuwasz żadnej grupy z, ponieważ obie mają any(x > 1) == TRUE. Zatem najprawdopodobniej będziesz chciał zrobić: DF %>% group_by(z) %>% filter(any(x > 1)) %>% summarise(y = y[1]). - person talat; 31.12.2014
comment
Komentarze nie są przeznaczone do dłuższej dyskusji; ta rozmowa została przeniesiono do czatu. - person Taryn; 31.12.2014
comment
@bluefeet: Nie sądzę, że wyświadczyłeś nam wielką przysługę, przenosząc tę ​​dyskusję do czatu. Miałem wrażenie, że Arun był jednym z programistów, co mogło zaowocować przydatnymi spostrzeżeniami. - person IRTFM; 06.01.2015
comment
@BondedDust Problem polegał na tym, że pod tym postem pojawiło się ponad 30 komentarzy, przez co niezwykle trudno było znaleźć w nich cokolwiek, co byłoby pomocne. Wszystkie komentarze zostały przeniesione na czat, a następnie w razie potrzeby można je było uwzględnić w odpowiedzi. Dłuższe dyskusje nie powinny odbywać się w komentarzach, po to jest chat. - person Taryn; 06.01.2015
comment
Kiedy wszedłem na czat, korzystając z Twojego linku, okazało się, że cały materiał następujący po komentarzu rozpoczynającym się od „Powinieneś użyć filtra” zniknął. Czy brakuje mi czegoś na temat mechanizmu SO-chat? - person IRTFM; 06.01.2015
comment
@BondedDust Nie było żadnych dodatkowych komentarzy tutaj ani na czacie po komentarzu Powinieneś filtrować.... Wygląda na to, że nikt nie kontynuował dyskusji ani tutaj, ani na czacie. - person Taryn; 06.01.2015
comment
Myślę, że wszędzie tam, gdzie używasz przypisania przez odniesienie (:=), odpowiednik dplyr powinien również używać <- jak w DF <- DF %>% mutate... zamiast tylko DF %>% mutate... - person David Arenburg; 07.01.2015
comment
Jeśli chodzi o składnię. Wierzę, że dplyr może być łatwiejszy dla użytkowników, którzy używali składni plyr, ale data.table może być łatwiejszy dla użytkowników, którzy używali do wykonywania zapytań składni w językach takich jak SQL i związanej z nią algebry relacyjnej, która polega w całości na transformacji danych tabelarycznych. @Arun powinieneś zauważyć, że operatory zestawów można bardzo łatwo wykonać, zawijając funkcję data.table i oczywiście przynoszą znaczne przyspieszenie. - person jangorecki; 14.01.2015
comment
dodaj dplyr do funkcji i natychmiast zobacz wykładniczo złożony kod. powinieneś dodać je również do swojego porównania - person MySchizoBuddy; 10.06.2016
comment
@MySchizoBuddy, może sedno pokazujące, o czym konkretnie mówisz? Czy to wersja SE, do której przypisujesz? - person Arun; 15.06.2016
comment
@Arun tak, gdy użycie dplyr jest używane wewnątrz funkcji, musisz użyć wersji SE wraz z lazyeval::interp() Czy data.table ma podobne problemy podczas używania wewnątrz funkcji - person MySchizoBuddy; 16.06.2016
comment
@MySchizoBuddy, słuszna uwaga. .SD w data.table wraz z .SDcols jest znacznie prostsze niż interp() AFAIU, przynajmniej w większości scenariuszy. To razem z użyciem get() i mget() (w rzadkich przypadkach) wystarczy w ~ 99% wszystkich scenariuszy. [data.table musi zaimplementować jeszcze jedną rzecz w argumencie i - lepsze reguły określania zakresu, ale jest to niezwykle rzadki scenariusz.] Pomyślę o sposobie edycji tego w poście, jeśli uda mi się zrozumieć interp(). Dzięki. - person Arun; 16.06.2016
comment
@Arun Bardzo chciałbym, aby data.table obsługiwał backendy baz danych! Wyobraź sobie składnię data.table na backendzie Redshift. Być może z lokalną warstwą buforowania, aby uniknąć kosztownych powtarzających się zapytań, jeśli to możliwe. Wow. - person andrew; 08.08.2018
comment
Czytałem ten post wiele razy i bardzo pomógł mi zrozumieć plik data.table i móc lepiej go używać. W większości przypadków wolę data.table od dplyr, pand lub PL/pgSQL. Nie mogłam jednak przestać myśleć, jak to wyrazić. Składnia nie jest łatwa, jasna i gadatliwa. Tak naprawdę, nawet po częstym korzystaniu z data.table, często nadal mam trudności ze zrozumieniem własnego kodu, który napisałem dosłownie tydzień temu. Jest to żywy przykład języka przeznaczonego wyłącznie do zapisu. en.wikipedia.org/wiki/Write-only_language Miejmy więc nadzieję, że pewnego dnia będziemy mogli używać dplyr na data.table. - person Ufos; 18.12.2018
comment
W c) ib) punktu 1. Podstawowe operacje agregacji/aktualizacji. Nie otrzymuję równoważnych wyników z dplyr i dt. - person its.me.adam; 05.03.2020
comment
Właściwie wiele kodu dplyr nie ma już zastosowania (z powodu aktualizacji?)... Ta odpowiedź mogłaby wymagać odświeżenia, ponieważ jest tak świetnym zasobem. - person its.me.adam; 05.03.2020
comment
@its.me.adam problem polega na tym, że kod ten zmieniał się wielokrotnie w czasie, więc jego aktualizacja jest w rzeczywistości bezcelowa, ponieważ po pewnym czasie konieczna będzie ponowna aktualizacja. Wyszukiwanie ręcznych wpisów (dla bieżącego API) nie jest takie trudne. - person jangorecki; 20.10.2020
comment
@Ufos możesz teraz używać składni dplyr przetłumaczonej na data.table (może nie jest to najszybszy kod, ale od czegoś trzeba zacząć); cran.r-project.org/web/packages/dtplyr/index. HTML - person llrs; 10.06.2021

Oto moja próba kompleksowej odpowiedzi z perspektywy dplyr, zgodnie z ogólnym zarysem odpowiedzi Aruna (ale nieco zmieniona w oparciu o różne priorytety).

Składnia

Składnia jest pewna subiektywna, ale podtrzymuję swoje stwierdzenie, że zwięzłość pliku data.table utrudnia naukę i czytanie. Dzieje się tak częściowo dlatego, że dplyr rozwiązuje znacznie łatwiejszy problem!

Jedną z naprawdę ważnych rzeczy, które robi dla Ciebie dplyr, jest to, że ogranicza Twoje opcje. Twierdzę, że większość problemów z pojedynczą tabelą można rozwiązać za pomocą zaledwie pięciu kluczowych czasowników: filtruj, wybieraj, mutuj, porządkuj i podsumowuj, wraz z przysłówkiem „według grupy”. To ograniczenie jest bardzo pomocne podczas nauki manipulacji danymi, ponieważ pomaga uporządkować myślenie o problemie. W dplyr każdy z tych czasowników jest mapowany na pojedynczą funkcję. Każda funkcja ma jedno zadanie i jest łatwa do zrozumienia oddzielnie.

Tworzysz złożoność, łącząc te proste operacje razem z %>%. Oto przykład z jednego z postów, do których Arun linkuje:

diamonds %>%
  filter(cut != "Fair") %>%
  group_by(cut) %>%
  summarize(
    AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = n()
  ) %>%
  arrange(desc(Count))

Nawet jeśli nigdy wcześniej nie widziałeś dplyr (ani nawet R!), nadal możesz zrozumieć, co się dzieje, ponieważ wszystkie funkcje są czasownikami angielskimi. Wadą czasowników angielskich jest to, że wymagają one więcej wpisania niż [, ale myślę, że można to w dużym stopniu złagodzić dzięki lepszemu autouzupełnianiu.

Oto równoważny kod data.table:

diamondsDT <- data.table(diamonds)
diamondsDT[
  cut != "Fair", 
  .(AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = .N
  ), 
  by = cut
][ 
  order(-Count) 
]

Trudniej jest zastosować się do tego kodu, jeśli nie znasz już data.table. (Nie mogłem też wymyślić, jak wciąć powtórzone [ w sposób, który będzie dobrze wyglądał na moje oko). Osobiście, kiedy patrzę na kod, który napisałem 6 miesięcy temu, mam wrażenie, że patrzę na kod napisany przez nieznajomego, więc wolę prosty, choć szczegółowy kod.

Dwa inne drobne czynniki, które moim zdaniem nieznacznie zmniejszają czytelność:

  • Ponieważ prawie każda operacja na tabeli danych wykorzystuje [, potrzebny jest dodatkowy kontekst, aby dowiedzieć się, co się dzieje. Na przykład, czy x[y] łączy dwie tabele danych, czy wyodrębnia kolumny z ramki danych? Jest to tylko mały problem, ponieważ w dobrze napisanym kodzie nazwy zmiennych powinny sugerować, co się dzieje.

  • Podoba mi się, że group_by() to osobna operacja w dplyr. Zasadniczo zmienia to obliczenia, więc myślę, że powinno być oczywiste podczas przeglądania kodu i łatwiej jest wykryć group_by() niż argument by do [.data.table.

Podoba mi się też to, że potok nie ogranicza się tylko do jednego pakietu. Możesz zacząć od uporządkowania danych za pomocą tidyr, a zakończyć za pomocą fabuła w ggvis. I nie jesteś ograniczony do pakietów, które piszę — każdy może napisać funkcję, która stanowi płynną część potoku manipulacji danymi. Właściwie wolę poprzedni kod data.table przepisany za pomocą %>%:

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

Pomysł potokowania za pomocą %>% nie ogranicza się tylko do ramek danych i można go łatwo uogólnić na inne konteksty: interaktywna grafika internetowa, przeszukiwanie sieci, podstawy, kontrakty wykonawcze, ...)

Pamięć i wydajność

Zebrałem je w jedno, ponieważ dla mnie nie są one aż tak ważne. Większość użytkowników R pracuje z mniej niż 1 milionem wierszy danych, a dplyr jest wystarczająco szybki dla takiego rozmiaru danych, że nie jesteś świadomy czasu przetwarzania. Optymalizujemy dplyr pod kątem wyrazistości na średnich danych; możesz użyć data.table, aby uzyskać surową prędkość w przypadku większych danych.

Elastyczność dplyr oznacza również, że możesz łatwo dostosować charakterystykę wydajności, używając tej samej składni. Jeśli wydajność dplyr z backendem ramki danych nie jest dla Ciebie wystarczająco dobra, możesz użyć backendu data.table (aczkolwiek z nieco ograniczonym zestawem funkcjonalności). Jeśli dane, z którymi pracujesz, nie mieszczą się w pamięci, możesz użyć zaplecza bazy danych.

Wszystko to powiedziawszy, wydajność dplyr poprawi się w dłuższej perspektywie. Z pewnością wdrożymy kilka świetnych pomysłów z data.table, takich jak porządkowanie radix i używanie tego samego indeksu dla złączeń i filtrów. Pracujemy również nad równoległością, abyśmy mogli wykorzystać wiele rdzeni.

Cechy

Kilka rzeczy, nad którymi planujemy pracować w 2015 roku:

  • pakiet readr, aby ułatwić pobieranie plików z dysku do pamięci, analogicznie do fread().

  • Bardziej elastyczne łączenia, w tym obsługa połączeń innych niż równorzędne.

  • Bardziej elastyczne grupowanie, takie jak próbki bootstrap, zestawienia i inne

Inwestuję także czas w ulepszanie złączy baz danych R, czyli możliwości komunikowania się z apis sieciowy i ułatwienie zeskrob strony HTML.

person hadley    schedule 08.01.2015
comment
Na marginesie, zgadzam się z wieloma twoimi argumentami (chociaż sam wolę składnię data.table), ale możesz z łatwością użyć %>% do potokowania operacji data.table, jeśli nie lubisz stylu [. %>% nie jest specyficzne dla dplyr, raczej pochodzi z osobnego pakietu (którego tak się składa, że ​​jesteś także współautorem), więc nie jestem pewien, czy rozumiem, co próbujesz powiedzieć w większości swojej Składni< /b> akapit. - person David Arenburg; 08.01.2015
comment
@DavidArenburg Dobra uwaga. Przepisałem składnię, aby, mam nadzieję, lepiej wyjaśnić, o co mi chodzi i podkreślić, że możesz używać %>% z data.table - person hadley; 08.01.2015
comment
Dziękuję Hadley, to przydatna perspektywa. Ponowne wcięcie zwykle robię DT[\n\texpression\n][\texpression\n] (Gist), co faktycznie działa całkiem nieźle Dobrze. Zachowuję odpowiedź Aruna jako odpowiedź, ponieważ on bardziej bezpośrednio odpowiada na moje konkretne pytania, które nie dotyczą tak bardzo dostępności składni, ale myślę, że to dobra odpowiedź dla osób próbujących uzyskać ogólne wyczucie różnic/podobieństw między dplyr i data.table. - person BrodieG; 08.01.2015
comment
Drobna kwestia: odróżniając złączenia od podzbioru kolumn data.frame, możesz wykonać X[J(Y)] lub X[.(Y)]. - person Arun; 08.01.2015
comment
@BrodieG jest znacznie lepiej, ale nadal tego nie kocham. - person hadley; 08.01.2015
comment
Słusznie. Chociaż jestem szczęśliwym data.table użytkownikiem (i okazjonalnym dplyr użytkownikiem), od czasu do czasu czuję, że data.table próbuję zrobić za dużo w środku [. - person BrodieG; 08.01.2015
comment
@BrodieG umieszcza wszystko, co jest potrzebne do algebry relacji, w formularzach operatora [ zapytania data.table, które mogą być bezpośrednio analogiczne (i głębokie rozszerzenie) do zapytań SQL< /b>. W tej chwili nie widzę, aby żaden z [ argumentów był zbędny (z wyjątkiem drop) i tak, jest ich już wiele :) - person jangorecki; 08.01.2015
comment
@JanGorecki, właściwie nie mam żadnych problemów z liczbą argumentów data.table (źle to sformułowałem), ale na przykład czasami czuję, że i nie powinno być używane zarówno do łączenia, jak i indeksowania, chociaż zwarta składnia jest ładna ( w przeciwieństwie do merge), więc w zasadzie mam co do tego mieszane uczucia. - person BrodieG; 08.01.2015
comment
Po co pracować nad fastread, skoro jest już fread()? Czy nie lepiej byłoby spędzić czas na ulepszaniu funkcji fread() lub pracy nad innymi (niedopracowanymi) rzeczami? - person EDi; 28.01.2015
comment
@EDi, ponieważ fastread i fread mają różne cele. fastread zapewni także interfejs API C++, dzięki czemu będziesz mógł używać tego samego kodu bazowego do czytania w nowych formatach plików - person hadley; 28.01.2015
comment
Interfejs API data.table opiera się na masowym nadużyciu notacji []. To jest jego największa siła i największa słabość. - person Paul; 28.01.2016
comment
@DavidArenburg używając operatora potoku jak w powyższym przykładzie zauważyłeś, że są 4 kropki(.) Dwie z nich dotyczą samego obiektu z danymi, a pozostałe dwie nie. To jest dla mnie po prostu mylące. - person MySchizoBuddy; 20.06.2016
comment
Dziękujemy za zgodę na wykorzystanie data.table na dużych ilościach danych! Już samo to było warte odpowiedzi. - person piccolbo; 01.05.2018
comment
Wolę fragment data.table, który pokazujesz, od dplyr : i użyłem data.table tylko raz do małego zadania 18 miesięcy temu. Całkiem czytelny i zwięzły. Chciałbym, żeby istniało rozsądne podobieństwo w python-krainie. Pakiet datatable w python powoli się poprawia, ale nie zaczyna dotykać r data.table - person WestCoastProjects; 30.03.2020

W bezpośredniej odpowiedzi na Tytuł pytania...

dplyr zdecydowanie robi rzeczy, których data.table nie może.

Twój punkt nr 3

dplyr streszcza (lub będzie) potencjalne interakcje z bazą danych

jest bezpośrednią odpowiedzią na twoje własne pytanie, ale nie jest podniesiona do wystarczająco wysokiego poziomu. dplyr jest naprawdę rozszerzalnym interfejsem dla wielu mechanizmów przechowywania danych, podczas gdy data.table jest rozszerzeniem jednego.

Spójrz na dplyr jak na niezależny interfejs zaplecza, w którym wszystkie cele używają tej samej gramatyki, gdzie możesz dowolnie rozszerzać cele i procedury obsługi. data.table jest, z dplyr perspektywy, jednym z tych celów.

Nigdy (mam nadzieję) nie nadejdzie dzień, w którym data.table będą podejmowane próby przetłumaczenia Twoich zapytań w celu utworzenia instrukcji SQL obsługujących magazyny danych znajdujące się na dysku lub w sieci.

dplyr prawdopodobnie może zrobić rzeczy, których data.table nie zrobi lub może nie zrobić równie dobrze.

Opierając się na projekcie pracy w pamięci, data.table może mieć znacznie trudniejsze rozszerzenie się na równoległe przetwarzanie zapytań niż dplyr.


W odpowiedzi na pytania wewnątrz ciała...

Wykorzystanie

Czy istnieją zadania analityczne, które są dużo łatwiejsze do kodowania przy użyciu jednego lub drugiego pakietu dla osób zaznajomionych z pakietami (tj. pewna kombinacja wymaganych naciśnięć klawiszy w porównaniu z wymaganym poziomem ezoteryki, gdzie mniej każdego z nich to dobra rzecz).

Może to wydawać się chwytem marketingowym, ale prawdziwa odpowiedź brzmi: nie. Wydaje się, że osoby zaznajomione z narzędziami używają tego, które jest im najbardziej znane, lub tego, które jest odpowiednie do wykonywanej pracy. Mając to na uwadze, czasami chcesz przedstawić określoną czytelność, czasami poziom wydajności, a kiedy potrzebujesz wystarczająco wysokiego poziomu obu, możesz po prostu potrzebować innego narzędzia, które będzie pasować do tego, co już masz, aby uzyskać jaśniejsze abstrakcje .

Wydajność

Czy istnieją zadania analityczne, które w jednym pakiecie są wykonywane znacznie (tj. ponad 2 razy) wydajniej niż w innym?

Ponownie, nie. data.table wyróżnia się wydajnością we wszystkim, co to robi, gdy dplyr ponosi ciężar bycia ograniczonym pod pewnymi względami do bazowego magazynu danych i zarejestrowanych procedur obsługi.

Oznacza to, że gdy napotkasz problem z wydajnością data.table, możesz być pewien, że ma on miejsce w Twojej funkcji zapytania, a jeśli jest w rzeczywistości wąskim gardłem w data.table, to wygrałeś sobie radość ze złożenia raportu . Dzieje się tak również wtedy, gdy dplyr używa data.table jako back-endu; możesz zobaczyć pewne koszty ogólne z dplyr, ale jest prawdopodobne, że to Twoje zapytanie.

Kiedy dplyr ma problemy z wydajnością backendów, możesz je obejść, rejestrując funkcję do oceny hybrydowej lub (w przypadku baz danych) manipulując wygenerowanym zapytaniem przed wykonaniem.

Zobacz także zaakceptowaną odpowiedź na kiedy plyr jest lepszy niż data.table ?

person Thell    schedule 16.11.2014
comment
Nie możesz dplyr zawinąć tabeli data.table za pomocą tbl_dt? Dlaczego po prostu nie wykorzystać tego, co najlepsze z obu światów? - person aaa90210; 09.12.2014
comment
@aaa90210, zobacz ten post - person BrodieG; 17.12.2014
comment
Zapominasz wspomnieć o odwrotnym stwierdzeniu data.table zdecydowanie robi rzeczy, których dplyr nie może, co również jest prawdą. - person jangorecki; 05.01.2015
comment
@JanGorecki Dzieje się tak, ponieważ nie jestem świadomy możliwości, którą ma data.table, a której dplyr nie jest w stanie wykonać ani bezpośrednio, ani za pośrednictwem procedur obsługi. Istnieją funkcje (omówione pod względem szybkości, pamięci i składni), które zostały omówione jako różnice w odpowiedzi na jakościową (słabą) część pytania PO nie przypominam sobie, żebym widział możliwości, których nie można/nie można uogólnić i wyodrębnić w warstwie. - person Thell; 05.01.2015
comment
Odpowiedź Aruna dobrze to wyjaśnia. Najważniejsze (pod względem wydajności) byłyby fread, aktualizacja przez odniesienie, złączenia kroczące, złączenia nakładające się. Uważam, że nie ma żadnego pakietu (nie tylko dplyr), który mógłby konkurować z tymi funkcjami. Dobrym przykładem może być ostatni slajd z tej prezentacji. - person jangorecki; 05.01.2015
comment
Całkowicie data.table jest powodem, dla którego nadal używam R. W przeciwnym razie użyłbym pand. Jest jeszcze lepszy/szybszy niż pandy. - person marbel; 02.12.2016
comment
Lubię data.table ze względu na jego prostotę i podobieństwo do struktury składni SQL. Moja praca polega na codziennym wykonywaniu bardzo intensywnych analiz danych ad hoc i grafice na potrzeby modelowania statystycznego i naprawdę potrzebuję narzędzia wystarczająco prostego, aby wykonywać skomplikowane rzeczy. Teraz w mojej codziennej pracy mogę ograniczyć mój zestaw narzędzi do tabeli danych i siatki do wykresów. Podaj przykład, że mogę nawet wykonywać takie operacje: $DT[group==1,y_hat:=predict(fit1,data=.SD),]$, co jest naprawdę fajne i uważam to za wielką zaletę SQL w klasyczne środowisko R. - person xappppp; 31.12.2016

Czytając odpowiedzi Hadleya i Aruna można odnieść wrażenie, że ci, którzy preferują składnię dplyr, w niektórych przypadkach musieliby przejść na data.table lub pójść na kompromis w przypadku długich czasów działania.

Ale jak niektórzy już wspomnieli, dplyr może używać data.table jako backendu. Osiąga się to za pomocą pakietu dtplyr, który niedawno miał wersję 1.0.0 uwolnij. Nauka dtplyr nie wymaga praktycznie żadnego dodatkowego wysiłku.

Używając dtplyr, używa się funkcji lazy_dt() do zadeklarowania leniwej tabeli danych, po czym używana jest standardowa składnia dplyr do określania operacji na niej. Wyglądałoby to mniej więcej tak:

new_table <- mtcars2 %>% 
  lazy_dt() %>%
  filter(wt < 5) %>% 
  mutate(l100k = 235.21 / mpg) %>% # liters / 100 km
  group_by(cyl) %>% 
  summarise(l100k = mean(l100k))

  new_table

#> Source: local data table [?? x 2]
#> Call:   `_DT1`[wt < 5][, `:=`(l100k = 235.21/mpg)][, .(l100k = mean(l100k)), 
#>     keyby = .(cyl)]
#> 
#>     cyl l100k
#>   <dbl> <dbl>
#> 1     4  9.05
#> 2     6 12.0 
#> 3     8 14.9 
#> 
#> # Use as.data.table()/as.data.frame()/as_tibble() to access results

Obiekt new_table nie jest oceniany do czasu wywołania go as.data.table()/as.data.frame()/as_tibble(), w którym to momencie wykonywana jest podstawowa operacja data.table.

odtworzyłem analizę porównawczą przeprowadzoną przez data.table autora Matta Dowle'a w grudniu 2018, który obejmuje przypadek operacji na dużej liczbie grup. Odkryłem, że dtplyr rzeczywiście pozwala w większości osobom preferującym składnię dplyr na dalsze używanie jej, jednocześnie ciesząc się szybkością oferowaną przez data.table.

person Iyar Lin    schedule 14.06.2020
comment
prawdopodobnie nie będziesz mieć tam wielu funkcji, dla których nie ma interfejsu API w dplyr, takich jak przypisanie podrzędne przez odniesienie, łączenia kroczące, łączenia nakładające się, łączenia nierówne, aktualizacja przy łączeniu i prawdopodobnie wiele innych. - person jangorecki; 24.06.2020
comment
Muszę przyznać, że żadna z tych funkcji nie przemawia. Czy mógłbyś podać konkretne przykłady w data.table? - person Iyar Lin; 25.06.2020
comment
?data.table przykłady, wszystko, o czym wspomniałem, z wyjątkiem nakładania się dołącza. są tam - person jangorecki; 26.06.2020
comment
Aktualizację połączeń walcowanych i nakładających się można wykonać w prosty sposób z kilku części rury. - person Arthur Yip; 16.10.2020
comment
Zobacz fuzzyjoin dla złączeń innych niż równoznaczne (wydaje się mieć jeszcze więcej funkcji i funkcjonalności niż połączenia inne niż równoznaczne w data.table). - person Arthur Yip; 16.10.2020