2015-08-06 12 views
18

Mi sono abituato a ottenere i miei dati con il dplyr, ma alcuni dei calcoli sono "lenti". In particolare sottoinsieme per gruppi, ho letto che dplyr è lento quando c'è un sacco di gruppi e basato su this benchmark data.table potrebbe essere più veloce quindi ho iniziato a imparare data.table.Come velocizzare il sottogruppo per gruppi

Ecco come riprodurre qualcosa vicino ai miei dati reali con 250k righe e circa 230k gruppi. Vorrei raggruppare per id1, id2 e impostare le righe con lo max(datetime) per ogni gruppo.

Dati

# random datetime generation function by Dirk Eddelbuettel 
# https://stackoverflow.com/questions/14720983/efficiently-generate-a-random-sample-of-times-and-dates-between-two-dates 
rand.datetime <- function(N, st = "2012/01/01", et = "2015/08/05") { 
    st <- as.POSIXct(as.Date(st)) 
    et <- as.POSIXct(as.Date(et)) 
    dt <- as.numeric(difftime(et,st,unit="sec")) 
    ev <- sort(runif(N, 0, dt)) 
    rt <- st + ev 
} 

set.seed(42) 
# Creating 230000 ids couples 
ids <- data.frame(id1 = stringi::stri_rand_strings(23e4, 9, pattern = "[0-9]"), 
        id2 = stringi::stri_rand_strings(23e4, 9, pattern = "[0-9]")) 
# Repeating randomly the ids[1:2000, ] to create groups 
ids <- rbind(ids, ids[sample(1:2000, 20000, replace = TRUE), ]) 
# Adding random datetime variable and dummy variables to reproduce real datas 
datas <- transform(ids, 
        datetime = rand.datetime(25e4), 
        var1 = sample(LETTERS[1:6], 25e4, rep = TRUE), 
        var2 = sample(c(1:10, NA), 25e4, rep = TRUE), 
        var3 = sample(c(1:10, NA), 25e4, rep = TRUE), 
        var4 = rand.datetime(25e4), 
        var5 = rand.datetime(25e4)) 

datas.tbl <- tbl_df(datas) 
datas.dt <- data.table(datas, key = c("id1", "id2")) 

non riuscivo a trovare la retta via per sottoinsieme da gruppi con data.table così ho fatto questa domanda: Filter rows by groups with data.table

Consigliamo me di utilizzare .SD:

datas.dt[, .SD[datetime == max(datetime)], by = c("id1", "id2")] 

Ma ho due problemi, funziona con la data ma non con POSIXct ("Errore in UseMethod (" as.data.table "): nessun metodo applicabile per 'as.data. tabella 'applicata a un oggetto di classe "c (' POSIXct ',' POSIXt ')" "), e questo è molto lento. Ad esempio, con le date:

> system.time({ 
+ datas.dt[, .SD[as.Date(datetime) == max(as.Date(datetime))], by = c("id1", "id2")] 
+ }) 
utilisateur  système  écoulé 
     207.03  0.00  207.48 

così ho trovato altro modo molto più veloce per raggiungere questo obiettivo (e mantenendo datetimes) con data.table:

Funzioni

