web-dev-qa-db-fra.com

Pourquoi la jointure X [Y] de data.tables n'autorise-t-elle pas une jointure externe complète ou une jointure gauche?

Ceci est un peu une question philosophique sur la syntaxe de jointure data.table. Je trouve de plus en plus d'utilisations pour data.tables, mais j'apprends toujours ...

Le format de jointure X[Y] Pour data.tables est très concis, pratique et efficace, mais pour autant que je sache, il ne prend en charge que les jointures internes et les jointures externes droites. Pour obtenir une jointure externe gauche ou complète, je dois utiliser merge:

  • X[Y, nomatch = NA] - toutes les lignes en Y - jointure externe droite (par défaut)
  • X[Y, nomatch = 0] - uniquement les lignes avec des correspondances à la fois en X et en Y - jointure interne
  • merge(X, Y, all = TRUE) - toutes les lignes de X et Y - jointure externe complète
  • merge(X, Y, all.x = TRUE) - toutes les lignes de X - jointure externe gauche

Il me semble que ce serait pratique si le format de jointure X[Y] Supportait les 4 types de jointures. Y a-t-il une raison pour laquelle seuls deux types de jointures sont pris en charge?

Pour moi, les valeurs des paramètres nomatch = 0 Et nomatch = NA Ne sont pas très intuitives pour les actions en cours. Il m'est plus facile de comprendre et de mémoriser la syntaxe merge: all = TRUE, all.x = TRUE Et all.y = TRUE. Étant donné que l'opération X[Y] Ressemble beaucoup plus à merge qu'à match, pourquoi ne pas utiliser la syntaxe merge pour les jointures plutôt que la match le paramètre nomatch de la fonction?

Voici des exemples de code des 4 types de jointure:

# 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

Mise à jour: data.table v1.9.6 a introduit la syntaxe on=, Qui permet des jointures ad hoc sur des champs autres que la clé primaire. réponse de jangorecki à la question Comment joindre (fusionner) des trames de données (interne, externe, gauche, droite)? fournit quelques exemples de types de jointure supplémentaires que data.table peut gérer .

112
Douglas Clark

Pour citer le data.tableFAQ 1.11 Quelle est la différence entre X[Y] Et merge(X, Y)?

X[Y] Est une jointure, recherchant les lignes de X en utilisant Y (ou la clé de Y si elle en a une) comme index.

Y[X] Est une jointure, recherchant les lignes de Y à l'aide de X (ou la clé de X si elle en a une)

merge(X,Y) fait les deux dans le même temps. Le nombre de lignes de X[Y] Et Y[X] Diffère généralement, tandis que le nombre de lignes renvoyées par merge(X,Y) et merge(Y,X) est le même.

MAIS cela manque le point principal. La plupart des tâches nécessitent que quelque chose soit fait sur les données après une jointure ou une fusion. Pourquoi fusionner toutes les colonnes de données, pour n'en utiliser qu'un petit sous-ensemble par la suite? Vous pouvez suggérer merge(X[,ColsNeeded1],Y[,ColsNeeded2]), mais cela nécessite que le programmeur détermine quelles colonnes sont nécessaires. X[Y,j] Dans data.table fait tout cela en une seule étape pour vous. Lorsque vous écrivez X[Y,sum(foo*bar)], data.table inspecte automatiquement l'expression j pour voir quelles colonnes il utilise. Il ne sous-ensemble que ces colonnes; les autres sont ignorés. La mémoire n'est créée que pour les colonnes que j utilise et les colonnes Y bénéficient des règles de recyclage R standard dans le contexte de chaque groupe. Disons que foo est dans X, et la barre est dans Y (avec 20 autres colonnes dans Y). Est-ce que X[Y,sum(foo*bar)] n'est pas plus rapide à programmer et plus rapide à exécuter qu'une fusion de tout suivi inutilement par un sous-ensemble?


Si vous voulez une jointure externe gauche de 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

Si vous souhaitez une jointure externe complète

# 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)]]
66
mnel

La réponse de @ mnel est parfaite, alors acceptez cette réponse. C'est juste un suivi, trop long pour des commentaires.

Comme le dit mnel, la jointure externe gauche/droite est obtenue en échangeant Y et X: Y[X] -Vs- X[Y]. Ainsi, 3 des 4 types de jointure sont pris en charge dans cette syntaxe, pas 2, iiuc.

Ajouter le 4ème semble une bonne idée. Supposons que nous ajoutions full=TRUE Ou both=TRUE Ou merge=TRUE (Vous n'êtes pas sûr du meilleur nom d'argument?) Alors cela ne m'était pas venu à l'esprit avant que X[Y,j,merge=TRUE] Ne être utile pour les raisons après le MAIS dans FAQ 1.12. Nouvelle demande de fonctionnalité maintenant ajoutée et liée ici, merci:

