Pandy otrzymują wiersze, które NIE znajdują się w innych ramkach danych

229

Mam dwie ramki danych pand, które mają wspólne wiersze.

Załóżmy, że ramka danych2 jest podzbiorem ramki danych1.

Jak mogę uzyskać wiersze ramki danych 1, których nie ma w ramce danych 2?

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 'col2' : [10, 11, 12]})
myśleć miłe rzeczy
źródło
1
@TedPetrou Nie widzę, w jaki sposób udzielona odpowiedź jest poprawna. Jeśli mam dwie ramki danych, z których jedna jest podzbiorem drugiej, muszę usunąć wszystkie wiersze znajdujące się w tym podzbiorze. Nie chcę usuwać duplikatów. Całkowicie chcę usunąć podzbiór.
szafa grająca

Odpowiedzi:

172

Jedną z metod byłoby przechowywanie wyniku scalenia wewnętrznego z obu plików dfs, a następnie możemy po prostu wybrać wiersze, gdy wartości jednej kolumny nie są wspólne:

In [119]:

common = df1.merge(df2,on=['col1','col2'])
print(common)
df1[(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))]
   col1  col2
0     1    10
1     2    11
2     3    12
Out[119]:
   col1  col2
3     4    13
4     5    14

EDYTOWAĆ

Inną metodą, którą znalazłeś, jest użycie, isinktóre utworzy NaNwiersze, które możesz upuścić:

In [138]:

df1[~df1.isin(df2)].dropna()
Out[138]:
   col1  col2
3     4    13
4     5    14

Jednak jeśli df2 nie uruchamia wierszy w ten sam sposób, to nie zadziała:

df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11, 12,13]})

wyprodukuje cały df:

In [140]:

df1[~df1.isin(df2)].dropna()
Out[140]:
   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
EdChum
źródło
13
df1[~df1.isin(df2)].dropna(how = 'all')wydaje się załatwić sprawę. W każdym razie dzięki - twoja odpowiedź pomogła mi znaleźć rozwiązanie.
pomyśl ładne rzeczy
5
Zauważ, że użycie isinwymaga, aby oba dfs zaczynały się od tych samych wartości wierszy, więc na przykład, jeśli df2 był, df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11,12, 13]})wtedy twoja metoda nie zadziała
EdChum
2
przekształciło to wszystkie ints w float!
Chris Nielsen
3
@SergeyZakharov ta odpowiedź wysłana prawie 3 lata temu była poprawna w odniesieniu do PO, a dla ich problemu druga odpowiedź jest lepszą odpowiedzią i obsługuje szerszy problem, który nigdy nie był częścią pierwotnego pytania, błędne jest stwierdzenie, że to odpowiedź jest błędna, jest poprawna, biorąc pod uwagę opisany problem. Dodatkowo ktoś głosował za tym bez wyjaśnienia, niewiele mogę zrobić, ponieważ jest to akceptowana odpowiedź, OP nie zmienił zdania i nie zamierzam kanibalizować innej odpowiedzi, aby to zrobić poprawnie .
EdChum
1
@Cecilia musisz przekazać keep=False: df0.append(df1).drop_duplicates(keep=False)domyślnie zachowuje pierwszy duplikat, chcesz usunąć wszystkie duplikaty
EdChum
189

Aktualnie wybrane rozwiązanie daje nieprawidłowe wyniki. Aby poprawnie rozwiązać ten problem, możemy wykonać lewe połączenie od df1do df2, upewniając się, że najpierw otrzymamy tylko unikalne wiersze df2.

Najpierw musimy zmodyfikować oryginalny DataFrame, aby dodać wiersz z danymi [3, 10].

df1 = pd.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 
                           'col2' : [10, 11, 12, 13, 14, 10]}) 
df2 = pd.DataFrame(data = {'col1' : [1, 2, 3],
                           'col2' : [10, 11, 12]})

df1

   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14
5     3    10

df2

   col1  col2
0     1    10
1     2    11
2     3    12

Wykonaj lewe łączenie, eliminując duplikaty df2, aby każdy rząd df1złączeń miał dokładnie 1 wiersz df2. Użyj tego parametru, indicatoraby zwrócić dodatkową kolumnę wskazującą, z której tabeli pochodzi wiersz.

