Warum erlaubt X von data.tables keinen vollständigen äußeren Join oder einen linken Join?

Dies ist ein bisschen eine philosophische Frage über data.table Join-Syntax. Ich finde mehr und mehr Anwendungen für data.tables, aber lerne immer noch …

Das Join-Format X[Y] für data.tables ist sehr übersichtlich, handlich und effizient, aber soweit ich das beurteilen kann, unterstützt es nur innere Joins und rechte äußere Joins. Um einen linken oder vollen äußeren Join zu erhalten, muss ich merge :

  • X[Y, nomatch = NA] – alle Zeilen in Y – rechte äußere Verknüpfung (Standard)
  • X[Y, nomatch = 0] – nur Zeilen mit Übereinstimmungen in X und Y – innerer Join
  • merge(X, Y, all = TRUE) – alle Zeilen von X und Y – voller äußerer Join
  • merge(X, Y, all.x = TRUE) – alle Zeilen in X – linker äußerer Join

Es scheint mir, dass es praktisch wäre, wenn das X[Y] Join-Format alle 4 Arten von Joins unterstützt. Gibt es einen Grund, warum nur zwei Arten von Joins unterstützt werden?

Für mich sind die nomatch = 0 und nomatch = NA für die ausgeführten Aktionen nicht sehr intuitiv. Es ist einfacher für mich, die all.x = TRUE zu verstehen und zu merken: all = TRUE , all.x = TRUE und all.y = TRUE . Da die X[Y] nomatch der merge viel mehr als entspricht, verwenden Sie die merge für Joins und nicht den nomatch Parameter der nomatch .

Hier sind Codebeispiele für die 4 Join-Typen:

 # sample X and Y data.tables library(data.table) X <- data.table(t = 1:4, a = (1:4)^2) setkey(X, t) X # ta # 1: 1 1 # 2: 2 4 # 3: 3 9 # 4: 4 16 Y <- data.table(t = 3:6, b = (3:6)^2) setkey(Y, t) Y # tb # 1: 3 9 # 2: 4 16 # 3: 5 25 # 4: 6 36 # all rows from Y - right outer join X[Y] # default # tab # 1: 3 9 9 # 2: 4 16 16 # 3: 5 NA 25 # 4: 6 NA 36 X[Y, nomatch = NA] # same as above # tab # 1: 3 9 9 # 2: 4 16 16 # 3: 5 NA 25 # 4: 6 NA 36 merge(X, Y, by = "t", all.y = TRUE) # same as above # tab # 1: 3 9 9 # 2: 4 16 16 # 3: 5 NA 25 # 4: 6 NA 36 identical(X[Y], merge(X, Y, by = "t", all.y = TRUE)) # [1] TRUE # only rows in both X and Y - inner join X[Y, nomatch = 0] # tab # 1: 3 9 9 # 2: 4 16 16 merge(X, Y, by = "t") # same as above # tab # 1: 3 9 9 # 2: 4 16 16 merge(X, Y, by = "t", all = FALSE) # same as above # tab # 1: 3 9 9 # 2: 4 16 16 identical( X[Y, nomatch = 0], merge(X, Y, by = "t", all = FALSE) ) # [1] TRUE # all rows from X - left outer join merge(X, Y, by = "t", all.x = TRUE) # tab # 1: 1 1 NA # 2: 2 4 NA # 3: 3 9 9 # 4: 4 16 16 # all rows from both X and Y - full outer join merge(X, Y, by = "t", all = TRUE) # tab # 1: 1 1 NA # 2: 2 4 NA # 3: 3 9 9 # 4: 4 16 16 # 5: 5 NA 25 # 6: 6 NA 36 

Update: data.table v1.9.6 führte die on= -Syntax ein, die Ad-hoc-Joins für andere Felder als den Primärschlüssel erlaubt. jangoreckis Antwort auf die Frage Wie fügt man Datenrahmen zusammen (innere, äußere, linke, rechte)? bietet einige Beispiele für zusätzliche Join-Typen, die data.table verarbeiten kann.

   

Zitat aus der data.table FAQ 1.12

1.12

