2013-03-23 20 views
6

Dati due dataframes come qui sotto:come eseguire un interno o esterno unione di DataFrames con i panda sulla non semplicistiche criterio

>>> import pandas as pd 

>>> df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}]) 
>>> df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}]) 
>>> df_a 
    a b 
0 1 4 
1 2 5 
2 3 6 

>>> df_b 
    c d 
0 2 7 
1 3 8 

vorremmo produrre uno stile di SQL join di entrambe le dataframes utilizzando un non- criteri semplicistici, diciamo "df_b.c> df_a.a". Da quello che posso dire, mentre il merge() è sicuramente parte della soluzione, non posso usarlo direttamente dal momento che non accetta espressioni arbitrarie per i criteri "ON" (a meno che manchi qualcosa?).

In SQL, i risultati simile a questa:

# inner join 
sqlite> select * from df_a join df_b on c > a; 
1|4|2|7 
1|4|3|8 
2|5|3|8 

# outer join 
sqlite> select * from df_a left outer join df_b on c > a; 
1|4|2|7 
1|4|3|8 
2|5|3|8 
3|6|| 

mio approccio attuale per inner join è quello di produrre un prodotto cartesiano di df_a e df_b, con l'aggiunta di una colonna di "1" s ad entrambi, quindi usando unione() sulla colonna "1" s, quindi applicando i criteri "c> a".

>>> import numpy as np 
>>> df_a['ones'] = np.ones(3) 
>>> df_b['ones'] = np.ones(2) 
>>> cartesian = pd.merge(df_a, df_b, left_on='ones', right_on='ones') 
>>> cartesian 
    a b ones c d 
0 1 4  1 2 7 
1 1 4  1 3 8 
2 2 5  1 2 7 
3 2 5  1 3 8 
4 3 6  1 2 7 
5 3 6  1 3 8 
>>> cartesian[cartesian.c > cartesian.a] 
    a b ones c d 
0 1 4  1 2 7 
1 1 4  1 3 8 
3 2 5  1 3 8 

per outer join, non sono sicuro del modo migliore per andare, finora Ho giocato con ricevendo il join interno, applicando poi la negazione dei criteri per ottenere tutti gli altri righe, quindi prova a modificare quella "negazione" impostata sull'originale, ma in realtà non funziona.

Modifica. HYRY ha risposto alla domanda specifica qui, ma avevo bisogno di qualcosa di più generico e più all'interno dell'API Pandas, in quanto il mio criterio di adesione potrebbe essere qualsiasi cosa, non solo un confronto. Per join esterno, prima io sono l'aggiunta di un indice in più per il lato "sinistro" che mantenere se stesso dopo che faccio l'INNER JOIN:

df_a['_left_index'] = df_a.index 

allora gli facciamo lo cartesiano e ottenere il INNER JOIN:

cartesian = pd.merge(df_a, df_b, left_on='ones', right_on='ones') 
innerjoin = cartesian[cartesian.c > cartesian.a] 

tanto sono gli iD di indice supplementari in "df_a" che avremo bisogno, e ottenere le righe da "df_a":

remaining_left_ids = set(df_a['_left_index']).\ 
        difference(innerjoin['_left_index']) 
remaining = df_a.ix[remaining_left_ids] 

allora usiamo un concat diritto(), che sostituisce le colonne mancanti con "NaN" per sinistra (pensavo che lo fosse Non fare questo prima, ma credo che lo fa):

outerjoin = pd.concat([innerjoin, remaining]).reset_index() 

idea di HYRY per fare il cartesiano solo su quei colli che abbiamo bisogno di confrontare su è fondamentalmente la risposta giusta, anche se nel mio caso specifico potrebbe essere un po 'difficile da implementare (generalizzato e tutto).

domande:

  1. Come vi produrre un "join" di df_1 e df_2 su "c> a"? farebbe lo stesso approccio "prodotto cartesiano, filtro" o c'è un modo migliore ?

  2. Come si genera il "join esterno sinistro" dello stesso?

risposta

4

Io uso il metodo esterno del ufunc per calcolare il risultato, qui è l'esempio:

In primo luogo, alcuni dati:

import pandas as pd 
import numpy as np 
df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}, {"a": 4, "b": 8}, {"a": 1, "b": 7}]) 
df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}, {"c": 2, "d": 10}]) 
print "df_a" 
print df_a 
print "df_b" 
print df_b 

uscita:

df_a 
    a b 
0 1 4 
1 2 5 
2 3 6 
3 4 8 
4 1 7 
df_b 
    c d 
0 2 7 
1 3 8 
2 2 10 

interno unisciti, perché questo calcola solo il prodotto cartesiano di c & a, la memoria useage è minore di prodotto cartesiano di tutta dataframe:

ia, ib = np.where(np.less.outer(df_a.a, df_b.c)) 
print pd.concat((df_a.take(ia).reset_index(drop=True), 
       df_b.take(ib).reset_index(drop=True)), axis=1) 

uscita:

a b c d 
0 1 4 2 7 
1 1 4 3 8 
2 1 4 2 10 
3 2 5 3 8 
4 1 7 2 7 
5 1 7 3 8 
6 1 7 2 10 

per calcolare il join esterno sinistro, utilizzare numpy.setdiff1d() per trovare tutte le file di df_a che non nel join interno:

na = np.setdiff1d(np.arange(len(df_a)), ia) 
nb = -1 * np.ones_like(na) 
oa = np.concatenate((ia, na)) 
ob = np.concatenate((ib, nb)) 
print pd.concat([df_a.take(oa).reset_index(drop=True), 
       df_b.take(ob).reset_index(drop=True)], axis=1) 

uscita:

a b c d 
0 1 4 2 7 
1 1 4 3 8 
2 1 4 2 10 
3 2 5 3 8 
4 1 7 2 7 
5 1 7 3 8 
6 1 7 2 10 
7 3 6 NaN NaN 
8 4 8 NaN NaN 
+0

ancora l'analisi di questo, c'è un modo per raggiungere l'espressione utilizzando una serie Pandas (cioè generata da un'espressione come "df_a.a < df_b.c "? Non avrò necessariamente" a zzzeek

+0

ma solo l'idea di fare cartesiano sui cols ho bisogno di risparmiare memoria, vale la pena guardare in ... – zzzeek

0

Questo può essere fatto in questo modo con broadcasting e np.where. Utilizzare qualsiasi operatore binario che vuoi che restituisce Vero/Falso:

import operator as op 

df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}]) 
df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}]) 

binOp = op.lt 
matches = np.where(binOp(df_a.a[:,None],df_b.c.values)) 

print pd.concat([df.ix[idxs].reset_index(drop=True) 
       for df,idxs in zip([df_a,df_b],matches)], 
       axis=1).to_csv() 

, a, b, c, d

0,1,4,2,7

1,1,4 , 3,8

2,2,5,3,8

Problemi correlati