df_all = df1.merge(df2.drop_duplicates(), on=['col1','col2'], 
                   how='left', indicator=True)
df_all

   col1  col2     _merge
0     1    10       both
1     2    11       both
2     3    12       both
3     4    13  left_only
4     5    14  left_only
5     3    10  left_only

Utwórz warunek logiczny:

df_all['_merge'] == 'left_only'

0    False
1    False
2    False
3     True
4     True
5     True
Name: _merge, dtype: bool

Dlaczego inne rozwiązania są złe

Kilka rozwiązań popełnia ten sam błąd - sprawdzają tylko, czy każda wartość jest niezależnie w każdej kolumnie, a nie razem w tym samym wierszu. Dodanie ostatniego wiersza, który jest unikalny, ale zawiera wartości z obu kolumn, df2ujawnia błąd:

common = df1.merge(df2,on=['col1','col2'])
(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))
0    False
1    False
2    False
3     True
4     True
5    False
dtype: bool

To rozwiązanie otrzymuje ten sam zły wynik:

df1.isin(df2.to_dict('l')).all(1)
Ted Petrou
źródło
2
ale przypuszczam, że zakładali, że col1 jest unikalny, ponieważ jest indeksem (nie wspomnianym w pytaniu, ale oczywistym). Tak więc, jeśli nigdy nie ma takiego przypadku, w którym istnieją dwie wartości col2 dla tej samej wartości col1 (nie może być dwóch col1 = 3 wierszy), powyższe odpowiedzi są poprawne.
pashute
14
To z pewnością nie jest oczywiste, więc twój punkt jest nieważny. Moje rozwiązanie uogólnia na więcej przypadków.
Ted Petrou,
Pytanie, czy nie byłoby łatwiej stworzyć plasterek niż tablicę boolowską? Ponieważ celem jest zdobycie rzędów.
Matías Romo
5
Użyj, df_all[df_all['_merge'] == 'left_only']aby mieć df z wynikami
gies0r
77

Zakładając, że indeksy są spójne w ramkach danych (nie biorąc pod uwagę rzeczywistych wartości col):

df1[~df1.index.isin(df2.index)]
Dennis Golomazov
źródło
1
@ChrisNielsen negacja warunku. W tym przykładzie oznacza to więc „weź wiersze, z df1których NIE ma indeksów df2.index”. Więcej na temat negacji: stackoverflow.com/q/19960077/304209 (zaskakujące, nie znalazłem żadnych wzmianek o tyldach w dokumentach pandas).
Dennis Golomazov
Wydaje się, że dfs muszą być tej samej długości, nie? DostajęValueError: Item wrong length x instead of y.
wordsforthewise
@ słowa, w przeciwnym razie nie. Maska ma długość df1 i jest stosowana również do df1. Czy możesz podać swój przykład?
Dennis Golomazov
Aby rozwiązać problem z długością przedmiotu, należy dodać .loc
Moreno
13

Jak już wspomniano, isin wymaga, aby kolumny i indeksy były takie same dla dopasowania. Jeśli dopasowanie powinno dotyczyć tylko zawartości wierszy, jednym ze sposobów uzyskania maski do filtrowania obecnych wierszy jest przekonwertowanie wierszy na (Multi) Indeks:

In [77]: df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 'col2' : [10, 11, 12, 13, 14, 10]})
In [78]: df2 = pandas.DataFrame(data = {'col1' : [1, 3, 4], 'col2' : [10, 12, 13]})
In [79]: df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)]
Out[79]:
   col1  col2
1     2    11
4     5    14
5     3    10

Jeśli indeks ma być brany pod uwagę, set_index ma argument słowa kluczowego, który dołącza kolumny do istniejącego indeksu. Jeśli kolumny nie są wyrównane, listę (df.columns) można zastąpić specyfikacjami kolumn, aby wyrównać dane.

pandas.MultiIndex.from_tuples(df<N>.to_records(index = False).tolist())

można alternatywnie wykorzystać do stworzenia indeksów, choć wątpię, aby było to bardziej wydajne.

Rune Lyngsoe
źródło
@ Dev_123 Usuń ~ na początku. Rdzeniem jest utworzenie listy predykatów, czy wiersze w df1 również występują w df2, więc wiersze w df1 nie są unikalne dla df1, ~ neguje to do predykatowej listy, czy wiersze w df1 nie występują w df2.
Rune Lyngsoe
11