FR # 2301: Ajouter l'argument merge = TRUE pour les jointures X [Y] et Y [X] comme le fait merge ().

Les versions récentes ont accéléré merge.data.table (En prenant une copie superficielle en interne pour définir les clés plus efficacement, par exemple). Nous essayons donc de rapprocher merge() et X[Y] Et de fournir toutes les options à l'utilisateur pour une flexibilité totale. Il y a des avantages et des inconvénients des deux. Une autre demande de fonctionnalité en suspens est:

FR # 2033: Ajoutez by.x et by.y à merge.data.table

S'il y en a d'autres, continuez à les faire venir.

Par cette partie de la question:

pourquoi ne pas utiliser la syntaxe de fusion pour les jointures plutôt que le paramètre nomatch de la fonction de correspondance?

Si vous préférez la syntaxe merge() et ses 3 arguments all, all.x Et all.y, Utilisez-le simplement au lieu de X[Y]. Pensez qu'il devrait couvrir tous les cas. Ou vouliez-vous dire pourquoi l'argument est-il un simple nomatch dans [.data.table? Si c'est le cas, c'est juste la façon qui semblait naturelle étant donné FAQ 2.14: "Pouvez-vous expliquer plus en détail pourquoi data.table est inspiré par la syntaxe A [B] dans la base?". Mais aussi, nomatch ne prend actuellement que deux valeurs 0 et NA. Cela pourrait être étendu de sorte qu'une valeur négative signifiait quelque chose, ou 12 signifierait utiliser les valeurs de la 12e ligne pour remplir les NA, pour exemple, ou nomatch à l'avenir pourrait être un vecteur ou même lui-même un data.table.

Hm. Comment by-without-by interagirait-il avec merge = TRUE? Peut-être devrions-nous reprendre cela à datatable-help .

21
Matt Dowle

Cette "réponse" est une proposition de discussion: comme indiqué dans mon commentaire, je suggère d'ajouter un paramètre join à [.data.table () pour activer d'autres types de jointures, c'est-à-dire: X[Y,j,join=string] . En plus des 4 types de jointures ordinaires, je suggère également de prendre en charge 3 types de jointures exclusive et la jointure cross.

Les valeurs de chaîne join (et alias) pour les différents types de jointures sont proposées comme suit:

  1. "all.y" Et "right" - jointure à droite, la valeur par défaut actuelle de data.table (nomatch = NA) - toutes les lignes Y avec NA où il n'y a pas de correspondance X;
  2. "both" Et "inner" - jointure interne (nomatch = 0) - uniquement les lignes où X et Y correspondent;

  3. "all.x" Et "left" - jointure gauche - toutes les lignes de X, NA où aucun Y ne correspond:

  4. "outer" Et "full" - jointure externe complète - toutes les lignes de X et Y, NA où aucune correspondance

  5. "only.x" Et "not.y" - non-jointure ou anti-jointure renvoyant des lignes X où il n'y a pas de correspondance Y

  6. "only.y" Et "not.x" - non-jointure ou anti-jointure renvoyant des lignes Y où il n'y a pas de correspondance X
  7. "not.both" - jointure exclusive renvoyant des lignes X et Y là où il n'y a pas de correspondance avec l'autre table, c'est-à-dire un exclusif ou (XOR)
  8. "cross" - jointure croisée ou produit cartésien avec chaque ligne de X correspondant à chaque ligne de Y

La valeur par défaut est join="all.y" Qui correspond à la valeur par défaut actuelle.

Les valeurs de chaîne "all", "all.x" et "all.y" correspondent aux paramètres merge(). Les chaînes "droite", "gauche", "intérieure" et "extérieure" peuvent être plus accessibles aux utilisateurs SQL.

Les chaînes "both" et "not.both" sont ma meilleure suggestion pour le moment - mais quelqu'un peut avoir de meilleures suggestions de chaînes pour la jointure interne et la jointure exclusive. (Je ne sais pas si "exclusif" est la bonne terminologie, corrigez-moi s'il existe un terme approprié pour une jointure "XOR".)

L'utilisation de join="not.y" Est une alternative à la syntaxe de non-jointure X[-Y,j] Ou X[!Y,j] Et peut-être plus claire (pour moi), même si je ne suis pas sûr que ce soit la même ( nouvelle fonctionnalité de data.table version 1.8.3).

La jointure croisée peut parfois être utile, mais elle peut ne pas correspondre au paradigme data.table.

15
Douglas Clark