Znajdź szybko pary symetryczne w numpy

15
from itertools import product
import pandas as pd

df = pd.DataFrame.from_records(product(range(10), range(10)))
df = df.sample(90)
df.columns = "c1 c2".split()
df = df.sort_values(df.columns.tolist()).reset_index(drop=True)
#     c1  c2
# 0    0   0
# 1    0   1
# 2    0   2
# 3    0   3
# 4    0   4
# ..  ..  ..
# 85   9   4
# 86   9   5
# 87   9   7
# 88   9   8
# 89   9   9
# 
# [90 rows x 2 columns]

Jak szybko znaleźć, zidentyfikować i usunąć ostatni duplikat wszystkich par symetrycznych w tej ramce danych?

Przykładem pary symetrycznej jest to, że „(0, 1)” jest równe „(1, 0)”. Ten ostatni należy usunąć.

Algorytm musi być szybki, dlatego zaleca się użycie numpy. Konwersja na obiekt python jest niedozwolona.

The Unfun Cat
źródło
1
Czy możesz podać przykład tego, co rozumiesz symmetric pairs?
yatu
(0, 1) == (1,0) jest prawdą
The Unfun Cat
1
Czy (0, 1) == (0, 1) jest również Prawdą?
wundermahn
@JerryM. Tak, ale usunięcie go za pomocądf.drop_duplicates()
The Unfun Cat
2
@ molybdenum42 Używam produktu itertools do stworzenia przykładu, same dane nie są tworzone za pomocą produktu itertools.
The Unfun Cat

Odpowiedzi:

13

Możesz posortować wartości, a następnie groupby:

a= np.sort(df.to_numpy(), axis=1)
df.groupby([a[:,0], a[:,1]], as_index=False, sort=False).first()

Opcja 2 : jeśli masz wiele par c1, c2, groupbymoże być powolny. W takim przypadku możemy przypisać nowe wartości i filtrować według drop_duplicates:

a= np.sort(df.to_numpy(), axis=1) 

(df.assign(one=a[:,0], two=a[:,1])   # one and two can be changed
   .drop_duplicates(['one','two'])   # taken from above
   .reindex(df.columns, axis=1)
)
Quang Hoang
źródło
7

Jednym ze sposobów jest użycie np.uniquez return_index=Truei wykorzystać wynik do indeksowania dataframe:

a = np.sort(df.values)
_, ix = np.unique(a, return_index=True, axis=0)

print(df.iloc[ix, :])

    c1  c2
0    0   0
1    0   1
20   2   0
3    0   3
40   4   0
50   5   0
6    0   6
70   7   0
8    0   8
9    0   9
11   1   1
21   2   1
13   1   3
41   4   1
51   5   1
16   1   6
71   7   1
...
yatu
źródło
1
Tak, inaczej unikalny nie wykryje par symetrycznych @DanielMesejo
yatu
Ok, rozumiem, więc sortujesz pary
Dani Mesejo
Tak, ale mam na myśli, że przekształcasz [1, 0] w [0, 1] prawda?
Dani Mesejo
6

frozenset

mask = pd.Series(map(frozenset, zip(df.c1, df.c2))).duplicated()

df[~mask]
piRSquared
źródło
1
Czy nie iterujesz powoli po krotkach nad każdą kolumną tutaj? Mimo to głosuj pozytywnie.
The Unfun Cat
Tak, iteruję. Nie, to nie jest tak wolne, jak myślisz.
piRSquared
5

zrobię

df[~pd.DataFrame(np.sort(df.values,1)).duplicated().values]

Od pand i numpy tri

s=pd.crosstab(df.c1,df.c2)
s=s.mask(np.triu(np.ones(s.shape)).astype(np.bool) & s==0).stack().reset_index()
YOBEN_S
źródło
5

Oto jeden oparty na NumPy dla liczb całkowitych -

def remove_symm_pairs(df):
    a = df.to_numpy(copy=False)
    b = np.sort(a,axis=1)
    idx = np.ravel_multi_index(b.T,(b.max(0)+1))
    sidx = idx.argsort(kind='mergesort')
    p = idx[sidx]
    m = np.r_[True,p[:-1]!=p[1:]]
    a_out = a[np.sort(sidx[m])]
    df_out = pd.DataFrame(a_out)
    return df_out

Jeśli chcesz zachować dane indeksu bez zmian, użyj return df.iloc[np.sort(sidx[m])].

W przypadku liczb ogólnych (ints / floats itp.) Użyjemy view-basedjednego -

# https://stackoverflow.com/a/44999009/ @Divakar
def view1D(a): # a is array
    a = np.ascontiguousarray(a)
    void_dt = np.dtype((np.void, a.dtype.itemsize * a.shape[1]))
    return a.view(void_dt).ravel()

i po prostu zastąpić krok, aby dostać idxsię idx = view1D(b)w remove_symm_pairs.

Divakar
źródło
1

Jeśli to musi być szybkie , a twoje zmienne są liczbami całkowitymi, może pomóc następująca sztuczka: niech v,wbędą kolumny wektora; konstruować [v+w, np.abs(v-w)] =: [x, y]; następnie posortuj tę matrycę leksykograficznie, usuń duplikaty, a na końcu zamapuj ją z powrotem [v, w] = [(x+y), (x-y)]/2.

Federico Poloni
źródło