2012-07-18 10 views
59

Myślę, że używam niepoprawnie plyr. Czy ktoś mógłby mi powiedzieć, czy jest to "wydajny" kod plyr?Dlaczego plyr jest taki wolny?

require(plyr) 
plyr <- function(dd) ddply(dd, .(price), summarise, ss=sum(volume)) 

Mały kontekst: Mam kilka dużych problemów z agregacją i zauważyłem, że każdy z nich poświęcił trochę czasu. Próbując rozwiązać te problemy, zainteresowałem się wydajnością różnych procedur agregacji w R.

Przetestowałem kilka metod agregacji - i zacząłem czekać cały dzień.

Kiedy w końcu otrzymałem wyniki, odkryłem ogromną lukę między metodą plyr a innymi - co sprawia, że ​​myślę, że zrobiłem coś nie tak.

Pobiegłem następujący kod (myślałem, że sprawdzić nowy pakiet dataframe gdy byłem na nią):

require(plyr) 
require(data.table) 
require(dataframe) 
require(rbenchmark) 
require(xts) 

plyr <- function(dd) ddply(dd, .(price), summarise, ss=sum(volume)) 
t.apply <- function(dd) unlist(tapply(dd$volume, dd$price, sum)) 
t.apply.x <- function(dd) unlist(tapply(dd[,2], dd[,1], sum)) 
l.apply <- function(dd) unlist(lapply(split(dd$volume, dd$price), sum)) 
l.apply.x <- function(dd) unlist(lapply(split(dd[,2], dd[,1]), sum)) 
b.y <- function(dd) unlist(by(dd$volume, dd$price, sum)) 
b.y.x <- function(dd) unlist(by(dd[,2], dd[,1], sum)) 
agg <- function(dd) aggregate(dd$volume, list(dd$price), sum) 
agg.x <- function(dd) aggregate(dd[,2], list(dd[,1]), sum) 
dtd <- function(dd) dd[, sum(volume), by=(price)] 

obs <- c(5e1, 5e2, 5e3, 5e4, 5e5, 5e6, 5e6, 5e7, 5e8) 
timS <- timeBasedSeq('20110101 083000/20120101 083000') 

bmkRL <- list(NULL) 

for (i in 1:5){ 
    tt <- timS[1:obs[i]] 

    for (j in 1:8){ 
    pxl <- seq(0.9, 1.1, by= (1.1 - 0.9)/floor(obs[i]/(11-j))) 
    px <- sample(pxl, length(tt), replace=TRUE) 
    vol <- rnorm(length(tt), 1000, 100) 

    d.df <- base::data.frame(time=tt, price=px, volume=vol) 
    d.dfp <- dataframe::data.frame(time=tt, price=px, volume=vol) 
    d.matrix <- as.matrix(d.df[,-1]) 
    d.dt <- data.table(d.df) 

    listLabel <- paste('i=',i, 'j=',j) 

    bmkRL[[listLabel]] <- benchmark(plyr(d.df), plyr(d.dfp), t.apply(d.df),  
         t.apply(d.dfp), t.apply.x(d.matrix), 
         l.apply(d.df), l.apply(d.dfp), l.apply.x(d.matrix), 
         b.y(d.df), b.y(d.dfp), b.y.x(d.matrix), agg(d.df), 
         agg(d.dfp), agg.x(d.matrix), dtd(d.dt), 
      columns =c('test', 'elapsed', 'relative'), 
      replications = 10, 
      order = 'elapsed') 
    } 
} 

Badanie miało sprawdzić się do 5E8, ale zajęło zbyt długo - głównie z powodu plyr. Tabela 5e5 ostateczna pokazuje problem:

$`i= 5 j= 8` 
        test elapsed relative 
15   dtd(d.dt) 4.156 1.000000 
6  l.apply(d.df) 15.687 3.774543 
7  l.apply(d.dfp) 16.066 3.865736 
8 l.apply.x(d.matrix) 16.659 4.008422 
4  t.apply(d.dfp) 21.387 5.146054 
3  t.apply(d.df) 21.488 5.170356 
5 t.apply.x(d.matrix) 22.014 5.296920 
13   agg(d.dfp) 32.254 7.760828 
14  agg.x(d.matrix) 32.435 7.804379 
12   agg(d.df) 32.593 7.842397 
10   b.y(d.dfp) 98.006 23.581809 
11  b.y.x(d.matrix) 98.134 23.612608 
9   b.y(d.df) 98.337 23.661453 
1   plyr(d.df) 9384.135 2257.972810 
2   plyr(d.dfp) 9384.448 2258.048123 