f.dplyr <- function(x) x %>% group_by(id1, id2) %>% filter(datetime == max(datetime)) 
f.dt.i <- function(x) x[x[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1] 
f.dt <- function(x) x[x[, datetime == max(datetime), by = c("id1", "id2")]$V1] 

ma poi ho pensato di dati. tabella sarebbe molto più veloce, la differenza di tempo con dplyr non è significativa.

microbenchmark

mbm <- microbenchmark(
    dplyr = res1 <- f.dplyr(datas.tbl), 
    data.table.I = res2 <- f.dt.i(datas.dt), 
    data.table = res3 <- f.dt(datas.dt), 
    times = 50L) 

Unit: seconds 
     expr  min  lq  mean median  uq  max neval 
     dplyr 31.84249 32.24055 32.59046 32.61311 32.88703 33.54226 50 
data.table.I 30.02831 30.94621 31.19660 31.17820 31.42888 32.16521 50 
    data.table 30.28923 30.84212 31.09749 31.04851 31.40432 31.96351 50 

enter image description here

mi sto perdendo/abuso qualcosa con data.table? Hai idee per accelerare questo calcolo?

Qualsiasi aiuto sarebbe molto apprezzato! Grazie


Modifica: alcune precisioni relative alle versioni di sistema e pacchetti utilizzate per il microbenchmark. (Il computer non è una macchina da guerra, 12Go i5)

sistema

sessionInfo() 
R version 3.1.3 (2015-03-09) 
Platform: x86_64-w64-mingw32/x64 (64-bit) 
Running under: Windows 7 x64 (build 7601) Service Pack 1 

locale: 
    [1] LC_COLLATE=French_France.1252 LC_CTYPE=French_France.1252 
[3] LC_MONETARY=French_France.1252 LC_NUMERIC=C     
[5] LC_TIME=French_France.1252  

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

other attached packages: 
    [1] readr_0.1.0   ggplot2_1.0.1  microbenchmark_1.4-2 
[4] data.table_1.9.4  dplyr_0.4.1   plyr_1.8.2   

loaded via a namespace (and not attached): 
    [1] assertthat_0.1 chron_2.3-45  colorspace_1.2-6 DBI_0.3.1  
[5] digest_0.6.8  grid_3.1.3  gtable_0.1.2  lazyeval_0.1.10 
[9] magrittr_1.5  MASS_7.3-39  munsell_0.4.2 parallel_3.1.3 
[13] proto_0.3-10  Rcpp_0.11.5  reshape2_1.4.1 scales_0.2.4  
[17] stringi_0.4-1 stringr_0.6.2 tools_3.1.3 

> packageVersion("data.table") 
[1] ‘1.9.4’ 
> packageVersion("dplyr") 
[1] ‘0.4.1’ 
+0

Si desidera ottenere tutti i valori uguali a max o solo il primo valore come 'which.max' restituisce? Anche 'datas.dt [, .SD [as.Date (datetime) == max (as.Date (datetime))], per = c (" id1 "," id2 ")]' è una cattiva pratica. Devi convertire 'date' in' IDate' class prima del subsetting. –

+0

Solo per divertimento, puoi aggiungere 'x%>% group_by (id1, id2)%>% slice (che (datetime == max (datetime)))' al tuo confronto? –

+0

Anche 'datas.dt [, datetime: = as.IDate (datetime)]; system.time (datas.dt [datas.dt [, .I [datetime == max (datetime)], di = c ("id1", "id2")] $ V1]) 'funziona solo 5 secondi rispetto a 200 quando usi '.SD', quindi trovo difficile credere ai tuoi benchmark. –

risposta

19

Ottima domanda!

I'll assumere df e dt essere i nomi degli oggetti per la digitazione facile/veloce.

df = datas.tbl 
dt = datas.dt 

confronto a -O3 ottimizzazione livello:

In primo luogo, ecco i tempi sul mio sistema sulla versione corrente di CRAN dplyr e la versione devel di data.table. La versione di sviluppo di dplyr sembra soffrire di regressioni di prestazioni (e viene riparata da Romain).

system.time(df %>% group_by(id1, id2) %>% filter(datetime == max(datetime))) 
# 25.291 0.128 25.610 

system.time(dt[dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1]) 
# 17.191 0.075 17.349 

Ho eseguito questo un bel po 'di volte, e Dint sembrano cambiare. Tuttavia, compilo tutti i pacchetti con il flag di ottimizzazione -O3 (impostando ~/.R/Makevars in modo appropriato). E ho notato che le prestazioni dello data.table sono molto migliori rispetto agli altri pacchetti con cui ho confrontato lo -O3.

velocità Raggruppamento confronto

In secondo luogo, è importante capire il motivo di tale lentezza. Per prima cosa paragoniamo il tempo al gruppo .

system.time(group_by(df, id1, id2)) 
# 0.303 0.007 0.311 
system.time(data.table:::forderv(dt, by = c("id1", "id2"), retGrp = TRUE)) 
# 0.002 0.000 0.002 

Anche se ci sono un totale di 250.000 righe la dimensione dei dati è di circa ~ 38MB. A queste dimensioni, è improbabile notare una differenza notevole nella velocità di raggruppamento. raggruppamento

data.table 's è >100x più veloce qui, non è chiaramente il motivo di tale lentezza ...

Perché è lento?

Quindi qual è il motivo? Torniamo sulla datatable.verbose opzione e controllare ancora:

options(datatable.verbose = TRUE) 
dt[dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1] 
# Detected that j uses these columns: datetime 
# Finding groups (bysameorder=TRUE) ... done in 0.002secs. bysameorder=TRUE and o__ is length 0 
# lapply optimization is on, j unchanged as '.I[datetime == max(datetime)]' 
# GForce is on, left j unchanged 
# Old mean optimization is on, left j unchanged. 
# Starting dogroups ... 
# memcpy contiguous groups took 0.097s for 230000 groups 
# eval(j) took 17.129s for 230000 calls 
# done dogroups in 17.597 secs 

Così eval(j) da solo ha preso ~ 97% del tempo! L'espressione che abbiamo fornito in j viene valutata per per ogni gruppo. Dal momento che hai 230.000 gruppi e c'è una penalità per la chiamata eval(), ciò si aggiunge.

Evitare la pena di eval()

Dal momento che siamo consapevoli di questa pena, siamo andati avanti e iniziato ad attuare le versioni interne di alcune funzioni di uso comune: sum, mean, min, max. Questo/dovrebbe essere esteso a quante più funzioni possibili (quando troviamo il tempo).

Quindi, proviamo calcolo del tempo solo per ottenere max(datetime) prima:

dt.agg = dt[, .(datetime = max(datetime)), by = .(id1, id2)] 
# Detected that j uses these columns: datetime 
# Finding groups (bysameorder=TRUE) ... done in 0.002secs. bysameorder=TRUE and o__ is length 0 
# lapply optimization is on, j unchanged as 'list(max(datetime))' 
# GForce optimized j to 'list(gmax(datetime))' 

Ed è istantanea. Perché? Perché max() ottiene internamente ottimizzato per gmax() e non c'è nessuna chiamata eval() per ciascuno dei gruppi 230 K.

Quindi perché non è datetime == max(datetime) istantaneo? Perché è più complicato analizzare tali espressioni e ottimizzarle internamente, e non siamo ancora arrivati.

Soluzione

Quindi, ora che conosciamo il problema, e un modo per aggirare l'ostacolo, lo si può usare.

dt.agg = dt[, .(datetime = max(datetime)), by = .(id1, id2)] 
dt[dt.agg, on = c("id1", "id2", "datetime")] # v1.9.5+ 

Questo richiede circa 0,14 secondi sul mio Mac.

Si noti che questo è solo veloce perché l'espressione viene ottimizzata per gmax(). Confronta con:

dt[, .(datetime = base::max(datetime)), by = .(id1, id2)] 

Acconsento ottimizzare le espressioni più complicate per evitare la pena eval() sarebbe la soluzione ideale, ma non siamo ancora arrivati.

+8

Impressionante lezione di profilazione. –

+1

Grazie per questa risposta illuminante. Mi hai dato una soluzione per dividere il tempo di esecuzione di 100, ma mi ha anche aiutato molto a capire il collo di bottiglia in questo calcolo! Grazie. –

9

Come circa riassume i dati.tavolo e join dati originali

system.time({ 
    datas1 <- datas.dt[, list(datetime=max(datetime)), by = c("id1", "id2")] #summarize the data 
    setkey(datas1, id1, id2, datetime) 
    setkey(datas.dt, id1, id2, datetime) 
    datas2 <- datas.dt[datas1] 
}) 
# user system elapsed 
# 0.083 0.000 0.084 

che filtra i dati correttamente

system.time(dat1 <- datas.dt[datas.dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1]) 
# user system elapsed 
# 23.226 0.000 23.256 
all.equal(dat1, datas2) 
# [1] TRUE 

Addendum

setkey argomento è superfluo se si utilizza la devel version del data.table (Grazie a @akrun per il puntatore)

system.time({ 
    datas1 <- datas.dt[, list(datetime=max(datetime)), by = c("id1", "id2")] #summarize the data 
    datas2 <- datas.dt[datas1, on=c('id1', 'id2', 'datetime')] 
}) 
+1

Nella versione di sviluppo non hai bisogno il 'setkey'. 'datas.dt [datas1, on = c ('id1', 'id2')]' dovrebbe funzionare. sebbene non testato con i tempi. – akrun

+0

@akrun, grazie. Sono cieco di dadi e bulloni di 'data.table'. – Khashaa

+0

Probabilmente dovresti conservare entrambe le versioni, poiché la tua modifica funziona solo sulla versione di sviluppo. –