Perché il join X [Y] di data.tables non consente un join esterno completo o un join sinistro?


123

Questa è una domanda un po 'filosofica sulla sintassi del join di data.table. Sto trovando sempre più usi per data.tables, ma sto ancora imparando ...

Il formato di join X[Y]per data.tables è molto conciso, pratico ed efficiente, ma per quanto ne so, supporta solo inner join e right outer join. Per ottenere un join esterno sinistro o completo, devo usare merge:

  • X[Y, nomatch = NA] - tutte le righe in Y - join esterno destro (predefinito)
  • X[Y, nomatch = 0] - solo righe con corrispondenze sia in X che in Y - join interno
  • merge(X, Y, all = TRUE) - tutte le righe da X e Y - join esterno completo
  • merge(X, Y, all.x = TRUE) - tutte le righe in X - join esterno sinistro

Mi sembra che sarebbe utile se il X[Y]formato di join supportasse tutti e 4 i tipi di join. C'è un motivo per cui sono supportati solo due tipi di join?

Per me, i valori dei parametri nomatch = 0e nomatch = NAnon sono molto intuitivi per le azioni eseguite. E 'più facile per me capire e ricordo la mergesintassi: all = TRUE, all.x = TRUEe all.y = TRUE. Poiché l' X[Y]operazione assomiglia mergemolto di più a match, perché non utilizzare la mergesintassi per i join anziché il parametro matchdella funzione nomatch?

Di seguito sono riportati esempi di codice dei 4 tipi di join:

# 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

Aggiornamento: data.table v1.9.6 ha introdotto la on=sintassi, che consente join ad hoc su campi diversi dalla chiave primaria. La risposta di jangorecki alla domanda Come unire (unire) frame di dati (interno, esterno, sinistro, destro)? fornisce alcuni esempi di tipi di join aggiuntivi che data.table può gestire.


4
Hai letto la FAQ 1.12 ? Puoi sempre chiamare Y[X]se vuoi il join esterno sinistro di X[Y]e rbind(Y[X],X[Y])se vuoi un join esterno completo
mnel

Vedi la mia risposta per un approccio più data.table al join esterno completo
mnel

@mnel, presumo che il tuo unique()approccio di seguito per il join completo sia preferibile a rbind(Y[X],X[Y]), poiché rbind comporterebbe la copia della tabella. È giusto?
Douglas Clark

