2016-06-09 23 views
5

Mam prosty dataframe tak:Jak scalić dwie ramki danych z "symbolami wieloznacznymi"?

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

i tabeli odnośników tak:

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

Jak mogę połączyć (join) dwóch dataframes, ale z poszanowaniem 'dziką kartę' w kolumnie b, czyli oczekiwanego wyniku jest:

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

najlepszym mogę wymyślić to jest, ale jest to dość brzydki i gadatliwy:

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

Czy istnieje mądrzejszy sposób?

+0

W oczekiwanym wyniku powyżej, dlaczego nie ma żadnych wierszy z 'v' równym 888? – unutbu

+0

Dobre pytanie - to dlatego, że symbol wieloznaczny obowiązuje tylko wtedy, gdy nie ma dokładniejszego dopasowania. – Matthew

+1

@Matthew, jeśli jest to coś, co stworzyłeś, musisz pomyśleć o modelu danych. – Merlin

Odpowiedz

5

myślę trochę czystsze jest oczyścić wildcards pierwszy:

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

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

Teraz można połączyć dwa scala (bez konieczności set_index) z 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

Podoba mi się to rozwiązanie! Jedyną czkawką jest sytuacja, w której istnieje wiele kolumn z (potencjalnymi) znakami wieloznacznymi. Nie jest oczywiste, jak mogłem to rozszerzyć, aby działało w tym scenariuszu. – Matthew

+0

@Matthew, niestety, 'b kupić 888.0' powinien być 'b buy 789', ale 3 upvotes, :-) – Merlin

+0

@Matthew pozwól mi to naprawić! –

1

Uważam to intuicyjny:

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 lubię dokąd zmierzasz. Myślę, że groupby -> aplikacja może być czystsza - pozwól mi o tym pomyśleć ... – Matthew

1

Znalazłem anothe r rozwiązanie inspirowane niektórymi z powyższych pomysłów (dzięki wszystkim!). Jest to starsza niż moja pierwsza próba, więc umieszczę ją tutaj, choć jestem pewien, że jest miejsce na ulepszenia. To rozwiązanie zakłada, że ​​wyszukiwanie jest sortowane tak, że symbole wieloznaczne znajdują się u dołu tabeli:

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 
Powiązane problemy