Was ist der Unterschied zwischen X [Y] und merge (X, Y)?

  • X[Y] ist ein Join, bei dem die Zeilen von X mit Y (oder dem Schlüssel von Y, falls vorhanden) nachgeschlagen werden.
  • Y[X] ist ein Join, der die Y-Zeilen mit X (oder dem X-Schlüssel, falls vorhanden) nachschlägt.
  • merge(X,Y) führt beide Möglichkeiten gleichzeitig aus.

Die Anzahl der Zeilen von X[Y] und Y[X] normalerweise unterschiedlich; Die Anzahl der Zeilen, die von merge(X,Y) und merge(Y,X) ist identisch. ABER das vermisst den Hauptpunkt. Die meisten Aufgaben erfordern, dass nach einem Join oder einer Zusammenführung etwas an den Daten vorgenommen wird. Warum alle Datenspalten merge, um danach nur eine kleine Teilmenge davon zu verwenden? Sie können merge(X[,ColsNeeded1],Y[,ColsNeeded2]) vorschlagen merge(X[,ColsNeeded1],Y[,ColsNeeded2]) , aber das nimmt Kopien der merge(X[,ColsNeeded1],Y[,ColsNeeded2]) von Daten, und der Programmierer muss herausfinden, welche Spalten benötigt werden. X[Y,j ] in data.table erledigt das alles in einem Schritt für Sie. Wenn Sie X[Y,sum(foo*bar)] schreiben, überprüft data.table automatisch den j-Ausdruck, um zu sehen, welche Spalten er verwendet. Es wird nur diese Spalten nur untergeordnet; die anderen werden ignoriert. Speicher wird nur für die Spalten erstellt, die j verwendet, und Y-Spalten genießen Standard-R-Recycling-Regeln im Kontext jeder Gruppe. Nehmen wir an, foo ist in X und Balken ist in Y (zusammen mit 20 anderen Spalten in Y). Ist X[Y,sum(foo*bar)] schneller zu programmieren und schneller auszuführen als eine Zusammenführung gefolgt von einer Teilmenge?

Wenn Sie eine linke äußere Verbindung von X[Y] möchten

 le < - Y[X] mallx <- merge(X, Y, all.x = T) # the column order is different so change to be the same as `merge` setcolorder(le, names(mallx)) identical(le, mallx) # [1] TRUE 

Wenn Sie eine vollständige äußere Verbindung wünschen

 # the unique values for the keys over both data sets unique_keys < - unique(c(X[,t], Y[,t])) Y[X[J(unique_keys)]] ## tba ## 1: 1 NA 1 ## 2: 2 NA 4 ## 3: 3 9 9 ## 4: 4 16 16 ## 5: 5 25 NA ## 6: 6 36 NA # The following will give the same with the column order X,Y X[Y[J(unique_keys)]] 

@ mnels Antwort ist genau richtig, also akzeptiere diese Antwort. Dies ist nur Follow-up, zu lang für Kommentare.

Wie mnel sagt, wird die linke / rechte äußere Verbindung durch Vertauschen von Y und X : Y[X] -vs- X[Y] . Daher werden 3 der 4 Join-Typen in dieser Syntax unterstützt, nicht 2, iiuc.

Hinzufügen der 4. scheint eine gute Idee. Angenommen, wir fügen full=TRUE oder both=TRUE oder merge=TRUE (nicht sicher, welcher Name am besten ist?), Dann ist mir noch nicht in den Sinn gekommen, dass X[Y,j,merge=TRUE] aus den Gründen nützlich wäre nach dem ABER in FAQ 1.12. Neue Feature-Anfrage jetzt hinzugefügt und hier verlinkt, danke:

FR # 2301: Fügen Sie merge = TRUE Argument für beide X [Y] und Y [X] Join wie merge () hinzu.

Neuere Versionen haben merge.data.table beschleunigt (indem Sie beispielsweise intern eine flache Kopie verwenden, um die Schlüssel effizienter zu setzen). Daher versuchen wir, merge() und X[Y] näher zu bringen und dem Benutzer alle Optionen für volle Flexibilität zur Verfügung zu stellen. Es gibt Vor- und Nachteile von beidem. Eine weitere herausragende Feature-Anfrage ist:

FR # 2033: Fügen Sie by.x und by.y zu merge.data.table hinzu

