Warum erlaubt die X [Y] -Verbindung von data.tables keine vollständige äußere Verknüpfung oder eine linke Verknüpfung?

122

Dies ist eine philosophische Frage zur Join-Syntax von data.table. Ich finde immer mehr Verwendungsmöglichkeiten für data.tables, lerne aber immer noch ...

Das Join-Format X[Y]für data.tables ist sehr präzise, ​​praktisch und effizient, aber soweit ich das beurteilen kann, unterstützt es nur innere Joins und rechte äußere Joins. Um eine linke oder vollständige äußere Verknüpfung zu erhalten, muss ich Folgendes verwenden merge:

  • X[Y, nomatch = NA] - alle Zeilen in Y - rechter äußerer Join (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 - vollständige äußere Verknüpfung
  • merge(X, Y, all.x = TRUE) - alle Zeilen in X - linker äußerer Join

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

Für mich sind die Werte nomatch = 0und nomatch = NAParameter für die ausgeführten Aktionen nicht sehr intuitiv. Es fällt mir leichter, die mergeSyntax zu verstehen und sich daran zu erinnern : all = TRUE, all.x = TRUEund all.y = TRUE. Da die X[Y]Operation mergeviel mehr ähnelt als match, warum nicht die mergeSyntax für Verknüpfungen anstelle des matchFunktionsparameters verwenden 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
#    t  a
# 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
#    t  b
# 1: 3  9
# 2: 4 16
# 3: 5 25
# 4: 6 36

# all rows from Y - right outer join
X[Y]  # default
#  t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

X[Y, nomatch = NA]  # same as above
#    t  a  b
# 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
#    t  a  b
# 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]  
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t")  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t", all = FALSE)  # same as above
#    t  a  b
# 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)
#    t  a  b
# 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)
#    t  a  b
# 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- Verknüpfungen für andere Felder als den Primärschlüssel ermöglicht. jangoreckis Antwort auf die Frage Wie werden Datenrahmen (innen, außen, links, rechts) verbunden (zusammengeführt)? bietet einige Beispiele für zusätzliche Join-Typen, die data.table verarbeiten kann.

Douglas Clark
quelle
4
Hast du FAQ 1.12 gelesen ? Sie können jederzeit anrufen, Y[X]wenn Sie die linke äußere Verknüpfung von möchten X[Y]und rbind(Y[X],X[Y])wenn Sie eine vollständige äußere Verknüpfung
wünschen
Siehe meine Antwort für mehr data.table Ansatz für die vollständige äußere Verbindung
8.
@mnel, ich gehe davon aus, dass Ihr unique()Ansatz unten für den vollständigen Join vorzuziehen ist rbind(Y[X],X[Y]), da das rbind das Kopieren der Tabelle beinhalten würde. Ist das richtig?
Douglas Clark
nach bestem Wissen ja. Ich habe nicht getestet, ob drei kleinere eindeutige Aufrufe schneller als ein großer sind (z. B. unique(c(unique(X[,t]), unique(Y[,t]))- dies sollte speichereffizienter sein, da nur zwei Listen kombiniert werden, die kleiner oder gleich der Anzahl der Zeilen in X und Y sind .
Mnel
2
Ihre Frage so eine gute Beschreibung; Ich habe Antworten auf meine Fragen in Ihrer Frage gefunden. Danke
irriss

Antworten:

71

Zitat aus den data.table FAQ 1.11 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) als Index gesucht werden.

Y[X] ist ein Join, bei dem Ys Zeilen mit X (oder Xs Schlüssel, falls vorhanden) nachgeschlagen werden.

merge(X,Y)macht beide Wege gleichzeitig. Die Anzahl der Zeilen von X[Y]und Y[X]unterscheidet sich normalerweise, während die Anzahl der von merge(X,Y)und zurückgegebenen Zeilen merge(Y,X)gleich ist.

ABER das verfehlt den Hauptpunkt. Bei den meisten Aufgaben müssen nach einem Join oder Merge etwas an den Daten ausgeführt werden. Warum alle Datenspalten zusammenführen, um anschließend nur eine kleine Teilmenge davon zu verwenden? Sie können vorschlagen merge(X[,ColsNeeded1],Y[,ColsNeeded2]), aber das erfordert, dass der Programmierer herausfindet, welche Spalten benötigt werden. X[Y,j] in data.table erledigt das alles in einem Schritt für Sie. Beim Schreiben X[Y,sum(foo*bar)]überprüft data.table den jAusdruck automatisch, um festzustellen , welche Spalten verwendet werden. Es werden nur diese Spalten untergeordnet. Die anderen werden ignoriert. Der Speicher wird nur für die Spalten erstellt, die jverwendet werden, und YSpalten unterliegen den Standardregeln für das R-Recycling im Kontext jeder Gruppe. Nehmen wir an, es fooist in Xund die Leiste ist in Y(zusammen mit 20 anderen Spalten in Y). Ist nichtX[Y,sum(foo*bar)] schneller zu programmieren und schneller auszuführen als eine Verschmelzung von allem, gefolgt von einer Teilmenge?


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

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 Verknüpfung 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)]]
##   t  b  a
## 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)]]
mnel
quelle
5
Danke @mnel. In FAQ 1.12 wird die vollständige oder linke äußere Verknüpfung nicht erwähnt. Ihr vollständiger Vorschlag für eine äußere Verknüpfung mit unique () ist eine große Hilfe. Das sollte in den FAQ stehen. Ich weiß, dass Matthew Dowle "es für seinen eigenen Gebrauch entworfen hat, und er wollte es so." (FAQ 1.9), aber ich dachte, es X[Y,all=T]könnte eine elegante Möglichkeit sein, einen vollständigen äußeren Join innerhalb der data.table X [Y] -Syntax anzugeben. Oder X[Y,all.x=T]für die linke Verbindung. Ich fragte mich, warum es nicht so gestaltet war. Nur ein Gedanke.
Douglas Clark
1
@DouglasClark Habe eine Antwort hinzugefügt und 2302 abgelegt : Füge die Merge Join-Syntax von mnel zu den FAQ hinzu (mit Timings) . Tolle Vorschläge!
Matt Dowle
1
@mnel Danke für die Lösung ... machte meinen Tag ... :)
Ankit
@mnel Gibt es eine Möglichkeit, NAs bei der Aufführung mit 0 zu unterstellen X[Y[J(unique_keys)]]?
Ankit
11
Was mich an der Dokumentation von data.table beeindruckt, ist, dass sie so ausführlich sein kann und dennoch so kryptisch bleibt ...
NiuBiBang
24

