Comparer deux dataframes et obtenir les différences


89

J'ai deux dataframes. Exemples:

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

Chaque dataframe a la date comme index. Les deux dataframes ont la même structure.

Ce que je veux faire, c'est comparer ces deux dataframes et trouver quelles lignes sont dans df2 qui ne sont pas dans df1. Je veux comparer la date (index) et la première colonne (Banana, APple, etc.) pour voir si elles existent dans df2 vs df1.

J'ai essayé ce qui suit:

Pour la première approche, j'obtiens cette erreur: "Exception: ne peut comparer que des objets DataFrame étiquetés de manière identique" . J'ai essayé de supprimer la date comme index mais j'obtiens la même erreur.

Sur la troisième approche , j'obtiens l'assert pour retourner False mais je ne peux pas comprendre comment voir réellement les différentes lignes.

Tous les pointeurs seraient les bienvenus


Si vous faites ceci: cookbook-r.com/Manipulating_data/… , va-t-il se débarrasser de l'exception «Objets DataFrame étiquetés de manière identique»?
Anthony Kong

J'ai changé de nom de colonne plusieurs fois pour essayer de contourner le problème sans succès.
Eric

1
FWIW, j'ai changé les noms de colonne pour être "a, b, c, d" sur les deux dataframes et reçois le même message d'erreur.
Eric

Réponses:


103

Cette approche df1 != df2ne fonctionne que pour les dataframes avec des lignes et des colonnes identiques. En fait, tous les axes des dataframes sont comparés à la _indexed_sameméthode, et une exception est déclenchée si des différences sont trouvées, même dans l'ordre des colonnes / index.

Si je vous ai bien compris, vous ne voulez pas trouver de changements, mais une différence symétrique. Pour cela, une approche pourrait être de concaténer des dataframes:

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

par groupe

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

obtenir l'index des enregistrements uniques

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

filtre

>>> df.reindex(idx)
         Date   Fruit   Num   Color
9  2013-11-25  Orange   8.6  Orange
8  2013-11-25   Apple  22.1     Red

C'était la réponse. J'ai supprimé l'index "Date" et suivi cette approche et j'obtiens le bon résultat.
Eric

8
Existe-t-il un moyen simple d'ajouter un indicateur à cela pour voir quelles lignes ont été supprimées / ajoutées / modifiées de df1 à df2?
pyCthon

@alko Je me demandais, est-ce que cela pd.concatajoute uniquement les éléments manquants du df1? Ou remplace-t-il df1complètement par df2?
jake wong

@jakewong pd.concat- tel qu'utilisé ici - fait une jointure externe. En d'autres termes, il joint tous les index des deux df et c'est en fait le comportement par défaut pour pd.concat(), voici la documentation pandas.pydata.org/pandas-docs/stable/merging.html
Thanos

quel est le nombre maximum d'enregistrements que nous pouvons comparer en utilisant des pandas?
pyd

25

Passer les dataframes à concat dans un dictionnaire, aboutit à une dataframe multi-index à partir de laquelle vous pouvez facilement supprimer les doublons, ce qui se traduit par une dataframe multi-index avec les différences entre les dataframes:

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)

Résultat:

             Date   Fruit   Num   Color
DF2 4  2013-11-25   Apple  22.1     Red
    5  2013-11-25  Orange   8.6  Orange

1
C'est une méthode beaucoup plus simple, une seule révision supplémentaire peut la rendre plus facile. Pas besoin de concaténer dans un dictionnaire, utiliser df = pd.concat ([df1, df2]) ferait de même
ling

vous ne devez pas écraser le mot-clé intégré dict!
denfromufa

Existe-t-il un moyen d'ajouter à cela pour déterminer quelle trame de données contenait la ligne unique?
jlewkovich

Vous pouvez dire par le premier niveau dans le multiindex qui contient la clé de la dataframe dans le dictionnaire (j'ai mis à jour la sortie avec les bonnes clés)
jur

24

Mise à jour et placement, quelque part qu'il sera plus facile pour les autres de trouver, le commentaire de ling sur la réponse de jur ci-dessus.

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

Test avec ces dataframes:

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'],
})

Résultats en ceci: entrez la description de l'image ici


5

En s'appuyant sur la réponse d'alko qui a presque fonctionné pour moi, à l'exception de l'étape de filtrage (où j'obtiens :) ValueError: cannot reindex from a duplicate axis, voici la solution finale que j'ai utilisée:

# 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]

Bel ajout à la réponse. Merci
Eric

1
J'obtiens l'erreur » IndexError: index out of bounds', lorsque j'essaye d'exécuter la troisième ligne.
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']

c'est la meilleure réponse
moshevi

3

Il existe une solution plus simple, plus rapide et meilleure, et si les nombres sont différents, cela peut même vous donner des différences de quantités:

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_'])

Ici, df_diff est un synopsis des différences. Vous pouvez même l'utiliser pour trouver les différences de quantités. Dans votre exemple:

entrez la description de l'image ici

Explication: De la même manière que pour comparer deux listes, pour le faire efficacement, nous devons d'abord les ordonner puis les comparer (la conversion de la liste en ensembles / hachage serait également rapide; les deux sont une amélioration incroyable de la simple double boucle de comparaison O (N ^ 2)

Remarque: le code suivant produit les tables:

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'],
})

3

Fonder une solution simple ici:

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

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


1
Bienvenue dans Stack Overflow Tom2shoes. Veuillez ne pas fournir de réponses par lien uniquement, essayez d'extraire le contenu du lien et laissez-le uniquement comme référence (car le contenu du lien peut être supprimé ou le lien lui-même peut se rompre). Pour plus d'informations, reportez-vous à "Comment rédiger une bonne réponse?" . Si vous pensez que cette question a déjà reçu une réponse dans une autre question, veuillez la marquer comme doublon.
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

1

J'ai cette solution. Cela vous aide-t-il?

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))

résultat

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

Merci pour l'aide. J'ai vu la réponse de @alko et ce code a bien fonctionné.
Eric

1

Depuis que pandas >= 1.1.0nous avons DataFrame.compareet Series.compare.

Remarque: la méthode ne peut comparer que des objets DataFrame étiquetés de manière identique, cela signifie des DataFrames avec des étiquettes de ligne et de colonne identiques.

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

Merci pour cette information. Je ne suis pas encore passé à la version 1.1, mais c'est bon à savoir.
Eric

0

Un détail important à noter est que vos données ont des valeurs d'index en double.Par conséquent, pour effectuer une comparaison simple, nous devons tout rendre unique df.reset_index()et nous pouvons donc effectuer des sélections en fonction des conditions. Une fois que dans votre cas, l'index est défini, je suppose que vous souhaitez conserver l'index afin qu'il existe une solution en une ligne:

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

Une fois que l'objectif d'un point de vue pythonique est d'améliorer la lisibilité, nous pouvons casser un peu:

# 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)

0

J'espère que cela vous sera utile. ^ 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
"""

0

J'ai essayé cette méthode et cela a fonctionné. J'espère que cela peut aussi aider:

"""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']]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.