2016-06-09 14 views
5

Ho un semplice dataframe in questo modo:Come posso unire due dataframe con 'caratteri jolly'?

p  b 
0 a buy 
1 b buy 
2 a sell 
3 b sell 

e una tabella di ricerca come questa:

p  b v 
0 a buy 123 
1 a sell 456 
2 a  * 888 
4 b  * 789 

Come posso unire (join) i due dataframes, ma rispettando il 'jolly' nella colonna B, vale a dire il risultato atteso è:

p  b v 
0 a buy 123 
1 b buy 789 
2 a sell 456 
3 b sell 789 

il meglio che posso venire in mente è questo, ma è piuttosto brutto e verbose:

data = pd.DataFrame([ 
     ['a', 'buy'], 
     ['b', 'buy'],   
     ['a', 'sell'], 
     ['b', 'sell'],    
    ], columns = ['p', 'b']) 
lookup = pd.DataFrame([ 
     ['a', 'buy', 123], 
     ['a', 'sell', 456], 
     ['a', '*', 888], 
     ['b', '*', 789],   
], columns = ['p','b', 'v']) 

x = data.reset_index() 
y1 = pd.merge(x, lookup, on=['p', 'b'], how='left').set_index('index') 
y2 = pd.merge(x[y1['v'].isnull()], lookup, on=['p'], how='left').set_index('index') 
data['v'] = y1['v'].fillna(y2['v']) 

C'è un modo più intelligente?

+0

Nel risultato atteso sopra, perché non ci sono i file con 'V pari a 888? – unutbu

+0

Buona domanda: è perché il jolly si applica solo quando non c'è una corrispondenza più specifica. – Matthew

+1

@Mattagna se questo è qualcosa che hai creato, devi pensare al modello di dati. – Merlin

risposta

5

Penso che un po 'più pulito è quello di ripulire il wildcards prima:

In [11]: wildcards = lookup[lookup["b"] == "*"] 

In [12]: wildcards.pop("b") # ditch the * column, it'll confuse the later merge 

Ora è possibile combinare le due unioni (senza bisogno set_index) con un update:

In [13]: res = df.merge(lookup, how="left") 

In [14]: res 
Out[14]: 
    p  b  v 
0 a buy 123.0 
1 b buy NaN 
2 a sell 456.0 
3 b sell NaN 

In [15]: res.update(df.merge(wildcards, how="left"), overwrite=False) 

In [16]: res 
Out[16]: 
    p  b  v 
0 a buy 123.0 
1 b buy 789.0 
2 a sell 456.0 
3 b sell 789.0 
+1

Mi piace questa soluzione! L'unico problema è che ho un caso in cui ci sono più colonne con i (potenziali) caratteri jolly. Non è ovvio come potrei estenderlo a questo scenario. – Matthew

+0

@Matthew, sfortunatamente, 'b buy 888.0' dovrebbe essere 'b buy 789', ma 3 upvotes, :-) – Merlin

+0

@Matthew fammi solo aggiustarlo! –

1

ho trovato questo intuitivo:

def find_lookup(lookup, p, b): 
    ps = lookup.p == p 
    bs = lookup.b.isin([b, '*']) 
    return lookup.loc[ps & bs].iloc[0].replace('*', b) 

data.apply(lambda x: find_lookup(lookup, x.loc['p'], x.loc['b']), axis=1) 

    p  b v 
0 a buy 123 
1 b buy 789 
2 a sell 456 
3 b sell 789 
+0

Hmm Mi piace dove stai andando. Penso che groupby -> applicare potrebbe essere più pulito - lasciami pensare ... – Matthew

1

Ho trovato un altro soluzione, ispirata da alcune delle idee di cui sopra (grazie a tutti!). È più bello del mio primo tentativo, quindi lo metto qui, anche se sono sicuro che c'è spazio per miglioramenti. Questa soluzione presuppone ricerca viene ordinata in modo che jolly sono in fondo della tabella:

x = data.reset_index().merge(lookup, on=['p'], suffixes=["", "_y"]) 
x = x[(x['b'] == x['b_y']) | (x['b_y'] == '*')] 
x = x.groupby('index').first() # see note about sorting lookup! 
x[['p', 'b', 'c', 'v']] 

     p  b v 
index     
0  0 a buy 123 
1  6 b buy 789 
2  4 a sell 456 
3  7 b sell 789