Porównanie dwóch ramek danych i uzyskanie różnic

89

Mam dwie ramki danych. Przykłady:

df1:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green

df2:
Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange

Każda ramka danych ma datę jako indeks. Obie ramki danych mają taką samą strukturę.

Co chcę zrobić, to porównać te dwie ramki danych i znaleźć wiersze w df2, które nie są w df1. Chcę porównać datę (indeks) i pierwszą kolumnę (banan, jabłko itp.), Aby sprawdzić, czy istnieją w df2 vs df1.

Próbowałem następujących rzeczy:

W przypadku pierwszego podejścia pojawia się ten błąd: „Wyjątek: można porównać tylko obiekty DataFrame o identycznych etykietach” . Próbowałem usunąć datę jako indeks, ale pojawia się ten sam błąd.

Przy trzecim podejściu asercja zwraca False, ale nie mogę dowiedzieć się, jak faktycznie zobaczyć różne wiersze.

Wszelkie wskazówki byłyby mile widziane

Eric D. Brown
źródło
Jeśli to zrobisz: cookbook-r.com/Manipulating_data/… , czy pozbędzie się wyjątku „obiekty DataFrame o identycznych etykietach”?
Anthony Kong
Wiele razy zmieniałem nazwy kolumn, aby bez powodzenia spróbować obejść ten problem.
Eric D. Brown
1
FWIW, zmieniłem nazwy kolumn na „a, b, c, d” w obu ramkach danych i otrzymałem ten sam komunikat o błędzie.
Eric D. Brown

Odpowiedzi:

103

To podejście df1 != df2działa tylko w przypadku ramek danych z identycznymi wierszami i kolumnami. W rzeczywistości wszystkie osie ramek danych są porównywane z _indexed_samemetodą, a wyjątek jest zgłaszany w przypadku znalezienia różnic, nawet w kolejności kolumn / indeksów.

Jeśli dobrze zrozumiałem, nie chcesz znaleźć zmian, ale symetryczną różnicę. W tym celu jednym podejściem może być konkatenacja ramek danych:

>>> df = pd.concat([df1, df2])
>>> df = df.reset_index(drop=True)

Grupuj według

>>> df_gpby = df.groupby(list(df.columns))

uzyskać indeks unikalnych rekordów

>>> idx = [x[0] for x in df_gpby.groups.values() if len(x) == 1]

filtr

>>> df.reindex(idx)
         Date   Fruit   Num   Color
9  2013-11-25  Orange   8.6  Orange
8  2013-11-25   Apple  22.1     Red
alko
źródło
To była odpowiedź. Usunąłem indeks „Data” i zastosowałem to podejście i otrzymałem właściwe wyniki.
Eric D. Brown
8
Czy istnieje łatwy sposób na dodanie do tego flagi, aby zobaczyć, które wiersze zostały usunięte / dodane / zmienione z df1 na df2?
pyCthon
@alko Zastanawiałem się, czy to pd.concatdodaje tylko brakujące elementy z df1? A może df1całkowicie zastępuje df2?
jake wong
@jakewong pd.concat- tak jak tutaj użyto - wykonuje sprzężenie zewnętrzne. Innymi słowy, łączy wszystkie indeksy z obu df i jest to w rzeczywistości domyślne zachowanie dla pd.concat(), oto dokumentacja pandas.pydata.org/pandas-docs/stable/merging.html
Thanos
jaka jest maksymalna liczba rekordów, które możemy porównać za pomocą pand?
pyd
25

Przekazanie ramek danych do konkatacji w słowniku daje w wyniku ramkę danych z wieloma indeksami, z której można łatwo usunąć duplikaty, co skutkuje ramką danych z wieloma indeksami z różnicami między ramkami danych:

import sys
if sys.version_info[0] < 3:
    from StringIO import StringIO
else:
    from io import StringIO
import pandas as pd

