Python Pandas - znajdź różnicę między dwiema ramkami danych

104

Mam dwie ramki danych df1 i df2, gdzie df2 jest podzbiorem df1. Jak uzyskać nową ramkę danych (df3), która jest różnicą między dwiema ramkami danych?

Innymi słowy, ramka danych, która ma wszystkie wiersze / kolumny w df1, których nie ma w df2?

wprowadź opis obrazu tutaj

userPyGeo
źródło
3
Najłatwiejszy sposób, aby to zrobić, będzie zależeć od struktury ramek danych (tj. Czy można używać indeksów itp.). To jest dobry przykład, dlaczego w pytaniach o pandy zawsze powinieneś umieszczać powtarzalny przykład .
cmaher
1
Dodałem przykładowy obraz
ramki danych
podobny do stackoverflow.com/q/20225110
SpeedCoder5

Odpowiedzi:

163

Używając drop_duplicates

pd.concat([df1,df2]).drop_duplicates(keep=False)

Update :

Above method only working for those dataframes they do not have duplicate itself, For example

df1=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})
df2=pd.DataFrame({'A':[1],'B':[2]})

Wyświetli się jak poniżej, co jest błędne

Niewłaściwe wyjście:

pd.concat([df1, df2]).drop_duplicates(keep=False)
Out[655]: 
   A  B
1  2  3

Prawidłowe wyjście

Out[656]: 
   A  B
1  2  3
2  3  4
3  3  4

Jak to osiągnąć?

Metoda 1: używanie isinztuple

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]
Out[657]: 
   A  B
1  2  3
2  3  4
3  3  4

Metoda 2: mergezindicator

df1.merge(df2,indicator = True, how='left').loc[lambda x : x['_merge']!='both']
Out[421]: 
   A  B     _merge
1  2  3  left_only
2  3  4  left_only
3  3  4  left_only
BENY
źródło
4
Możesz także określić, które kolumny mają być brane pod uwagę podczas wyszukiwania duplikatów:pd.concat([df1,df2]).drop_duplicates(subset = ['col1','col2'], keep=False)
Szpaqn
1
@Szpaqn zauważ, że ta metoda nie będzie obsługiwać specjalnego przypadku. :-)
BENY
Zwróć uwagę, że może to spowodować, że w wyniku pozostaną nieoczekiwane wiersze, jeśli jeden z typów danych to float(ponieważ 12.00000000001 != 12). Lepszą praktyką jest znalezienie przecięcia zestawu identyfikatorów w dwóch ramkach danych i na tej podstawie obliczenie różnicy.
Jiāgěng
1
@DtechNet musisz sprawić, by dwie ramki danych miały tę samą nazwę
BENY
2
Metoda 2 ( indicator=True) jest bardzo wszechstronnym i użytecznym narzędziem. Chciałbym zobaczyć ją na początku tej odpowiedzi, ale z łączeniem „zewnętrznym”, a nie „lewym”, obejmującym wszystkie 3 sytuacje.
mirekphd
34

W przypadku wierszy spróbuj tego, gdzie Namejest kolumna wspólnego indeksu (może to być lista wielu wspólnych kolumn lub określić left_oni right_on):

m = df1.merge(df2, on='Name', how='outer', suffixes=['', '_'], indicator=True)

To indicator=Trueustawienie jest przydatne, ponieważ dodaje kolumnę o nazwie _merge, zawierającą wszystkie zmiany między df1i df2, podzielone na 3 możliwe rodzaje: „left_only”, „right_only” lub „both”.

W przypadku kolumn spróbuj tego:

set(df1.columns).symmetric_difference(df2.columns)
jpp
źródło
9
Chcesz komentować? mergewith indicator=Trueto klasyczne rozwiązanie do porównywania ramek danych według danych pól.
jpp
9

Zaakceptowana odpowiedź Metoda 1 nie będzie działać dla ramek danych z NaNami wewnątrz, as pd.np.nan != pd.np.nan. Nie jestem pewien, czy to najlepszy sposób, ale można tego uniknąć

df1[~df1.astype(str).apply(tuple, 1).isin(df2.astype(str).apply(tuple, 1))]
toecsnar42
źródło
7

edit2, wymyśliłem nowe rozwiązanie bez potrzeby ustawiania indeksu

newdf=pd.concat([df1,df2]).drop_duplicates(keep=False)

Okay, znalazłem odpowiedź najwyższego głosowania już zawiera to, co odkryłem. Tak, możemy użyć tego kodu tylko pod warunkiem, że nie ma duplikatów w każdym z dwóch plików df.


Mam trudną metodę. Najpierw ustawiliśmy „Name” jako indeks dwóch ramek danych podanych w pytaniu. Ponieważ mamy tę samą „Nazwę” w dwóch plikach df, możemy po prostu usunąć indeks „mniejszego” pliku df z „większego” pliku df. Oto kod.

df1.set_index('Name',inplace=True)
df2.set_index('Name',inplace=True)
newdf=df1.drop(df2.index)
liangli
źródło
1
prawdopodobnie miałeś na myśli pd.concat ([df1, df2]). drop_duplicates (keep = False)
Manaslu
4
import pandas as pd
# given
df1 = pd.DataFrame({'Name':['John','Mike','Smith','Wale','Marry','Tom','Menda','Bolt','Yuswa',],
    'Age':[23,45,12,34,27,44,28,39,40]})
df2 = pd.DataFrame({'Name':['John','Smith','Wale','Tom','Menda','Yuswa',],
    'Age':[23,12,34,44,28,40]})

# find elements in df1 that are not in df2
df_1notin2 = df1[~(df1['Name'].isin(df2['Name']) & df1['Age'].isin(df2['Age']))].reset_index(drop=True)