Załóżmy, że masz dwie ramki danych: df_1 i df_2 z wieloma polami (nazwy kolumn) i chcesz znaleźć tylko te wpisy w df_1, których nie ma w df_2 na podstawie niektórych pól (np. Field_x, fields_y), wykonaj następujące kroki.

Krok 1. Dodaj kolumnę klucz1 i klucz2 odpowiednio do df_1 i df_2.

Krok 2. Połącz ramki danych, jak pokazano poniżej. field_x i field_y są naszymi pożądanymi kolumnami.

Krok 3. Wybierz tylko te wiersze z df_1, w których klucz1 nie jest równy kluczowi2.

Step4.Drop key1 i key2.

Ta metoda rozwiąże Twój problem i działa szybko nawet w przypadku dużych zbiorów danych. Wypróbowałem to dla ramek danych z ponad 1 000 000 wierszy.

df_1['key1'] = 1
df_2['key2'] = 1
df_1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'left')
df_1 = df_1[~(df_1.key2 == df_1.key1)]
df_1 = df_1.drop(['key1','key2'], axis=1)
Pragalbh kulshrestha
źródło
Nie sądzę, żeby technicznie tego chciał - chce wiedzieć, które rzędy były unikalne dla którego df. ale myślę, że to rozwiązanie zwraca df wierszy, które były albo unikalne dla pierwszego df lub drugiego df.
Legit Stack
3

możesz to zrobić za pomocą metody isin (dict) :

In [74]: df1[~df1.isin(df2.to_dict('l')).all(1)]
Out[74]:
   col1  col2
3     4    13
4     5    14

Wyjaśnienie:

In [75]: df2.to_dict('l')
Out[75]: {'col1': [1, 2, 3], 'col2': [10, 11, 12]}

In [76]: df1.isin(df2.to_dict('l'))
Out[76]:
    col1   col2
0   True   True
1   True   True
2   True   True
3  False  False
4  False  False

In [77]: df1.isin(df2.to_dict('l')).all(1)
Out[77]:
0     True
1     True
2     True
3    False
4    False
dtype: bool
MaxU
źródło
Daje to zły wynik. Zobacz moje wyjaśnienie poniżej.
Ted Petrou
2

Można również Concat df1, df2:

x = pd.concat([df1, df2])

a następnie usuń wszystkie duplikaty:

y = x.drop_duplicates(keep=False, inplace=False)
Semeon Balagula
źródło
Witamy w StackOverflow: jeśli publikujesz kod, XML lub próbki danych, zaznacz te wiersze w edytorze tekstu i kliknij przycisk „próbki kodu” ({}) na pasku narzędzi edytora lub użyj Ctrl + K na klawiaturze, aby ładnie sformatować i składnia to podkreślają!
WhatsThePoint,
4
Zwróci to wszystkie dane, które są w jednym z zestawów, a nie tylko dane, które są tylko w df1.
Jamie Marshall
1

Co powiesz na to:

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 
                               'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 
                               'col2' : [10, 11, 12]})
records_df2 = set([tuple(row) for row in df2.values])
in_df2_mask = np.array([tuple(row) in records_df2 for row in df1.values])
result = df1[~in_df2_mask]
adamwlev
źródło
1

Oto inny sposób rozwiązania tego:

df1[~df1.index.isin(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]

Lub:

df1.loc[df1.index.difference(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]
Siergiej Zacharow
źródło
0

Mój sposób na zrobienie tego polega na dodaniu nowej kolumny, która jest unikalna dla jednej ramki danych i za pomocą tej opcji mogę wybrać, czy zachować wpis

df2[col3] = 1
df1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'outer')
df1['Empt'].fillna(0, inplace=True)

To sprawia, że ​​każdy wpis w df1 ma kod - 0, jeśli jest unikalny dla df1, 1, jeśli jest w obu ramkach danych. Następnie użyj tego, aby ograniczyć się do tego, co chcesz

answer = nonuni[nonuni['Empt'] == 0]
r.rz
źródło
0
wyodrębnij niepodobne wiersze za pomocą funkcji scalania
df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
               how='left', indicator=True)
zapisz różne wiersze w CSV
df[df['_merge'] == 'left_only'].to_csv('output.csv')
Gajanan Kothawade
źródło