Die Antwort von @ mnel ist genau richtig. Akzeptieren Sie diese Antwort. Dies ist nur eine Nachverfolgung, zu lang für Kommentare.

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

Das Hinzufügen des 4. scheint eine gute Idee zu sein. Nehmen wir an, wir fügen hinzu full=TRUEoder both=TRUEoder merge=TRUE(nicht sicher, ob der beste Argumentname?), Dann war es mir vorher nicht in den Sinn gekommen, X[Y,j,merge=TRUE]was aus den Gründen nach dem ABER in FAQ 1.12 nützlich wäre. Neue Feature-Anfrage jetzt hinzugefügt und hierher verlinkt, danke:

FR # 2301: Fügen Sie das Argument merge = TRUE für X [Y] und Y [X] hinzu, wie dies bei merge () der Fall ist.

Neuere Versionen haben sich beschleunigt merge.data.table(indem Sie beispielsweise eine flache Kopie intern erstellen, um die Schlüssel effizienter einzustellen). So versuchen wir , zu bringen merge()und X[Y]näher, und bieten alle Optionen Benutzer für volle Flexibilität. Beides hat Vor- und Nachteile. 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, lassen Sie sie bitte kommen.

Durch diesen Teil in der Frage:

Warum nicht die Zusammenführungssyntax für Joins anstelle des Nomatch-Parameters der Match-Funktion verwenden?

Wenn Sie es vorziehen , merge()Syntax und seine drei Argumente all, all.xund all.yverwenden Sie dann nur , dass statt X[Y]. Denken Sie, es sollte alle Fälle abdecken. Oder meinten Sie , warum ist das Argument , eine einzige nomatchin [.data.table? Wenn ja, ist dies angesichts der häufig gestellten Fragen 2.14 nur natürlich. "Können Sie weiter erklären, warum data.table von der A [B] -Syntax in base inspiriert ist?". Nimmt aber auch nomatchnur zwei Werte aktuell 0und an NA. Das könnte erweitert werden, so dass ein negativer Wert etwas bedeutet, oder 12 würde bedeuten, die Werte der 12. Zeile zum Ausfüllen von NAs zu verwenden, oder könnte nomatchin Zukunft ein Vektor oder sogar selbst ein sein data.table.

Hm. Wie würde By-Without- By mit Merge = TRUE interagieren? Vielleicht sollten wir dies auf datatable-help übertragen .

Matt Dowle
quelle
Danke @Matthew. Die Antwort von @ mnel ist ausgezeichnet, aber meine Frage war nicht, wie ein vollständiger oder linker Join durchgeführt werden soll, sondern "Gibt es einen Grund, warum nur zwei Arten von Joins unterstützt werden?" Jetzt ist es also etwas philosophischer ;-) Eigentlich bevorzuge ich keine Merge-Syntax, aber es scheint eine R-Tradition zu geben, auf vorhandenen Dingen aufzubauen, mit denen die Leute vertraut sind. Ich hatte join="all", join="all.x", join="all.y" and join="x.and.y"am Rand meiner Notizen gekritzelt . Ich bin mir nicht sicher, ob das besser ist.
Douglas Clark
@DouglasClark Vielleicht joinso, gute Idee. Ich habe auf datatable-help gepostet, also mal sehen. Vielleicht geben Sie auch data.tableetwas Zeit, um sich einzuleben. Haben Sie von-ohne-by noch zum Beispiel, und join geerbt Umfang ?
Matt Dowle
Wie in meinem obigen Kommentar angegeben, schlage ich vor, ein joinSchlüsselwort hinzuzufügen , wenn ich eine Datentabelle bin : X[Y,j,join=string]. Die möglichen Zeichenfolgenwerte für den Join werden wie folgt vorgeschlagen: 1) "all.y" und "right" -
Douglas Clark
1
Hallo Matt, die data.table-Bibliothek ist fantastisch. Danke für das; obwohl ich denke, dass das Join-Verhalten (standardmäßig ein rechter äußerer Join) in der Hauptdokumentation deutlich erklärt werden sollte; Ich habe 3 Tage gebraucht, um das herauszufinden.
Timothée HENRY
1
@tucson Nur um hier zu verlinken, jetzt als Ausgabe # 709 abgelegt .
Matt Dowle
17