DF1 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
""")
DF2 = StringIO("""Date       Fruit  Num  Color 
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange  8.6 Orange
2013-11-24 Apple   7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple  22.1 Red
2013-11-25 Orange  8.6 Orange""")


df1 = pd.read_table(DF1, sep='\s+')
df2 = pd.read_table(DF2, sep='\s+')
#%%
dfs_dictionary = {'DF1':df1,'DF2':df2}
df=pd.concat(dfs_dictionary)
df.drop_duplicates(keep=False)

Wynik:

             Date   Fruit   Num   Color
DF2 4  2013-11-25   Apple  22.1     Red
    5  2013-11-25  Orange   8.6  Orange
jur
źródło
1
Jest to znacznie łatwiejsza metoda, wystarczy jeszcze jedna poprawka, która może ją ułatwić. Nie ma potrzeby concat w słowniku, stosowanie df = pd.concat ([DF1, DF2]), by zrobić to samo
ling
nie powinieneś nadpisywać wbudowanego słowa kluczowego dict!
denfromufa
Czy istnieje sposób, aby to dodać, aby określić, która ramka danych zawiera unikalny wiersz?
jlewkovich
Możesz to poznać po pierwszym poziomie w multiindeksie, który zawiera klucz ramki danych w słowniku (zaktualizowałem dane wyjściowe odpowiednimi kluczami)
jur
24

Aktualizowania i wprowadzania, gdzie łatwiej będzie znaleźć dla innych, ling 's komentować jur jest odpowiedź powyżej.

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

Testowanie z tymi ramkami danych:

df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})

df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})

Skutkuje to: wprowadź opis obrazu tutaj

leerssej
źródło
5

Opierając się na odpowiedzi alko, która prawie zadziałała dla mnie, z wyjątkiem etapu filtrowania (gdzie otrzymuję:) ValueError: cannot reindex from a duplicate axis, oto ostateczne rozwiązanie, którego użyłem:

# join the dataframes
united_data = pd.concat([data1, data2, data3, ...])
# group the data by the whole row to find duplicates
united_data_grouped = united_data.groupby(list(united_data.columns))
# detect the row indices of unique rows
uniq_data_idx = [x[0] for x in united_data_grouped.indices.values() if len(x) == 1]
# extract those unique values
uniq_data = united_data.iloc[uniq_data_idx]
fnl
źródło
Niezły dodatek do odpowiedzi. Dzięki
Eric D. Brown
1
Otrzymuję błąd ” IndexError: index out of bounds', kiedy próbuję uruchomić trzecią linię.
Moondra
5
# THIS WORK FOR ME

# Get all diferent values
df3 = pd.merge(df1, df2, how='outer', indicator='Exist')
df3 = df3.loc[df3['Exist'] != 'both']


# If you like to filter by a common ID
df3  = pd.merge(df1, df2, on="Fruit", how='outer', indicator='Exist')
df3  = df3.loc[df3['Exist'] != 'both']
Ivan Moran
źródło
to najlepsza odpowiedź
moshevi
3

Istnieje prostsze rozwiązanie, które jest szybsze i lepsze, a jeśli liczby są różne, mogą nawet podać różnice ilościowe:

df1_i = df1.set_index(['Date','Fruit','Color'])
df2_i = df2.set_index(['Date','Fruit','Color'])
df_diff = df1_i.join(df2_i,how='outer',rsuffix='_').fillna(0)
df_diff = (df_diff['Num'] - df_diff['Num_'])

Tutaj df_diff jest streszczeniem różnic. Możesz go nawet użyć do znalezienia różnic w ilościach. W twoim przykładzie:

wprowadź opis obrazu tutaj

Wyjaśnienie: Podobnie jak w przypadku porównywania dwóch list, aby zrobić to efektywnie, najpierw należy je uporządkować, a następnie porównać (konwersja listy na zestawy / haszowanie również byłaby szybka; obie są niesamowitym ulepszeniem prostej pętli podwójnych porównań O (N ^ 2)

Uwaga: poniższy kod tworzy tabele:

df1=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green'],
})
df2=pd.DataFrame({
    'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,10.2,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange'],
})
ntg
źródło
3

Założyciel proste rozwiązanie tutaj:

https://stackoverflow.com/a/47132808/9656339

pd.concat([df1, df2]).loc[df1.index.symmetric_difference(df2.index)]

Tom2shoes
źródło
1
Witamy w Stack Overflow Tom2shoes. Nie podawaj odpowiedzi zawierających tylko łącze, spróbuj wyodrębnić treść z linku i pozostaw ją tylko jako odniesienie (ponieważ treść w linku może zostać usunięta lub sam link może się zepsuć). Aby uzyskać więcej informacji, zobacz „Jak napisać dobrą odpowiedź?” . Jeśli uważasz, że na to pytanie udzielono już odpowiedzi w innym pytaniu, oznacz je jako duplikat.
GGG
2
# given
df1=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24'],
    'Fruit':['Banana','Orange','Apple','Celery'],
    'Num':[22.1,8.6,7.6,10.2],
    'Color':['Yellow','Orange','Green','Green']})
df2=pd.DataFrame({'Date':['2013-11-24','2013-11-24','2013-11-24','2013-11-24','2013-11-25','2013-11-25'],
    'Fruit':['Banana','Orange','Apple','Celery','Apple','Orange'],
    'Num':[22.1,8.6,7.6,1000,22.1,8.6],
    'Color':['Yellow','Orange','Green','Green','Red','Orange']})

# find which rows are in df2 that aren't in df1 by Date and Fruit
df_2notin1 = df2[~(df2['Date'].isin(df1['Date']) & df2['Fruit'].isin(df1['Fruit']) )].dropna().reset_index(drop=True)

# output
print('df_2notin1\n', df_2notin1)
#      Color        Date   Fruit   Num
# 0     Red  2013-11-25   Apple  22.1
# 1  Orange  2013-11-25  Orange   8.6
SpeedCoder5
źródło
1

Mam to rozwiązanie. Czy to ci pomaga?

text = """df1:
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green

df2:
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange



argetz45
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 118.6 Orange
2013-11-24 Apple 74.6 Green
2013-11-24 Celery 10.2 Green
2013-11-25     Nuts    45.8 Brown
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange
2013-11-26   Pear 102.54    Pale"""

.

from collections import OrderedDict
import re

r = re.compile('([a-zA-Z\d]+).*\n'
               '(20\d\d-[01]\d-[0123]\d.+\n?'
               '(.+\n?)*)'
               '(?=[ \n]*\Z'
                  '|'
                  '\n+[a-zA-Z\d]+.*\n'
                  '20\d\d-[01]\d-[0123]\d)')

r2 = re.compile('((20\d\d-[01]\d-[0123]\d) +([^\d.]+)(?<! )[^\n]+)')

d = OrderedDict()
bef = []

for m in r.finditer(text):
    li = []
    for x in r2.findall(m.group(2)):
        if not any(x[1:3]==elbef for elbef in bef):
            bef.append(x[1:3])
            li.append(x[0])
    d[m.group(1)] = li


for name,lu in d.iteritems():
    print '%s\n%s\n' % (name,'\n'.join(lu))

wynik

df1
2013-11-24 Banana 22.1 Yellow
2013-11-24 Orange 8.6 Orange
2013-11-24 Apple 7.6 Green
2013-11-24 Celery 10.2 Green

df2
2013-11-25 Apple 22.1 Red
2013-11-25 Orange 8.6 Orange

argetz45
2013-11-25     Nuts    45.8 Brown
2013-11-26   Pear 102.54    Pale
eyquem
źródło
Dzięki za pomoc. Widziałem odpowiedź od @alko i ten kod działał dobrze.
Eric D. Brown
1

Ponieważ pandas >= 1.1.0mamy DataFrame.comparei Series.compare.

Uwaga: ta metoda może porównywać tylko obiekty DataFrame o identycznych etykietach, co oznacza, że ​​ramki DataFrame mają identyczne etykiety wierszy i kolumn.

df1 = pd.DataFrame({'A': [1, 2, 3],
                    'B': [4, 5, 6],
                    'C': [7, np.NaN, 9]})

df2 = pd.DataFrame({'A': [1, 99, 3],
                    'B': [4, 5, 81],
                    'C': [7, 8, 9]})

   A  B    C
0  1  4  7.0
1  2  5  NaN
2  3  6  9.0 

    A   B  C
0   1   4  7
1  99   5  8
2   3  81  9
df1.compare(df2)

     A          B          C      
  self other self other self other
1  2.0  99.0  NaN   NaN  NaN   8.0
2  NaN   NaN  6.0  81.0  NaN   NaN
Erfan
źródło
Dziękuję za tę informację. Nie przeszedłem jeszcze do wersji 1.1, ale dobrze jest wiedzieć.
Eric D. Brown
0

Jednym z ważnych szczegółów, na które należy zwrócić uwagę, jest to, że Twoje dane mają zduplikowane wartości indeksu , więc aby wykonać proste porównanie, musimy ustawić wszystko jako unikalne, df.reset_index()a zatem możemy dokonać selekcji na podstawie warunków. Po zdefiniowaniu indeksu zakładam, że chciałbyś zachować indeks, więc istnieje rozwiązanie jednowierszowe:

[~df2.reset_index().isin(df1.reset_index())].dropna().set_index('Date')

Kiedy celem z pythonowego punktu widzenia jest poprawa czytelności, możemy trochę zepsuć:

# keep the index name, if it does not have a name it uses the default name
index_name = df.index.name if df.index.name else 'index' 

# setting the index to become unique
df1 = df1.reset_index()
df2 = df2.reset_index()

# getting the differences to a Dataframe
df_diff = df2[~df2.isin(df1)].dropna().set_index(index_name)
gandreoti
źródło
0

Mam nadzieję, że to Ci się przyda. ^ o ^

df1 = pd.DataFrame({'date': ['0207', '0207'], 'col1': [1, 2]})
df2 = pd.DataFrame({'date': ['0207', '0207', '0208', '0208'], 'col1': [1, 2, 3, 4]})
print(f"df1(Before):\n{df1}\ndf2:\n{df2}")
"""
df1(Before):
   date  col1
0  0207     1
1  0207     2

df2:
   date  col1
0  0207     1
1  0207     2
2  0208     3
3  0208     4
"""

old_set = set(df1.index.values)
new_set = set(df2.index.values)
new_data_index = new_set - old_set
new_data_list = []
for idx in new_data_index:
    new_data_list.append(df2.loc[idx])

if len(new_data_list) > 0:
    df1 = df1.append(new_data_list)
print(f"df1(After):\n{df1}")
"""
df1(After):
   date  col1
0  0207     1
1  0207     2
2  0208     3
3  0208     4
"""
Carson
źródło
0

Wypróbowałem tę metodę i zadziałała. Mam nadzieję, że to też może pomóc:

"""Identify differences between two pandas DataFrames"""
df1.sort_index(inplace=True)
df2.sort_index(inplace=True)
df_all = pd.concat([df1, df12], axis='columns', keys=['First', 'Second'])
df_final = df_all.swaplevel(axis='columns')[df1.columns[1:]]
df_final[df_final['change this to one of the columns'] != df_final['change this to one of the columns']]
debugowanie XD
źródło