# output:
print('df1\n', df1)
print('df2\n', df2)
print('df_1notin2\n', df_1notin2)

# df1
#     Age   Name
# 0   23   John
# 1   45   Mike
# 2   12  Smith
# 3   34   Wale
# 4   27  Marry
# 5   44    Tom
# 6   28  Menda
# 7   39   Bolt
# 8   40  Yuswa
# df2
#     Age   Name
# 0   23   John
# 1   12  Smith
# 2   34   Wale
# 3   44    Tom
# 4   28  Menda
# 5   40  Yuswa
# df_1notin2
#     Age   Name
# 0   45   Mike
# 1   27  Marry
# 2   39   Bolt
SpeedCoder5
źródło
Co oznacza „~”?
Piotrek Leśniak
„~” nie służy do indeksowania wartości logicznych. Zobacz: pandas.pydata.org/pandas-docs/stable/user_guide/…
SpeedCoder5
3

Być może prostszy jednolinijkowy, z identycznymi lub różnymi nazwami kolumn. Działa nawet wtedy, gdy df2 ['Name2'] zawiera zduplikowane wartości.

newDf = df1.set_index('Name1')
           .drop(df2['Name2'], errors='ignore')
           .reset_index(drop=False)
Cherif Diallo
źródło
2
proste i skuteczne. Dodano błędy = „ignoruj”, aby rozwiązać problem w przypadku, gdy wartości docelowe nie znajdują się w źródle (tj. Na przecięciu), a zresetowanie indeksu na końcu daje plik df podobny do oryginału.
MrE
1

Jak wspomniano tutaj, że

df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]

jest poprawnym rozwiązaniem, ale jeśli

df1=pd.DataFrame({'A':[1],'B':[2]})
df2=pd.DataFrame({'A':[1,2,3,3],'B':[2,3,4,4]})

W takim przypadku powyższe rozwiązanie da Empty DataFrame , zamiast tego powinieneś użyć concatmetody po usunięciu duplikatów z każdej ramki danych.

Posługiwać się concate with drop_duplicates

df1=df1.drop_duplicates(keep="first") 
df2=df2.drop_duplicates(keep="first") 
pd.concat([df1,df2]).drop_duplicates(keep=False)
arun pal
źródło
Autor pytania poprosił o zwrócenie wszystkich wartości w df1, których nie ma w df2. Dlatego df1[~df1.apply(tuple,1).isin(df2.apply(tuple,1))]nawet w tym przypadku jest to poprawna odpowiedź. Jeśli chciałeś uzyskać wartości, które są w df1 lub df2, ale nie w obu, to sugerowane podejście jest poprawne (z zastrzeżeniem usuwania duplikatów z oryginalnych ramek danych).
ira
0

Niewielka odmiana rozwiązania nice @ liangli, które nie wymaga zmiany indeksu istniejących ramek danych:

newdf = df1.drop(df1.join(df2.set_index('Name').index))
Serge Ballesta
źródło
0

Znajdowanie różnicy według indeksu. Zakładając, że df1 jest podzbiorem df2, a indeksy są przenoszone do przodu podczas podzbioru

df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

# Example

df1 = pd.DataFrame({"gender":np.random.choice(['m','f'],size=5), "subject":np.random.choice(["bio","phy","chem"],size=5)}, index = [1,2,3,4,5])

df2 =  df1.loc[[1,3,5]]

df1

 gender subject
1      f     bio
2      m    chem
3      f     phy
4      m     bio
5      f     bio

df2

  gender subject
1      f     bio
3      f     phy
5      f     bio

df3 = df1.loc[set(df1.index).symmetric_difference(set(df2.index))].dropna()

df3

  gender subject
2      m    chem
4      m     bio

DOS
źródło
0

Oprócz zaakceptowanej odpowiedzi chciałbym zaproponować jeszcze jedno szersze rozwiązanie, w którym można znaleźć różnicę w zestawie 2D dwóch ramek danych z dowolnym index/ columns(mogą nie pokrywać się dla obu nazw danych). Metoda pozwala również na ustawienie tolerancji dla floatelementów do porównania ramek danych (wykorzystuje np.isclose)


import numpy as np
import pandas as pd

def get_dataframe_setdiff2d(df_new: pd.DataFrame, 
                            df_old: pd.DataFrame, 
                            rtol=1e-03, atol=1e-05) -> pd.DataFrame:
    """Returns set difference of two pandas DataFrames"""

    union_index = np.union1d(df_new.index, df_old.index)
    union_columns = np.union1d(df_new.columns, df_old.columns)

    new = df_new.reindex(index=union_index, columns=union_columns)
    old = df_old.reindex(index=union_index, columns=union_columns)

    mask_diff = ~np.isclose(new, old, rtol, atol)

    df_bool = pd.DataFrame(mask_diff, union_index, union_columns)

    df_diff = pd.concat([new[df_bool].stack(),
                         old[df_bool].stack()], axis=1)

    df_diff.columns = ["New", "Old"]

    return df_diff

Przykład:

In [1]

df1 = pd.DataFrame({'A':[2,1,2],'C':[2,1,2]})
df2 = pd.DataFrame({'A':[1,1],'B':[1,1]})

print("df1:\n", df1, "\n")

print("df2:\n", df2, "\n")

diff = get_dataframe_setdiff2d(df1, df2)

print("diff:\n", diff, "\n")
Out [1]

df1:
   A  C
0  2  2
1  1  1
2  2  2 

df2:
   A  B
0  1  1
1  1  1 

diff:
     New  Old
0 A  2.0  1.0
  B  NaN  1.0
  C  2.0  NaN
1 B  NaN  1.0
  C  1.0  NaN
2 A  2.0  NaN
  C  2.0  NaN 
Luchko
źródło