Diese "Antwort" ist ein Diskussionsvorschlag: Wie in meinem Kommentar angegeben, schlage ich vor join, [.data.table () einen Parameter hinzuzufügen, um zusätzliche Arten von Verknüpfungen zu aktivieren, z X[Y,j,join=string]. Zusätzlich zu den 4 Arten von normalen Joins empfehle ich, 3 Arten von exklusiven Joins und den Cross- Join zu unterstützen.

Die joinZeichenfolgenwerte (und Aliase) für die verschiedenen Verknüpfungstypen lauten wie folgt:

  1. "all.y"und "right"- Rechtsverknüpfung, der aktuelle Standardwert für data.table (nomatch = NA) - alle Y-Zeilen mit NAs, bei denen keine X-Übereinstimmung vorliegt;
  2. "both"und "inner" - innerer Join (nomatch = 0) - nur Zeilen, in denen X und Y übereinstimmen;

  3. "all.x"und "left" - linker Join - alle Zeilen von X, NAs, bei denen kein Y übereinstimmt:

  4. "outer"und "full" - vollständige äußere Verknüpfung - alle Zeilen von X und Y, NAs, bei denen keine Übereinstimmung vorliegt

  5. "only.x"und "not.y"- Nicht-Join- oder Anti-Join-Rückgabe von X-Zeilen, bei denen keine Y-Übereinstimmung vorliegt

  6. "only.y" und "not.x"- Nicht-Join- oder Anti-Join-Rückgabe von Y-Zeilen, bei denen keine X-Übereinstimmung vorliegt
  7. "not.both" - Exklusiver Join, der X- und Y-Zeilen zurückgibt, bei denen keine Übereinstimmung mit der anderen Tabelle besteht, dh ein Exklusiv-Oder (XOR)
  8. "cross"- Cross Join oder kartesisches Produkt, wobei jede X-Reihe mit jeder Y-Reihe übereinstimmt

Der Standardwert join="all.y"entspricht dem aktuellen Standardwert.

Die Zeichenfolgenwerte "all", "all.x" und "all.y" entsprechen den merge()Parametern. Die Zeichenfolgen "rechts", "links", "innen" und "außen" sind für SQL-Benutzer möglicherweise besser geeignet.

Die Zeichenfolgen "beide" und "nicht beide" sind im Moment mein bester Vorschlag - aber jemand hat möglicherweise bessere Zeichenfolgenvorschläge für den inneren Join und den exklusiven Join. (Ich bin nicht sicher, ob "exklusiv" die richtige Terminologie ist. Korrigieren Sie mich, wenn es einen richtigen Begriff für einen "XOR" -Verbindung gibt.)

Die Verwendung von join="not.y"ist eine Alternative für X[-Y,j]oder X[!Y,j]ohne Join-Syntax und möglicherweise klarer (für mich), obwohl ich nicht sicher bin, ob sie identisch sind (neue Funktion in data.table Version 1.8.3).

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

Douglas Clark
quelle
1
Bitte senden Sie dies zur Diskussion an datatable-help .
Matt Dowle
3
+1 Bitte senden Sie jedoch entweder eine Hilfe an datatable-help oder reichen Sie eine Funktionsanforderung ein . Es macht mir nichts aus, etwas hinzuzufügen, joinaber wenn es nicht auf den Tracker gelangt, wird es vergessen.
Matt Dowle
1
Ich sehe, dass Sie sich eine Weile nicht mehr bei SO angemeldet haben. Also habe ich dies in FR # 2301
Matt Dowle
@MattDowle, +1 für diese Funktion. ( Ich habe versucht, dies über FR # 2301 zu tun, aber ich erhalte die Meldung "Berechtigungen verweigert".)
Adilapapaya
@adilapapaya Wir sind von RForge zu GitHub gewechselt. Bitte +1 hier: github.com/Rdatatable/data.table/issues/614 . Arun portierte die Probleme, damit sie nicht verloren gingen.
Matt Dowle