Czy to prawda? Dlaczego plyr 2250x jest wolniejszy niż data.table? Dlaczego zastosowanie nowego pakietu ramek danych nie miało znaczenia?

informacji sesja jest:

> sessionInfo() 
R version 2.15.1 (2012-06-22) 
Platform: x86_64-apple-darwin9.8.0/x86_64 (64-bit) 

locale: 
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8 

attached base packages: 
[1] stats  graphics grDevices utils  datasets methods base  

other attached packages: 
[1] xts_0.8-6  zoo_1.7-7  rbenchmark_0.3 dataframe_2.5 data.table_1.8.1  plyr_1.7.1  

loaded via a namespace (and not attached): 
[1] grid_2.15.1 lattice_0.20-6 tools_2.15.1 
+3

W przypadku stosunkowo prostych problemów manipulacji danymi/agregacji, znalazłem tabeli dane się niezwykle szybko. Jeśli może to zrobić, wcale nie jestem zaskoczony, że jest to oczywisty zwycięzca. Nie jestem wystarczająco zaznajomiony z 'plyr', aby go skomentować. – Joshua

+1

Czy obejrzałeś dokumentację dla 'plyr' i' data.table'? Jeśli dobrze pamiętam, 'plyr' działa z base-'R'' data.frame's. 'data.table' używa zupełnie innej reprezentacji, używając kolumn z kluczami i wydajnego sortowania radik. W ten sposób jest znacznie więcej bazy danych. –

+0

Mam spojrzenie - ale nie mogłem tego rozgryźć. plyr jest czymś więcej niż odrobinę wolniejszym ... zastosowanie rodziny, agresywności i przez to są bardzo szybkie - i są one podstawą. właśnie dlatego doszedłem do wniosku, że muszę popełnić jakiś błąd debiutanta z plyr. – ricardo

Odpowiedz

51

Dlaczego jest tak niska? Trochę badań znajduje się komentarz Mail Group z Sie 2011 gdzie @hadley, autor pakiet, states

To jest wadą sposób ddply zawsze pracuje z danych ramek. Będzie trochę szybciej, jeśli użyjesz podsumowania zamiast data.frame (ponieważ data.frame jest bardzo powolna), ale wciąż myślę o tym, jak przezwyciężyć to fundamentalne ograniczenie podejścia ddply .


chodzi o bycie wydajny kod plyr nie wiedziałem, albo. Po serii testów paramatycznych i benchmarkingu wygląda na to, że możemy zrobić to lepiej.

W twoim poleceniu summarize() jest po prostu funkcją pomocniczą, czystą i prostą. Możemy go zastąpić naszą własną funkcją sumowania, ponieważ nie pomaga ona w niczym, co nie jest jeszcze proste, a argumenty mogą być bardziej wyraźne. Rezultatem jest

ddply(dd[, 2:3], ~price, function(x) sum(x$volume)) 

summarize może wydawać się dobre, ale to nie jest szybsze niż proste wywołanie funkcji. To ma sens; wystarczy spojrzeć na naszą małą funkcję w stosunku do code dla summarize. Uruchamianie benchmarków za pomocą zmienionej formuły daje zauważalny zysk. Nie rozumiem, że źle używasz plyr, nie masz, to po prostu nie jest wydajne; nic, co można z tym zrobić, sprawi, że będzie tak szybki, jak inne opcje.

Moim zdaniem, zoptymalizowana funkcja nadal śmierdzi, ponieważ nie jest jasna i musi być przetwarzana mentalnie wraz z wciąż śmiesznie wolnym w porównaniu z data.table (nawet przy 60% zysku).


W tym samym thread wspomniano powyżej, w odniesieniu powolność plyr, projekt plyr2 wspomina. Od czasu pierwotnej odpowiedzi na pytanie autor plyr wypuścił dplyr jako następcę plyr. Chociaż zarówno plyr, jak i dplyr są zapowiadane jako narzędzia do manipulowania danymi, a twoim głównym deklarowanym celem jest agregacja, możesz nadal być zainteresowany wynikami testu porównawczego nowego pakietu dla porównania, ponieważ ma on ponownie opracowany backend, aby poprawić wydajność.

plyr_Original <- function(dd) ddply(dd, .(price), summarise, ss=sum(volume)) 
plyr_Optimized <- function(dd) ddply(dd[, 2:3], ~price, function(x) sum(x$volume)) 

