2014-01-08 6 views
18

Ich schreibe einige R-Skripte neu, die große Datenmengen analysieren (~ 17 Millionen Zeilen), und ich dachte, ich würde versuchen, die Speichereffizienz zu verbessern, indem ich das Paket data.table nutze (was ich gerade erst lerne!).Komplizierte Zusammenfassung Funktion - ist es möglich, mit R data.table Paket zu lösen?

Ein Teil des Codes hat mich ein wenig verwirrt. Ich kann meine ursprüngliche Lösung nicht veröffentlichen, weil (1) es Mist (langsam!) Ist und (2) es in Bezug auf die Daten sehr nuanciert ist und diese Frage nur komplizierter macht.

Stattdessen habe ich dieses Spielzeug Beispiel gemacht (und es ist wirklich ein Spielzeug Beispiel):

ds <- data.table(ID=c(1,1,1,1,2,2,2,3,3,3), 
Obs=c(1.5,2.5,0.0,1.25,1.45,1.5,2.5,0.0,1.25,1.45), 
Pos=c(1,3,5,6,2,3,5,2,3,4)) 

die wie folgt aussieht:

ID Obs Pos 
1: 1 1.50 1 
2: 1 2.50 3 
3: 1 0.00 5 
4: 1 1.25 6 
5: 2 1.45 2 
6: 2 1.50 3 
7: 2 2.50 5 
8: 3 0.00 2 
9: 3 1.25 3 
10: 3 1.45 4 

Zur Erleichterung der Erklärung, ich werde so tun, dass wir Züge beobachten (jeder Zug hat seine eigene ID), bewegt sich über eine lineare Einbahnstraße, mit Beobachtungen (etwas Wert, nicht von Import zu der Frage) über den Zug, der an festgelegten Positionen gemacht wird (pos, hier von 1-6) entlang der Strecke. Es ist nicht zu erwarten, dass ein Zug die gesamte Länge der Strecke zurücklegt (vielleicht explodierte er, bevor er Position 6 erreichte), und manchmal vergisst der Beobachter eine Beobachtung ... Die Positionen sind aufeinander folgend (wenn wir also die Beobachtung verpasst haben) der Zug an Position 4, aber wir haben ihn an Position 5 beobachtet, wir wissen, dass er Position 4) passiert haben muss.

Aus der obigen data.table muß ich so eine Tabelle generieren:

Pos Count 
1: 1  3 
2: 2  3 
3: 3  3 
4: 4  3 
5: 5  2 
6: 6  1 

Wo für jeden eindeutigen Po in meiner data.table ds ich eine Zählung der Anzahl der Züge das schaffte es zu dieser Position auf der Spur (oder weiter), unabhängig davon, ob die Beobachtung an dieser Position auf der Spur gemacht wurde.

Wenn jemand irgendwelche Ideen oder Vorschläge hat, wie man das anpackt, würde es sehr geschätzt werden. Leider kenne ich die data.table nicht genug, um zu wissen, ob dies möglich ist! Oder es könnte unglaublich einfach zu lösen sein Problem und ich bin nur langsam :)

Antwort

14

Große Frage !! Die Beispieldaten sind besonders gut aufgebaut und gut erklärt.

Zuerst werde ich diese Antwort zeigen, dann werde ich es Schritt für Schritt erklären.

> ids = 1:3 # or from the data: unique(ds$ID) 
> pos = 1:6 # or from the data: unique(ds$Pos) 
> setkey(ds,ID,Pos) 

> ds[CJ(ids,pos), roll=-Inf, nomatch=0][, .N, by=Pos] 
    Pos N 
1: 1 3 
2: 2 3 
3: 3 3 
4: 4 3 
5: 5 2 
6: 6 1 
> 

Das sollte auch auf Ihre großen Daten sehr effizient sein.

Schritt für Schritt

Zuerst habe ich versucht, ein Kreuz verbinden (CJ); d.h. für jeden Zug für jede Position.

> ds[CJ(ids,pos)] 
    ID Pos Obs 
1: 1 1 1.50 
2: 1 2 NA 
3: 1 3 2.50 
4: 1 4 NA 
5: 1 5 0.00 
6: 1 6 1.25 
7: 2 1 NA 
8: 2 2 1.45 
9: 2 3 1.50 
10: 2 4 NA 
11: 2 5 2.50 
12: 2 6 NA 
13: 3 1 NA 
14: 3 2 0.00 
15: 3 3 1.25 
16: 3 4 1.45 
17: 3 5 NA 
18: 3 6 NA 

