Użyj danych w ramkach danych pand, aby dopasować kolumny razem

18

Mam dwie pandasramki danych ai b:

a1   a2   a3   a4   a5   a6   a7
1    3    4    5    3    4    5
0    2    0    3    0    2    1
2    5    6    5    2    1    2

i

b1   b2   b3   b4   b5   b6   b7
3    5    4    5    1    4    3
0    1    2    3    0    0    2
2    2    1    5    2    6    5

Dwie ramki danych zawierają dokładnie te same dane, ale w innej kolejności i przy różnych nazwach kolumn. Na podstawie liczb w dwóch ramkach danych chciałbym móc dopasować nazwę akażdej kolumny do nazwy każdej kolumny w b.

Nie jest to tak proste, jak proste porównanie pierwszego wiersza az pierwszym wierszem, bponieważ istnieją zduplikowane wartości, na przykład oba a4i a7mają tę wartość, 5więc nie jest możliwe natychmiastowe dopasowanie ich do jednego b2lub drugiego b4.

Jak najlepiej to zrobić?

OD1995
źródło

Odpowiedzi:

16

Oto sposób użycia sort_values:

m=df1.T.sort_values(by=[*df1.index]).index
n=df2.T.sort_values(by=[*df2.index]).index
d=dict(zip(m,n))
print(d)

{'a1': 'b5', 'a5': 'b1', 'a2': 'b7', 'a3': 'b6', 'a6': 'b3', 'a7': 'b2', 'a4': 'b4'}
anky
źródło
Dziękujemy za udostępnienie miłego polecenia Anky, czy mógłbyś proszę wyjaśnić więcej po [*df1.index]części, proszę? Będę ci wdzięczny, na zdrowie.
RavinderSingh13
1
@ RavinderSingh13 Jasne, sort_values(by=..)bierze listę jako parametr, więc rozpakowuję tutaj indeks do listy, możesz też zrobić list(df1.index)zamiast [*df1.index]:)
anky
16

Oto jeden ze sposobów wykorzystania numpy broadcasting:

b_cols = b.columns[(a.values == b.T.values[...,None]).all(1).argmax(1)]
dict(zip(a, b_cols))

{'a1': 'b5',
 'a2': 'b7',
 'a3': 'b6',
 'a4': 'b4',
 'a5': 'b1',
 'a6': 'b3',
 'a7': 'b2'}

Inne podobne podejście (autorstwa @piR):

a_ = a.to_numpy()
b_ = b.to_numpy()
i, j = np.where((a_[:, None, :] == b_[:, :, None]).all(axis=0))
dict(zip(a.columns[j], b.columns[i]))

{'a1': 'b5',
 'a2': 'b7',
 'a3': 'b6',
 'a4': 'b4',
 'a5': 'b1',
 'a6': 'b3',
 'a7': 'b2'}
yatu
źródło
1
Wsadziłem nos w twój post. Mam nadzieję, że nie masz nic przeciwko. Zmień to według własnych upodobań.
piRSquared
Ach wręcz przeciwnie :) Ładne podejście, a sprawdzanie dużych ramek danych nieznacznie poprawia wydajność @piRSquared
yatu
12

Jednym ze sposobów merge

s=df1.T.reset_index().merge(df2.T.assign(match=lambda x : x.index))
dict(zip(s['index'],s['match']))
{'a1': 'b5', 'a2': 'b7', 'a3': 'b6', 'a4': 'b4', 'a5': 'b1', 'a6': 'b3', 'a7': 'b2'}
YOBEN_S
źródło
Pomyślałem, że dodam inne sprytne rozwiązanie, aby zobaczyć, że jest takie samo jak twoje (-:
ups
8

słownictwo

Użyj a tuplez wartości kolumny jako klucza skrótu w słowniku

d = {(*t,): c for c, t in df2.items()}
{c: d[(*t,)] for c, t in df1.items()}

{'a1': 'b5',
 'a2': 'b7',
 'a3': 'b6',
 'a4': 'b4',
 'a5': 'b1',
 'a6': 'b3',
 'a7': 'b2'}

Na wypadek, gdybyśmy nie mieli idealnej reprezentacji, stworzyłem słownik tylko dla kolumn, w których istnieje dopasowanie.

d2 = {(*t,): c for c, t in df2.items()}
d1 = {(*t,): c for c, t in df1.items()}

{d1[c]: d2[c] for c in {*d1} & {*d2}}

{'a5': 'b1',
 'a2': 'b7',
 'a7': 'b2',
 'a6': 'b3',
 'a3': 'b6',
 'a1': 'b5',
 'a4': 'b4'}

idxmax

To graniczy z absurdem ... Nie rób tego.

{c: df2.T.eq(df1[c]).sum(1).idxmax() for c in df1}

{'a1': 'b5',
 'a2': 'b7',
 'a3': 'b6',
 'a4': 'b4',
 'a5': 'b1',
 'a6': 'b3',
 'a7': 'b2'}
piRSquared
źródło
1
Jak to możliwe, że rozumiem każde wyrażenie w tych stwierdzeniach, ale nie do końca rozumiem, co się tutaj naprawdę dzieje? Trochę jak w szachy, wiem, jak przenieść cały pionek na planszy, ale nie widzę więcej niż 2 ruchy do przodu.
Scott Boston
Okej ... Przetrawiłem to teraz i jest to absolutnie po prostu genialne. +1
Scott Boston