Wenn es noch andere gibt, halten Sie sie bitte bereit.

Durch diesen Teil in der Frage:

Warum verwenden Sie nicht die Merge-Syntax für Joins und nicht den Nomatch-Parameter der Match-function?

Wenn Sie die merge() all.x und ihre 3 Argumente all , all.x und all.y dann verwenden Sie diese anstelle von X[Y] . Denke, es sollte alle Fälle abdecken. Oder meinst du, warum ist das Argument eine einzige nomatch in [.data.table ? Wenn dem so ist, dann ist es einfach so, wie es in FAQ 2.14 der Fall war: “Kannst du weiter erklären, warum data.table von der A [B] Syntax in der Basis inspiriert ist?”. Aber auch nomatch nimmt nur zwei Werte an, nämlich 0 und NA . Dies könnte so erweitert werden, dass ein negativer Wert etwas bedeutet, oder 12 würde bedeuten, dass die Werte der 12. Zeile verwendet werden, um zum Beispiel nomatch auszufüllen, oder nomatch in Zukunft ein Vektor oder selbst eine data.table .

Hm. Wie würde by-without mit merge = TRUE interagieren? Vielleicht sollten wir das Datatable-Hilfe übernehmen .

Diese “Antwort” ist ein Vorschlag zur Diskussion: Wie in meinem Kommentar angedeutet, schlage ich vor, einen join Parameter zu [.data.table () hinzuzufügen, um zusätzliche Typen von Joins zu aktivieren, dh: X[Y,j,join=string] . Zusätzlich zu den 4 Arten von normalen Joins empfehle ich auch, drei Arten von exklusiven Joins zu unterstützen, und den Cross Join.

Die join String-Werte (und Aliase) für die verschiedenen Join-Typen lauten wie folgt:

  1. "all.y" und "right" – rechts beitreten, die aktuelle data.table default (nomatch = NA) – alle Y-Zeilen mit NAs, wo es keine X-Übereinstimmung gibt;
  2. "both" und "inner" – innerer Join (nomatch = 0) – nur Zeilen, in denen X und Y übereinstimmen;

  3. "all.x" und "left" – left join – alle Zeilen von X, NA wo kein Y passt:

  4. "outer" und "full" – Full Outer Join – alle Zeilen von X und Y, wobei NAs nicht übereinstimmen

  5. "only.x" und "not.y" – Nicht-Joins oder Anti-Joins, die X-Zeilen zurückgeben, wo es keine Y-Übereinstimmung gibt

  6. "only.y" und "not.x" – Nicht-Join oder Anti-Join, die Y-Zeilen "not.x" denen keine X-Übereinstimmung vorhanden ist
  7. "not.both" – Exklusive Join-Rückgabe von X- und Y-Zeilen, bei denen keine Übereinstimmung mit der anderen Tabelle besteht, dh ein Exklusiv-Oder (XOR)
  8. "cross" – Kreuzverbindung oder kartesisches Produkt, wobei jede Zeile von X mit jeder Zeile von Y übereinstimmt

Der Standardwert ist join="all.y" , was dem aktuellen Standardwert entspricht.

Die Zeichenfolgewerte “all”, “all.x” und “all.y” entsprechen den merge() Parametern. Die “richtigen”, “linken”, “inneren” und “äußeren” Zeichenfolgen sind möglicherweise für SQL-Benutzer zugänglicher.

Die Strings “both” und “not.both” sind im Moment mein bester Vorschlag – aber jemand hat vielleicht bessere String-Vorschläge für den inneren Join und den exklusiven Join. (Ich bin mir nicht sicher, ob “exklusiv” die richtige Terminologie ist, korrigieren Sie mich, wenn es einen richtigen Begriff für einen “XOR” Join gibt.)

Verwendung von join="not.y" ist eine Alternative für X[-Y,j] oder X[!Y,j] Nicht-Join-Syntax und vielleicht klarer (für mich), obwohl ich mir nicht sicher bin, ob sie das sind das gleiche (neues Feature in data.table Version 1.8.3).

Der Cross-Join kann manchmal nützlich sein, aber er passt möglicherweise nicht in das data.table-Paradigma.