Ich sehe 6 Reihen pro Zug. Ich sehe 3 Züge. Ich habe 18 Zeilen wie erwartet. Ich sehe NA, wo dieser Zug nicht beobachtet wurde. Gut. Prüfen. Der Cross Join scheint zu funktionieren. Lassen Sie uns nun die Anfrage erstellen.

Sie haben geschrieben, wenn ein Zug an Position n beobachtet wird, muss er vorhergehende Positionen passiert haben. Sofort denke ich roll. Lass es uns versuchen.

Hm. Das brachte die Beobachtungen für jeden Zug vorwärts. Es hat einige NA an Position 1 für die Züge 2 und 3 verlassen, aber Sie sagten, wenn ein Zug an Position 2 beobachtet wird, muss er Position 1 passiert haben. Er hat auch die letzte Beobachtung für die Züge 2 und 3 auf Position 6 vorgerollt, aber Sie sagten Züge könnten explodieren. Also, wir wollen rückwärts rollen! Das ist roll=-Inf. Es ist eine komplizierte -Inf, weil Sie auch wie weit rückwärts rollen können, aber wir brauchen das nicht für diese Frage; Wir wollen nur unendlich lange rückwärts rollen. Lass uns versuchen roll=-Inf und sehen was passiert.

> ds[CJ(ids,pos), roll=-Inf] 
    ID Pos Obs 
1: 1 1 1.50 
2: 1 2 2.50 
3: 1 3 2.50 
4: 1 4 0.00 
5: 1 5 0.00 
6: 1 6 1.25 
7: 2 1 1.45 
8: 2 2 1.45 
9: 2 3 1.50 
10: 2 4 2.50 
11: 2 5 2.50 
12: 2 6 NA 
13: 3 1 0.00 
14: 3 2 0.00 
15: 3 3 1.25 
16: 3 4 1.45 
17: 3 5 NA 
18: 3 6 NA 

Das ist besser. Fast dort. Alles, was wir jetzt tun müssen, ist zu zählen. Aber diese lästigen NA sind da, nachdem die Züge 2 und 3 explodiert sind. Lass uns sie entfernen.

> ds[CJ(ids,pos), roll=-Inf, nomatch=0] 
    ID Pos Obs 
1: 1 1 1.50 
2: 1 2 2.50 
3: 1 3 2.50 
4: 1 4 0.00 
5: 1 5 0.00 
6: 1 6 1.25 
7: 2 1 1.45 
8: 2 2 1.45 
9: 2 3 1.50 
10: 2 4 2.50 
11: 2 5 2.50 
12: 3 1 0.00 
13: 3 2 0.00 
14: 3 3 1.25 
15: 3 4 1.45 

Btw, data.table mag so viel wie möglich innerhalb eines einzigen DT[...] zu sein, wie das ist, wie es die Abfrage optimiert. Intern erstellt es die NA nicht und entfernt sie dann; es schafft nie die NA an erster Stelle. Dieses Konzept ist wichtig für die Effizienz.

Schließlich müssen wir nur zählen. Wir können das am Ende einfach als eine zusammengesetzte Abfrage angehen.

> ds[CJ(ids,pos), roll=-Inf, nomatch=0][, .N, by=Pos] 
    Pos N 
1: 1 3 
2: 2 3 
3: 3 3 
4: 4 3 
5: 5 2 
6: 6 1 
+0

+1 wirklich schöne Lösung und noch bessere Erklärung. Könnten Sie etwas darüber sagen, wie Sie dies mit 'ds [, list (Pos = 1: Pos [.N]), mit = ID] [, .N, by = Pos] vergleichen würden, wenn die Daten größer werden? –

+0

@ SimonO'Hanlon Schöne Alternative. 'Pos [.N]' wäre ein neuer Vektor der Länge 1, der an die Funktion ':' übergeben wird, um einen neuen '1: Pos [.N]' Vektor zu erzeugen. Ich würde erwarten, dass all diese kleinen Vektoren Speicher blockieren und mehr Müll sammeln. Als die Anzahl der Züge zunahm, würde das mehr beißen (mehr Gruppen), als die Anzahl der Positionen vielleicht stieg. Wenn Sie es testen, interessiert mich das Ergebnis! –

