2013-03-07 4 views
10

Ich mache einige Aggregationen auf einer data.table (ausgezeichnetes Paket !!!) und ich fand die .SD-Variable sehr nützlich für viele Dinge. Wenn Sie es verwenden, verlangsamt es jedoch die Berechnung erheblich, wenn viele Gruppen vorhanden sind. Folgt einem Beispiel:R data.table langsame Aggregation bei Verwendung von .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 

Mache ich etwas falsch? Soll ich .SD zugunsten einzelner Spalten vermeiden? Danke im Voraus.

Antwort

11

Bin ich etwas falsch also tun sollte ich .SD zugunsten einzelner Spalten zu vermeiden?

Ja, genau. Verwenden Sie nur .SD, wenn Sie wirklich alle Daten innerhalb .SD verwenden. Sie können auch feststellen, dass der Anruf an nrow() und der Unterabfrage an [.data.table innerhalb j auch Schuldige sind: Verwenden Sie Rprof, um zu bestätigen.

Siehe die letzten Sätze von FAQ 2.1:

FAQ 2.1 Wie kann ich einen wirklich langen Ausdruck j vermeiden zu schreiben? Sie haben gesagt, ich sollte die Spaltennamen verwenden, aber ich habe viele Spalten.
Wenn die Gruppierung, die j Ausdruck Spaltennamen als Variablen verwenden können, wie Sie wissen, es kann aber auch ein Symbol reserviert .SD, die für jede Gruppe den Subset des Data.table bezieht sich verwenden (ohne die Gruppierung Spalten). Also, um alle Ihre Spalten zusammenzufassen, ist es nur DT[,lapply(.SD,sum),by=grp]. Es mag schwierig erscheinen, aber es ist schnell zu schreiben und schnell zu laufen. Beachten Sie, dass Sie keine anonyme Funktion erstellen müssen. Sehen Sie die Timing-Vignette und Wiki für den Vergleich mit anderen Methoden . Das Objekt .SD wird effizient intern implementiert und mehr als ein Argument an eine Funktion übergeben. Bitte tun Sie dies nicht though: DT[,sum(.SD[,"sales",with=FALSE]),by=grp]. Das funktioniert aber ist sehr ineffizient und unelegant. Dies ist, was beabsichtigt war: DT[,sum(sales),by=grp] und könnte 100-mal schneller sein.

Auch die erste Kugel von häufig gestellten Fragen 3.1:

FAQ 3.1 Ich habe 20 Spalten und eine große Anzahl von Zeilen. Warum ist ein Ausdruck einer Spalte so schnell?
Mehrere Gründe:
- Nur die Spalte gruppiert ist, werden die anderen 19 ignoriert, weil data.table die j Ausdruck prüft und erkennt es nicht die anderen Spalten zu verwenden ist.

Wenn data.table inspiziert j und sieht das .SD Symbol, geht der Effizienzgewinn aus dem Fenster. Es muss die gesamte Untergruppe .SD für jede Gruppe auffüllen, auch wenn Sie nicht alle Spalten verwenden. Es ist sehr schwierig für data.table zu wissen, welche Spalten von .SD Sie wirklich verwenden (j könnte if s enthalten, zum Beispiel). Allerdings, wenn Sie sie alle sowieso brauchen, ist es natürlich egal, wie in DT[,lapply(.SD,sum),by=...]. Das ist die ideale Verwendung von .SD.

Also, ja, vermeiden Sie .SD, wo immer es möglich ist. Verwenden Sie die Spaltennamen direkt, um der optimierung von data.table j die beste chance zu geben. Die bloße Existenz des Symbols .SD in j ist wichtig.

Deshalb wurde .SDcols eingeführt. So können Sie sagen, data.table welche Spalten sollten in .SD sein, wenn Sie nur eine Teilmenge möchten. Andernfalls wird data.table.SD mit allen Spalten füllen, nur für den Fall j braucht sie.

+0

Vielen Dank! Ich wurde mit dem Satz "Das .SD-Objekt ist effizient intern implementiert und effizienter als die Übergabe eines Arguments an eine Funktion" ausgetrickst und verstand nicht das "Bitte tue das nicht though: DT [, sum (.SD [," sales ", mit = FALSE]), by = grp]" aufgrund von with = FALSE. Das wird meinen Code beschleunigen! – vsalmendra

+0

@vsalmendra Ah, ja, es könnte klarer sein. Es ist eine Sache, die in der Vergangenheit der Gemeinschaftsdiskussion überlassen wurde. Letztendlich hoffen wir, die "j" -Optimierung zu verbessern, so dass Benutzer solche Dinge nicht mehr wissen müssen. –

+0

@vsalmendra Ich habe jetzt FAQ 2.1 für die nächste Version verbessert. –

3

Versuchen Sie, diese Lösung durch die Berechnungen in zwei Schritten zu brechen, dann die Zusammenführung der resultierenden Datenrahmen:

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

Auf meinem Rechner läuft es in 0,04 Sekunden zu 7,42 Sekunden gegenüber, dh ~ 200-mal schneller als Ihre Original-Code:

  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) Ich würde 'x2' durch' x2 <- x [J (eindeutige (id), "2"), list (code2 = .N)] [, code: = NULL, keyby = ersetzen "id"] '. Ihr Code entfernt Zeilen mit dem Code! = 2 für eine bestimmte ID. – Arun

+0

+1 Eigentlich ist das viel schneller als 'x [, Liste (Summe (Code == 2), N), durch = ID]' (Beispiel 2 in Frage) ist es nicht! Vielleicht, weil Sie den wiederholten Aufruf von '==' für jede Gruppe (assoziierte Zuweisung usw. für diese kleinen Vektoren) vermeiden. –

+0

@Arun Sind Sie sicher? 'x2 [xt] [is.na (code2)]' hat einige Zeilen. Sie erhalten nur "NA" anstelle von "0". Könnte falsch sein, sah nur schnell aus. –

Verwandte Themen