per quanto ne so, sì. Non ho testato se tre chiamate univoche più piccole sono più veloci di una grande (ad esempio unique(c(unique(X[,t]), unique(Y[,t])), dovrebbe essere più efficiente in termini di memoria poiché combina solo due elenchi che saranno inferiori o uguali al numero di righe in X e Y .
mnel

2
La tua domanda è una descrizione così buona; Ho trovato le risposte alle mie domande nella tua domanda. Grazie
irriss

Risposte:


71

Per citare la data.table FAQ 1.11 Qual è la differenza tra X[Y]e merge(X, Y)?

X[Y] è un join, che cerca le righe di X usando Y (o la chiave di Y se ne ha una) come indice.

Y[X] è un join, cerca le righe di Y usando X (o la chiave di X se ne ha una)

merge(X,Y)funziona in entrambi i modi contemporaneamente. Il numero di righe di X[Y]e di Y[X]solito è diverso, mentre il numero di righe restituite da merge(X,Y)e merge(Y,X)è lo stesso.

MA questo manca il punto principale. La maggior parte delle attività richiede l'esecuzione di qualcosa sui dati dopo un'unione o un'unione. Perché unire tutte le colonne di dati, solo per utilizzarne un piccolo sottoinsieme in seguito? Potresti suggerire merge(X[,ColsNeeded1],Y[,ColsNeeded2]), ma ciò richiede al programmatore di capire quali colonne sono necessarie. X[Y,j] in data.table fa tutto questo in un unico passaggio per te. Quando scrivi X[Y,sum(foo*bar)], data.table ispeziona automaticamente l' jespressione per vedere quali colonne utilizza. Subset solo quelle colonne; gli altri vengono ignorati. La memoria viene creata solo per le colonne jutilizzate e le Ycolonne godono delle regole di riciclaggio R standard nel contesto di ciascun gruppo. Diciamo che fooè in Xe bar è in Y(insieme ad altre 20 colonne in Y). non èX[Y,sum(foo*bar)] più veloce da programmare e più veloce da eseguire rispetto a una fusione di tutto seguita in modo sprecato da un sottoinsieme?


Se vuoi un join esterno sinistro di 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

Se vuoi un join esterno completo

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

5
Grazie @mnel. La FAQ 1.12 non menziona il join esterno completo o sinistro. Il tuo suggerimento completo per l'unione esterna con unique () è di grande aiuto. Dovrebbe essere nelle FAQ. So che Matthew Dowle "lo ha progettato per il proprio uso e lo voleva in questo modo". (FAQ 1.9), ma ho pensato che X[Y,all=T]potesse essere un modo elegante per specificare un outer join completo all'interno della sintassi data.table X [Y]. O X[Y,all.x=T]per il join sinistro. Mi chiedevo perché non fosse stato progettato in quel modo. Solo un pensiero.
Douglas Clark

1
@DouglasClark Hanno aggiunto la risposta e presentato 2302: Aggiungi la sintassi di unione di mnel alle domande frequenti (con i tempi) . Ottimi suggerimenti!
Matt Dowle

1
@mnel Grazie per la soluzione ... ha reso la mia giornata ... :)
Ankit

@mnel C'è un modo per imputare NA con 0 durante l'esecuzione X[Y[J(unique_keys)]]?
Ankit

11
ciò che mi colpisce della documentazione di data.table è che può essere così prolissa, ma rimanere così criptica ...
NiuBiBang

24

La risposta di @ mnel è perfetta, quindi accetta quella risposta. Questo è solo un seguito, troppo lungo per i commenti.

Come dice mnel, l'unione esterna sinistra / destra si ottiene scambiando Ye X: Y[X]-vs- X[Y]. Quindi 3 dei 4 tipi di join sono supportati in quella sintassi, non 2, iiuc.

Aggiungere il 4 ° sembra una buona idea. Diciamo che aggiungiamo full=TRUEo both=TRUEo merge=TRUE(non sei sicuro del miglior nome dell'argomento?) Allora non mi era venuto in mente prima che X[Y,j,merge=TRUE]sarebbe stato utile per i motivi dopo il MA nella FAQ 1.12. Nuova richiesta di funzionalità ora aggiunta e collegata di nuovo qui, grazie:

FR # 2301: Aggiungi merge = TRUE argomento per entrambi i join X [Y] e Y [X] come fa merge ().

Le versioni recenti sono state velocizzate merge.data.table(ad esempio, prendendo internamente una copia superficiale per impostare le chiavi in ​​modo più efficiente). Quindi stiamo cercando di portare merge()e X[Y]più da vicino, e di fornire tutte le opzioni per utente per la massima flessibilità. Ci sono pro e contro di entrambi. Un'altra richiesta di funzionalità eccezionale è:

FR # 2033: Aggiungi by.x e by.y a merge.data.table

Se ce ne sono altri, ti preghiamo di farli arrivare.

Da questa parte della domanda:

perché non utilizzare la sintassi merge per i join piuttosto che il parametro nomatch della funzione match?

Se si preferisce merge()la sintassi e le sue 3 argomenti all, all.xe all.ypoi basta usare che, invece di X[Y]. Penso che dovrebbe coprire tutti i casi. O intendevi perché l'argomento è un singolo nomatchin [.data.table? Se è così, è proprio il modo che sembrava naturale data la FAQ 2.14: "Puoi spiegare ulteriormente perché data.table si ispira alla sintassi A [B] in base?". Ma al momento nomatchaccetta solo due valori 0e NA. Ciò potrebbe essere esteso in modo che un valore negativo significhi qualcosa, oppure 12 significherebbe utilizzare i valori della 12a riga per riempire NA, ad esempio, o nomatchin futuro potrebbe essere un vettore o addirittura a data.table.

Hm. Come interagirebbe by-without-by con merge = TRUE? Forse dovremmo portare questo a datatable-help .


Grazie @Matthew. La risposta di @ mnel è eccellente, ma la mia domanda non era come eseguire un join completo o sinistro, ma "C'è una ragione per cui sono supportati solo due tipi di join?" Quindi ora è un po 'più filosofico ;-) In realtà non preferisco la sintassi di unione, ma sembra che ci sia una tradizione R per costruire su cose esistenti che la gente ha familiarità. Avevo scarabocchiato join="all", join="all.x", join="all.y" and join="x.and.y"a margine dei miei appunti. Non sono sicuro che sia meglio.
Douglas Clark

@DouglasClark Forse joincosì, buona idea. Ho postato su datatable-help quindi vediamo. Forse concedi anche un data.tablepo 'di tempo per sistemarti. Ad esempio, sei già riuscito a fare il by-without-by e unirti all'ambito ereditato ?
Matt Dowle

Come indicato nel mio commento di cui sopra, vi suggerisco di aggiungere una joinparola chiave per, quando i è un DataTable: X[Y,j,join=string]. I possibili valori di stringa per join sono suggeriti come: 1) "all.y" e "right" -
Douglas Clark

1
Ciao Matt, la libreria data.table è fantastica; grazie per questo; sebbene penso che il comportamento del join (essendo un join esterno destro per impostazione predefinita) dovrebbe essere spiegato in modo prominente nella documentazione principale; mi ci sono voluti 3 giorni per capirlo.
Timothée HENRY

1
@tucson Solo per collegare qui, ora archiviato come numero 709 .
Matt Dowle

17

Questa "risposta" è una proposta di discussione: Come indicato nel mio commento, vi suggerisco di aggiungere un joinparametro da [.data.table () per consentire ulteriori tipi di join, vale a dire: X[Y,j,join=string]. Oltre ai 4 tipi di join ordinari, suggerisco anche di supportare 3 tipi di join esclusivi e il cross join.

Si joinpropone che i valori di stringa (e gli alias) per i vari tipi di join siano:

  1. "all.y"e "right"- right join, l'attuale data.table predefinito (nomatch = NA) - tutte le righe Y con NA dove non c'è corrispondenza X;
  2. "both"e "inner" - inner join (nomatch = 0) - solo le righe in cui X e Y corrispondono;

  3. "all.x"e "left" - join sinistro - tutte le righe da X, NA dove nessuna corrispondenza Y:

  4. "outer"e "full" - full outer join - tutte le righe da X e Y, NA dove nessuna corrispondenza

  5. "only.x"e "not.y"- non join o anti-join che restituiscono X righe dove non c'è corrispondenza Y

  6. "only.y" e "not.x"- non join o anti-join restituiscono righe Y dove non c'è corrispondenza X
  7. "not.both" - join esclusivo che restituisce righe X e Y in cui non c'è corrispondenza con l'altra tabella, cioè un esclusivo-o (XOR)
  8. "cross"- cross join o prodotto cartesiano con ciascuna riga di X abbinata a ciascuna riga di Y

Il valore predefinito è join="all.y"che corrisponde al valore predefinito attuale.

I valori di stringa "all", "all.x" e "all.y" corrispondono ai merge()parametri. Le stringhe "destra", "sinistra", "interna" ed "esterna" possono essere più adatte agli utenti SQL.

Le stringhe "both" e "not.both" sono il mio miglior suggerimento al momento, ma qualcuno potrebbe avere suggerimenti di stringhe migliori per il join interno e il join esclusivo. (Non sono sicuro che "esclusivo" sia la terminologia corretta, correggimi se esiste un termine appropriato per un join "XOR".)

L'uso di join="not.y"è un'alternativa alla sintassi X[-Y,j]o X[!Y,j]non-join e forse è più chiara (per me), anche se non sono sicuro che siano la stessa cosa (nuova funzionalità in data.table versione 1.8.3).

Il cross join può essere utile a volte, ma potrebbe non adattarsi al paradigma data.table.


1
Si prega di inviare questo a datatable-help per la discussione.
Matt Dowle

3
+1 Ma, favore , invia a datatable-help o invia una richiesta di funzionalità . Non mi dispiace aggiungere joinma, a meno che non entri nel tracker, verrà dimenticato.
Matt Dowle

1
Vedo che non accedi a SO da un po '. Quindi ho archiviato questo in FR # 2301
Matt Dowle

@ MattDowle, +1 per questa funzione. ( Ho provato a farlo tramite FR # 2301 ma ricevo un messaggio di autorizzazione negata).
adilapapaya

@adilapapaya Ci siamo trasferiti da RForge a GitHub. Si prega di fare +1 qui: github.com/Rdatatable/data.table/issues/614 . Arun ha portato i problemi in modo che non andassero persi.
Matt Dowle
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.