+0

Ich verstehe die Data.table-Syntax nicht wirklich, aber das CJ sieht teuer aus (konzeptionell, wenn nicht wirklich?); Gibt es eine Lösung wie meine, in der data.table die maximale Pos nach ID identifiziert, d. h. uns schnell zu 'nAtMax' bringt? Vielleicht macht das @ SimonO'Hanlon? –

3

Sie können versuchen, wie unten. Ich habe es absichtlich in viele Schritt-Lösung zum besseren Verständnis aufgeteilt. Sie können wahrscheinlich alle in einem Schritt kombinieren, indem Sie einfach [] verketten.

Die Logik hier ist, dass wir zuerst die endgültige Position für jede ID finden. Dann aggregieren wir Daten, um die Anzahl der IDs für jede endgültige Position zu finden. Da alle IDs für die Endposition 6 auch für die Endposition 5 gezählt werden sollen, verwenden wir cumsum, um alle höheren ID-Nummern zu ihren jeweiligen niedrigeren IDs hinzuzufügen.

ds2 <- ds[, list(FinalPos=max(Pos)), by=ID] 

ds2 
## ID FinalPos 
## 1: 1  6 
## 2: 2  5 
## 3: 3  4 

ds3 <- ds2[ , list(Count = length(ID)), by = FinalPos][order(FinalPos, decreasing=TRUE), list(FinalPos, Count = cumsum(Count))] 

ds3 
## FinalPos Count 
## 1:  4  3 
## 2:  5  2 
## 3:  6  1 

setkey(ds3, FinalPos) 

ds3[J(c(1:6)), roll = 'nearest'] 

## FinalPos Count 
## 1:  1  3 
## 2:  2  3 
## 3:  3  3 
## 4:  4  3 
## 5:  5  2 
## 6:  6  1 
+0

+1, Sehr nette Verwendung von 'roll =" nearest "'. Ich glaube nicht, dass "DS3" notwendig ist? - 'setkey (ds [, Liste (N = max (Pos)), keyby = ID], N) [J (1: 6), roll =" nearest "]' – Arun

+1

Hierüber ein wenig nachdenken, 'roll =" nearest "' wird ein falsches Ergebnis liefern, wenn zum Beispiel "6" überhaupt nicht in den Daten vorhanden ist und Sie die Verknüpfung von 1: 6 durchführen, nicht wahr (wird "2" statt NA oder 0 geben)? – Arun

+0

@Arun Sie haben Recht! Lass mich meine Antwort ändern. –

8

data.table klingt wie eine ausgezeichnete Lösung. Von der Art und Weise werden die Daten bestellt man das Maximum jeder Zug mit

maxPos = ds$Pos[!duplicated(ds$ID, fromLast=TRUE)] 

Dann tabellarisieren die Züge, die diese Position an jeder Position

nAtMax = tabulate(maxPos) 

und berechnen die kumulative Summe der Züge erreichen finden konnten, zählen vom Ende

rev(cumsum(rev(nAtMax))) 
## [1] 3 3 3 3 2 1 

Ich denke, das wird ziemlich schnell für große Daten, obwohl nicht vollständig Speicher effizient.

+0

+1 Ich hatte den Eindruck von der Frage und dem Titel, dass eine "data.table" -Demonstration angefordert wurde, da Meep erklärte, dass seine Beispieldaten und -aufgabe sehr eingeschränkt waren. Die 'rev (cumsum (rev (tabulate()))' macht genau die gestellte Aufgabe, aber was ist, wenn Züge an verschiedenen Punkten starten, der Wert der Beobachtung von Interesse wird, Züge nicht mehr explodieren oder es auch Lastwagen gibt (2 Spalten von IDs)? Das sind einfache Änderungen (Schalter) in der data.table-Abfrage, während in der Basis einige Kopfkratzen auftreten können? –

+0

Vielen Dank für die Lösung, die immer noch viel besser war als das, was ich hatte! :) Matt hat recht, wenn er behauptet, dass die Daten komplizierter sein könnten, weshalb ich seine Antwort akzeptiert habe. Wenn Sie neugierig sind, woran ich gerade arbeite, sind DNA-Sequenzierungs-Trace-Daten, die nichts mit Zügen zu tun haben :) – Meep

Verwandte Themen