2013-03-07 1 views
10

Sto facendo alcune aggregazioni su un data.table (pacchetto eccellente !!!) e ho trovato la variabile .SD molto utile per molte cose. Tuttavia, utilizzarlo rallenta notevolmente il calcolo quando ci sono molti gruppi. Segue un esempio:R data.table aggregazione lenta quando si utilizza .SD

# A moderately big data.table 
x = data.table(id=sample(1e4,1e5,replace=T), 
       code=factor(sample(2,1e5,replace=T)), 
       z=runif(1e5) 
      ) 

setkey(x,id,code) 

system.time(x[,list(code2=nrow(.SD[code==2]), total=.N), by=id]) 
## user system elapsed 
## 6.226 0.000 6.242 

system.time(x[,list(code2=sum(code==2), total=.N), by=id]) 
## user system elapsed 
## 0.497 0.000 0.498 

system.time(x[,list(code2=.SD[code==2,.N], total=.N), by=id]) 
## user system elapsed 
## 6.152 0.000 6.168 

Sto facendo qualcosa di sbagliato? Dovrei evitare .SD a favore di singole colonne? Grazie in anticipo.

risposta

11

Sto facendo qualcosa di sbagliato cioè dovrei evitare .SD a favore di singole colonne?

Sì, esattamente. Utilizzare solo .SD se si stanno utilizzando tutti i dati all'interno di .SD. Potresti anche scoprire che la chiamata a nrow() e la chiamata di subquery a [.data.table all'interno di j sono anch'essi colpevoli: utilizzare Rprof per confermare.

Vedi le ultime frasi di FAQ 2.1:

FAQ 2.1 Come posso evitare di scrivere un tempo molto lungo espressione j? Hai detto che dovrei usare i nomi delle colonne, ma ho un sacco di colonne.
Quando il raggruppamento, l'espressione j può utilizzare i nomi delle colonne come variabili, come si sa, ma si può anche utilizzare un simbolo riservato .SD che si riferisce al il sottoinsieme del Data.table per ogni gruppo (esclusi i raggruppamento colonne). Quindi per riassumere tutte le tue colonne è solo DT[,lapply(.SD,sum),by=grp]. Potrebbe sembrare complicato, ma è veloce per scrivere e veloce da eseguire. Si noti che non è necessario creare una funzione anonima . Vedi la tempistica vignetta e wiki per il confronto con altri metodi . L'oggetto .SD viene implementato in modo efficiente internamente e più altro ecche passare un argomento a una funzione. Si prega di non fare questo però: DT[,sum(.SD[,"sales",with=FALSE]),by=grp]. Funziona ma è molto inefficiente e poco elegante. Questo è quanto previsto: DT[,sum(sales),by=grp] e potrebbe essere 100 volte più veloce.

veda anche la prima pallottola di FAQ 3.1:

FAQ 3.1 Ho 20 colonne e un gran numero di righe. Perché un'espressione di una colonna è così veloce?
diversi motivi:
- quella colonna è raggruppati, gli altri 19 vengono ignorati solo perché data.table ispeziona l'espressione j e si rende conto che non utilizza le altre colonne.

Quando data.table ispeziona j e vede il simbolo .SD, che l'aumento di efficienza va fuori dalla finestra. Dovrà popolare l'intero sottoinsieme .SD per ogni gruppo anche se non si utilizzano tutte le sue colonne. È molto difficile per data.table sapere quali colonne di .SD si utilizzano realmente (j potrebbero contenere if s, ad esempio). Tuttavia, se ne hai bisogno comunque, non importa, ad esempio in DT[,lapply(.SD,sum),by=...]. Questo è l'uso ideale di .SD.

Quindi, sì, evitare .SD ove possibile. Usa i nomi delle colonne direttamente per dare l'ottimizzazione data.table di j la migliore possibilità. La semplice esistenza del simbolo .SD in j è importante.

Ecco perché è stato introdotto .SDcols. Quindi puoi dire a data.table quali colonne dovrebbero essere nello .SD se vuoi solo un sottoinsieme. Altrimenti, data.table popolerà .SD con tutte le colonne nel caso in cui sia necessario il numero j.

+0

Grazie mille! Sono stato ingannato dalla frase "L'oggetto .SD è implementato in modo efficiente internamente ed è più efficiente del passare un argomento ad una funzione" e non ha capito "Per favore non farlo però: DT [, sum (.SD [," vendite ", con = FALSE]), da = grp]" a causa del con = FALSE. Questo accelererà molto il mio codice! – vsalmendra

+0

@vsalmendra Ah, sì, potrebbe essere più chiaro. È il tipo di cosa che è stata lasciata alla discussione della comunità in passato. In definitiva speriamo di migliorare l'ottimizzazione di 'j' in modo che gli utenti non debbano sapere cose come questa. –

+0

@vsalmendra Ora ho migliorato le FAQ 2.1 per la prossima versione. –

3

provare a risolvere questo rompendo i calcoli in due passi, poi la fusione dei risultanti frame di dati:

system.time({ 
    x2 <- x[code==2, list(code2=.N), by=id] 
    xt <- x[, list(total=.N), by=id] 
    print(x2[xt]) 
}) 

Sulla mia macchina funziona in 0,04 secondi rispetto a 7,42 secondi, vale a dire ~ 200 volte più veloce di quanto il tuo codice originale:

  id code2 total 
    1:  1  6 14 
    2:  2  8 10 
    3:  3  7 13 
    4:  4  5 13 
    5:  5  9 18 
    ---     
9995: 9996  4  9 
9996: 9997  3  6 
9997: 9998  6 10 
9998: 9999  3  4 
9999: 10000  3  6 
    user system elapsed 
    0.05 0.00 0.04 
+0

(+1) Sostituire 'x2' con' x2 <- x [J (unico (id), "2"), elenco (codice2 = .N)] [, codice: = NULL, keyby = "id"] '. Il tuo codice rimuove le righe dove codice! = 2 per un dato id. – Arun

+0

+1 In realtà è molto più veloce di 'x [, lista (somma (codice == 2) ,. N), per = id]' (esempio 2 in questione) non è così! Forse perché stai evitando la ripetuta chiamata a '==' per ogni gruppo (assegnazione associata ecc. Per quei piccoli vettori). –

+0

@Arun Sei sicuro? 'x2 [xt] [is.na (code2)]' ha alcune righe. Si ottiene 'NA' invece di' 0'. Potrebbe essere sbagliato, solo guardato rapidamente. –