dplyr <- function(dd) dd %.% group_by(price) %.% summarize(sum(volume))  

data_table <- function(dd) dd[, sum(volume), keyby=price] 

Pakiet dataframe została usunięta z CRAN, a następnie na podstawie testów, a także wersji funkcji matrycy.

Oto i=5, j=8 wyniki benchmarków:

$`obs= 500,000 unique prices= 158,286 reps= 5` 
        test elapsed relative 
9  data_table(d.dt) 0.074 1.000 
4   dplyr(d.dt) 0.133 1.797 
3   dplyr(d.df) 1.832 24.757 
6  l.apply(d.df) 5.049 68.230 
5  t.apply(d.df) 8.078 109.162 
8   agg(d.df) 11.822 159.757 
7   b.y(d.df) 48.569 656.338 
2 plyr_Optimized(d.df) 148.030 2000.405 
1 plyr_Original(d.df) 401.890 5430.946 

Niewątpliwie optymalizacji pomógł trochę. Spójrz na funkcje d.df; po prostu nie mogą konkurować.

Dla małej perspektywy powolności struktury data.frame są tutaj mikro-benchmarki czasów agregacji danych data_table i dplyr przy użyciu większego zestawu danych testowych (i=8,j=8).

$`obs= 50,000,000 unique prices= 15,836,476 reps= 5` 
Unit: seconds 
      expr min  lq median  uq max neval 
data_table(d.dt) 1.190 1.193 1.198 1.460 1.574 10 
     dplyr(d.dt) 2.346 2.434 2.542 2.942 9.856 10 
     dplyr(d.df) 66.238 66.688 67.436 69.226 86.641 10 

data.frame jest nadal pozostaje w kurzu. Nie tylko to, ale tutaj jest upływający system.time do wypełnienia struktury danych z danymi z badania:

`d.df` (data.frame) 3.181 seconds. 
`d.dt` (data.table) 0.418 seconds. 

Zarówno tworzenie i agregacja data.frame jest wolniejszy niż data.table.

Praca z data.frame w R jest wolniejszy niż kilka alternatyw, lecz jako benchmarki pokazują wbudowanej funkcji R dmuchać plyr z wody. Nawet zarządzanie data.frame jako dplyr, które poprawia wbudowane, nie daje optymalnej prędkości; gdzie jako data.table jest szybszy zarówno w tworzeniu i agregacji i data.table robi to, co robi podczas pracy z/na data.frames.

W końcu ...

Plyr jest powolny ze względu na sposób to współpracuje z i zarządza manipulacji data.frame.

[punt :: zobacz komentarze do pierwotnego pytania].


## R version 3.0.2 (2013-09-25) 
## Platform: x86_64-pc-linux-gnu (64-bit) 
## 
## attached base packages: 
## [1] stats  graphics grDevices utils  datasets methods base  
## 
## other attached packages: 
## [1] microbenchmark_1.3-0 rbenchmark_1.0.0  xts_0.9-7   
## [4] zoo_1.7-11   data.table_1.9.2  dplyr_0.1.2   
## [7] plyr_1.8.1   knitr_1.5.22   
## 
## loaded via a namespace (and not attached): 
## [1] assertthat_0.1 evaluate_0.5.2 formatR_0.10.4 grid_3.0.2  
## [5] lattice_0.20-27 Rcpp_0.11.0  reshape2_1.2.2 stringr_0.6.2 
## [9] tools_3.0.2 

Data-Generating gist .rmd

+0

+1. dobre sugestie. dzięki stary. Ponownie przeprowadzam testy z sugerowanym dzisiaj kodem 'plyr' i' dc'. Postawię odpowiedź, gdy skończą. Postanowiłem upuścić nieco matrycy, aby trochę przyspieszyć (przesuwanie df do matrycy nie wydawało się dodawać zbyt wiele). – ricardo

+0

Przyjąłem tę odpowiedź, ponieważ wydaje się, że jest tak daleko, jak to tylko możliwe - chyba że Hadley chce sprawdzić i wyjaśnić wewnętrzne działanie "plyr". – ricardo

+3

@ Thell Odkąd wspomniałeś o łatwości użycia, dodałem to, co 'dtd()' faktycznie jest obok, iiuc. Jak ktokolwiek może powiedzieć, że to nie jest łatwe, bije mnie. Ale dplyr korzystający z zaplecza data.table jest wolniejszy niż bezpośrednio przy użyciu data.table, a następnie? Dlaczego? –