2つのデータフレームを比較し、それらの違いを並べて出力する


162

2つのデータフレーム間で何が変わったかを正確に強調しようとしています。

2つのPython Pandasデータフレームがあるとします。

"StudentRoster Jan-1":
id   Name   score                    isEnrolled           Comment
111  Jack   2.17                     True                 He was late to class
112  Nick   1.11                     False                Graduated
113  Zoe    4.12                     True       

"StudentRoster Jan-2":
id   Name   score                    isEnrolled           Comment
111  Jack   2.17                     True                 He was late to class
112  Nick   1.21                     False                Graduated
113  Zoe    4.12                     False                On vacation

私の目標は、次のようなHTMLテーブルを出力することです。

  1. 変更された行を識別します(int、float、boolean、stringの場合があります)
  2. 同じ値、古い値、新しい値を持つ行を(理想的にはHTMLテーブルに)出力するため、コンシューマは2つのデータフレーム間の変更点を明確に確認できます。

    "StudentRoster Difference Jan-1 - Jan-2":  
    id   Name   score                    isEnrolled           Comment
    112  Nick   was 1.11| now 1.21       False                Graduated
    113  Zoe    4.12                     was True | now False was "" | now   "On   vacation"
    

行ごと、列ごとに比較できると思いますが、もっと簡単な方法はありますか?


pandas 1.1以降では、単一の関数呼び出し-でdf.compareこれを簡単に行うことができます。
cs95

回答:


153

最初の部分はコンスタンティンに似ており、どの行が空であるかを示すブール値を取得できます*:

In [21]: ne = (df1 != df2).any(1)

In [22]: ne
Out[22]:
0    False
1     True
2     True
dtype: bool

次に、変更されたエントリを確認できます。

In [23]: ne_stacked = (df1 != df2).stack()

In [24]: changed = ne_stacked[ne_stacked]

In [25]: changed.index.names = ['id', 'col']

In [26]: changed
Out[26]:
id  col
1   score         True
2   isEnrolled    True
    Comment       True
dtype: bool

ここで、最初のエントリはインデックスで、2番目のエントリは変更された列です。

In [27]: difference_locations = np.where(df1 != df2)

In [28]: changed_from = df1.values[difference_locations]

In [29]: changed_to = df2.values[difference_locations]

In [30]: pd.DataFrame({'from': changed_from, 'to': changed_to}, index=changed.index)
Out[30]:
               from           to
id col
1  score       1.11         1.21
2  isEnrolled  True        False
   Comment     None  On vacation

*注:ここで同じインデックスdf1df2共有することが重要です。このあいまいさを克服するために、を使用して共有ラベルのみを確認することができますが、これdf1.index & df2.indexは演習として残しておきます。


2
「同じインデックスを共有する」とは、「インデックスがソートされていることを確認する」という意味だと思います。これにより、インデックスの値に関係なく、df1最初のものと最初のものを比較df2します。JFYIは、これが自明ではない唯一の人物ではない場合に備えて。; Dありがとう!
dmn 2015年

12
nandf1とdf1の両方でスコアが等しい場合、この関数はからnanに変更されたと報告しnanます。これはをnp.nan != np.nan返すからTrueです。
James Owers

2
@kungfujamは正しいです。また、比較されている値がNoneの場合、そこでも誤った差異が発生します
FistOfFury

明確にするために-このソリューションの問題を説明し、以下
James Owers

1
['row'、 'col']は、['id'、 'col']よりもchanged.index.namesとして推奨されます。これは、IDではなく行だからです。
藤田直樹

87

2つのデータフレームの違いを強調する

DataFrameスタイルプロパティを使用して、違いのあるセルの背景色を強調表示することができます。

元の質問のサンプルデータを使用する

最初のステップは、DataFrameをconcat関数と水平に連結し、各フレームをkeysパラメーターで区別することです。

df_all = pd.concat([df.set_index('id'), df2.set_index('id')], 
                   axis='columns', keys=['First', 'Second'])
df_all

ここに画像の説明を入力してください

列レベルを入れ替えて、同じ列名を隣り合わせにする方がおそらく簡単です。

df_final = df_all.swaplevel(axis='columns')[df.columns[1:]]
df_final

ここに画像の説明を入力してください

これで、フレームの違いを簡単に見つけることができます。しかし、さらに進んで、styleプロパティを使用して、異なるセルを強調表示できます。これを行うカスタム関数を定義しますこれは、ドキュメントのこの部分で確認できます

def highlight_diff(data, color='yellow'):
    attr = 'background-color: {}'.format(color)
    other = data.xs('First', axis='columns', level=-1)
    return pd.DataFrame(np.where(data.ne(other, level=0), attr, ''),
                        index=data.index, columns=data.columns)

df_final.style.apply(highlight_diff, axis=None)

ここに画像の説明を入力してください

これにより、両方に欠損値があるセルが強調表示されます。それらを入力するか、それらが強調表示されないように追加のロジックを提供できます。


1
「First」と「Second」の両方を異なる色で着色できるかどうか知っていますか?
aturegano

1
異なる行のみを選択することは可能ですか?この場合、最初の行(111)を選択せず​​に2番目と3番目の行を選択するにはどうすればよいですか?
shantanuo 2018年

1
@shantanuo、はい、最後のメソッドを編集しますdf_final[(df != df2).any(1)].style.apply(highlight_diff, axis=None)
anmol

3
26K行と400列のデータフレームを比較する場合、この実装には長い時間がかかります。それをスピードアップする方法はありますか?
CodeLord 2018

42

この答えは単に@Andy Haydenを拡張し、数値フィールドがnanである場合に回復力を持たせ、関数にラップします。

import pandas as pd
import numpy as np


def diff_pd(df1, df2):
    """Identify differences between two pandas DataFrames"""
    assert (df1.columns == df2.columns).all(), \
        "DataFrame column names are different"
    if any(df1.dtypes != df2.dtypes):
        "Data Types are different, trying to convert"
        df2 = df2.astype(df1.dtypes)
    if df1.equals(df2):
        return None
    else:
        # need to account for np.nan != np.nan returning True
        diff_mask = (df1 != df2) & ~(df1.isnull() & df2.isnull())
        ne_stacked = diff_mask.stack()
        changed = ne_stacked[ne_stacked]
        changed.index.names = ['id', 'col']
        difference_locations = np.where(diff_mask)
        changed_from = df1.values[difference_locations]
        changed_to = df2.values[difference_locations]
        return pd.DataFrame({'from': changed_from, 'to': changed_to},
                            index=changed.index)

データを使用して(スコア列にNaNが含まれるように少し編集します):

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

DF1 = StringIO("""id   Name   score                    isEnrolled           Comment
111  Jack   2.17                     True                 "He was late to class"
112  Nick   1.11                     False                "Graduated"
113  Zoe    NaN                     True                  " "
""")
DF2 = StringIO("""id   Name   score                    isEnrolled           Comment
111  Jack   2.17                     True                 "He was late to class"
112  Nick   1.21                     False                "Graduated"
113  Zoe    NaN                     False                "On vacation" """)
df1 = pd.read_table(DF1, sep='\s+', index_col='id')
df2 = pd.read_table(DF2, sep='\s+', index_col='id')
diff_pd(df1, df2)

出力:

                from           to
id  col                          
112 score       1.11         1.21
113 isEnrolled  True        False
    Comment           On vacation

データ型の小さな違いを処理するコードを追加しました。これを考慮しなかった場合、エラーがスローされます。
Roobie Nuby 2018年

どちらの側にも比較する同一の行がない場合はどうなりますか?
Kishor kumar R

@KishorkumarR次に、新しいデータフレームに追加された行を検出し、古いデータフレームから行を削除して、まず行を均等にする必要があります
Sabre

22
import pandas as pd
import io

texts = ['''\
id   Name   score                    isEnrolled                        Comment
111  Jack   2.17                     True                 He was late to class
112  Nick   1.11                     False                           Graduated
113  Zoe    4.12                     True       ''',

         '''\
id   Name   score                    isEnrolled                        Comment
111  Jack   2.17                     True                 He was late to class
112  Nick   1.21                     False                           Graduated
113  Zoe    4.12                     False                         On vacation''']


df1 = pd.read_fwf(io.StringIO(texts[0]), widths=[5,7,25,21,20])
df2 = pd.read_fwf(io.StringIO(texts[1]), widths=[5,7,25,21,20])
df = pd.concat([df1,df2]) 

print(df)
#     id  Name  score isEnrolled               Comment
# 0  111  Jack   2.17       True  He was late to class
# 1  112  Nick   1.11      False             Graduated
# 2  113   Zoe   4.12       True                   NaN
# 0  111  Jack   2.17       True  He was late to class
# 1  112  Nick   1.21      False             Graduated
# 2  113   Zoe   4.12      False           On vacation

df.set_index(['id', 'Name'], inplace=True)
print(df)
#           score isEnrolled               Comment
# id  Name                                        
# 111 Jack   2.17       True  He was late to class
# 112 Nick   1.11      False             Graduated
# 113 Zoe    4.12       True                   NaN
# 111 Jack   2.17       True  He was late to class
# 112 Nick   1.21      False             Graduated
# 113 Zoe    4.12      False           On vacation

def report_diff(x):
    return x[0] if x[0] == x[1] else '{} | {}'.format(*x)

changes = df.groupby(level=['id', 'Name']).agg(report_diff)
print(changes)

プリント

                score    isEnrolled               Comment
id  Name                                                 
111 Jack         2.17          True  He was late to class
112 Nick  1.11 | 1.21         False             Graduated
113 Zoe          4.12  True | False     nan | On vacation

3
非常に素晴らしい解決策、私のものよりはるかにコンパクトです!
アンディヘイデン2013年

1
@AndyHayden:私はこのソリューションに完全に満足しているわけではありません。インデックスがマルチレベルインデックスである場合にのみ機能するようです。idインデックスとしてのみ使用しようとするdf.groupby(level='id')と、エラーが発生し、理由が
わかり

19

私はこの問題に直面しましたが、この投稿を見つける前に答えを見つけました:

unutbuの答えに基づいて、データをロードします...

import pandas as pd
import io

texts = ['''\
id   Name   score                    isEnrolled                       Date
111  Jack                            True              2013-05-01 12:00:00
112  Nick   1.11                     False             2013-05-12 15:05:23
     Zoe    4.12                     True                                  ''',

         '''\
id   Name   score                    isEnrolled                       Date
111  Jack   2.17                     True              2013-05-01 12:00:00
112  Nick   1.21                     False                                
     Zoe    4.12                     False             2013-05-01 12:00:00''']


df1 = pd.read_fwf(io.StringIO(texts[0]), widths=[5,7,25,17,20], parse_dates=[4])
df2 = pd.read_fwf(io.StringIO(texts[1]), widths=[5,7,25,17,20], parse_dates=[4])

... 差分関数を定義します...

def report_diff(x):
    return x[0] if x[0] == x[1] else '{} | {}'.format(*x)

次に、単にパネルを使用して結論を​​出すことができます:

my_panel = pd.Panel(dict(df1=df1,df2=df2))
print my_panel.apply(report_diff, axis=0)

#          id  Name        score    isEnrolled                       Date
#0        111  Jack   nan | 2.17          True        2013-05-01 12:00:00
#1        112  Nick  1.11 | 1.21         False  2013-05-12 15:05:23 | NaT
#2  nan | nan   Zoe         4.12  True | False  NaT | 2013-05-01 12:00:00

ちなみに、IPython Notebookを使用している場合は、色付きのdiff関数を使用して、セルが異なるか、等しいか、左/右nullかによって色を指定できます。

from IPython.display import HTML
pd.options.display.max_colwidth = 500  # You need this, otherwise pandas
#                          will limit your HTML strings to 50 characters

def report_diff(x):
    if x[0]==x[1]:
        return unicode(x[0].__str__())
    elif pd.isnull(x[0]) and pd.isnull(x[1]):
        return u'<table style="background-color:#00ff00;font-weight:bold;">'+\
            '<tr><td>%s</td></tr><tr><td>%s</td></tr></table>' % ('nan', 'nan')
    elif pd.isnull(x[0]) and ~pd.isnull(x[1]):
        return u'<table style="background-color:#ffff00;font-weight:bold;">'+\
            '<tr><td>%s</td></tr><tr><td>%s</td></tr></table>' % ('nan', x[1])
    elif ~pd.isnull(x[0]) and pd.isnull(x[1]):
        return u'<table style="background-color:#0000ff;font-weight:bold;">'+\
            '<tr><td>%s</td></tr><tr><td>%s</td></tr></table>' % (x[0],'nan')
    else:
        return u'<table style="background-color:#ff0000;font-weight:bold;">'+\
            '<tr><td>%s</td></tr><tr><td>%s</td></tr></table>' % (x[0], x[1])

HTML(my_panel.apply(report_diff, axis=0).to_html(escape=False))

(通常のPythonでは、iPythonノートブックではありません)my_panel = pd.Panel(dict(df1=df1,df2=df2))関数内に含めることは可能report_diff()ですか?print report_diff(df1,df2)つまり、これを行うことは可能ですか?そして、印刷ステートメントと同じ出力を取得しますか?
edesz

pd.Panel(dict(df1=df1,df2=df2)).apply(report_diff, axis=0)-これは素晴らしいです!!!
MaxU

5
パネルは非推奨です!これを移植する方法はありますか?
denfromufa

@denfromufa私は私の答えでそれを更新することに一服しました:stackoverflow.com/a/49038417/7607701
Aaron N. Brock

9

2つのデータフレームに同じIDが含まれている場合、変更点を見つけるのは実際には非常に簡単です。実行frame1 != frame2するだけで、それぞれTrueが変更されたデータであるブールDataFrameが得られます。それから、を行うことで、変更された各行のインデックスを簡単に取得できますchangedids = frame1.index[np.any(frame1 != frame2,axis=1)]


6

concatとdrop_duplicatesを使用した別のアプローチ:

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

DF1 = StringIO("""id   Name   score                    isEnrolled           Comment
111  Jack   2.17                     True                 "He was late to class"
112  Nick   1.11                     False                "Graduated"
113  Zoe    NaN                     True                  " "
""")
DF2 = StringIO("""id   Name   score                    isEnrolled           Comment
111  Jack   2.17                     True                 "He was late to class"
112  Nick   1.21                     False                "Graduated"
113  Zoe    NaN                     False                "On vacation" """)

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

出力:

       Name  score isEnrolled      Comment
  id                                      
1 112  Nick   1.11      False    Graduated
  113   Zoe    NaN       True             
2 112  Nick   1.21      False    Graduated
  113   Zoe    NaN      False  On vacation

3

@journoisの答えをいじり回した後、Panelの機能が低下したため、Panelの代わりにMultiIndexを使用して動作させることができました

まず、いくつかのダミーデータを作成します。

df1 = pd.DataFrame({
    'id': ['111', '222', '333', '444', '555'],
    'let': ['a', 'b', 'c', 'd', 'e'],
    'num': ['1', '2', '3', '4', '5']
})
df2 = pd.DataFrame({
    'id': ['111', '222', '333', '444', '666'],
    'let': ['a', 'b', 'c', 'D', 'f'],
    'num': ['1', '2', 'Three', '4', '6'],
})

次に、diff関数を定義します。この場合、彼の答えからの1つを使用しますreport_diff

def report_diff(x):
    return x[0] if x[0] == x[1] else '{} | {}'.format(*x)

次に、データを連結してMultiIndexデータフレームを作成します。

df_all = pd.concat(
    [df1.set_index('id'), df2.set_index('id')], 
    axis='columns', 
    keys=['df1', 'df2'],
    join='outer'
)
df_all = df_all.swaplevel(axis='columns')[df1.columns[1:]]

最後に、report_diff各列グループの下に適用します。

df_final.groupby(level=0, axis=1).apply(lambda frame: frame.apply(report_diff, axis=1))

これは出力します:

         let        num
111        a          1
222        b          2
333        c  3 | Three
444    d | D          4
555  e | nan    5 | nan
666  nan | f    nan | 6

そしてそれだけです!


3

@cgeの回答を拡張すると、結果が読みやすくなります。

a[a != b][np.any(a != b, axis=1)].join(pd.DataFrame('a<->b', index=a.index, columns=['a<=>b'])).join(
        b[a != b][np.any(a != b, axis=1)]
        ,rsuffix='_b', how='outer'
).fillna('')

完全なデモの例:

import numpy as np, pandas as pd

a = pd.DataFrame(np.random.randn(7,3), columns=list('ABC'))
b = a.copy()
b.iloc[0,2] = np.nan
b.iloc[1,0] = 7
b.iloc[3,1] = 77
b.iloc[4,2] = 777

a[a != b][np.any(a != b, axis=1)].join(pd.DataFrame('a<->b', index=a.index, columns=['a<=>b'])).join(
        b[a != b][np.any(a != b, axis=1)]
        ,rsuffix='_b', how='outer'
).fillna('')

1

以下は、選択とマージを使用する別の方法です。

In [6]: # first lets create some dummy dataframes with some column(s) different
   ...: df1 = pd.DataFrame({'a': range(-5,0), 'b': range(10,15), 'c': range(20,25)})
   ...: df2 = pd.DataFrame({'a': range(-5,0), 'b': range(10,15), 'c': [20] + list(range(101,105))})


In [7]: df1
Out[7]:
   a   b   c
0 -5  10  20
1 -4  11  21
2 -3  12  22
3 -2  13  23
4 -1  14  24


In [8]: df2
Out[8]:
   a   b    c
0 -5  10   20
1 -4  11  101
2 -3  12  102
3 -2  13  103
4 -1  14  104


In [10]: # make condition over the columns you want to comapre
    ...: condition = df1['c'] != df2['c']
    ...:
    ...: # select rows from each dataframe where the condition holds
    ...: diff1 = df1[condition]
    ...: diff2 = df2[condition]


In [11]: # merge the selected rows (dataframes) with some suffixes (optional)
    ...: diff1.merge(diff2, on=['a','b'], suffixes=('_before', '_after'))
Out[11]:
   a   b  c_before  c_after
0 -4  11        21      101
1 -3  12        22      102
2 -2  13        23      103
3 -1  14        24      104

これは、Jupyterスクリーンショットの同じものです。

ここに画像の説明を入力してください


0

パンダ> = 1.1: DataFrame.compare

Pandas 1.1を使用すると、Ted Petrouの出力を単一の関数呼び出しで本質的に複製できます。ドキュメントからの例:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

df1.compare(df2)

  score       isEnrolled       Comment             
   self other       self other    self        other
1  1.11  1.21        NaN   NaN     NaN          NaN
2   NaN   NaN        1.0   0.0     NaN  On vacation

ここで、「自己」はLHS dataFrameを指し、「その他」はRHS DataFrameを指します。デフォルトでは、等しい値はNaNに置き換えられるため、差分のみに集中できます。同じ値を表示したい場合は、

df1.compare(df2, keep_equal=True, keep_shape=True) 

  score       isEnrolled           Comment             
   self other       self  other       self        other
1  1.11  1.21      False  False  Graduated    Graduated
2  4.12  4.12       True  False        NaN  On vacation

を使用して比較の軸を変更することもできますalign_axis

df1.compare(df2, align_axis='index')

         score  isEnrolled      Comment
1 self    1.11         NaN          NaN
  other   1.21         NaN          NaN
2 self     NaN         1.0          NaN
  other    NaN         0.0  On vacation

これは、列ごとではなく行ごとに値を比較します。


注:pandas 1.1はまだ実験段階であり開発サンドボックスを構築することによってのみ利用できます。
cs95

-1

2つのデータフレームの間の非対称な差を見出す機能を以下に実装されている:(に基づくパンダの設定差)GIST:https://gist.github.com/oneryalcin/68cf25f536a25e65f0b3c84f9c118e03

def diff_df(df1, df2, how="left"):
    """
      Find Difference of rows for given two dataframes
      this function is not symmetric, means
            diff(x, y) != diff(y, x)
      however
            diff(x, y, how='left') == diff(y, x, how='right')

      Ref: /programming/18180763/set-difference-for-pandas/40209800#40209800
    """
    if (df1.columns != df2.columns).any():
        raise ValueError("Two dataframe columns must match")

    if df1.equals(df2):
        return None
    elif how == 'right':
        return pd.concat([df2, df1, df1]).drop_duplicates(keep=False)
    elif how == 'left':
        return pd.concat([df1, df2, df2]).drop_duplicates(keep=False)
    else:
        raise ValueError('how parameter supports only "left" or "right keywords"')

例:

df1 = pd.DataFrame(d1)
Out[1]: 
                Comment  Name  isEnrolled  score
0  He was late to class  Jack        True   2.17
1             Graduated  Nick       False   1.11
2                         Zoe        True   4.12


df2 = pd.DataFrame(d2)

Out[2]: 
                Comment  Name  isEnrolled  score
0  He was late to class  Jack        True   2.17
1           On vacation   Zoe        True   4.12

diff_df(df1, df2)
Out[3]: 
     Comment  Name  isEnrolled  score
1  Graduated  Nick       False   1.11
2              Zoe        True   4.12

diff_df(df2, df1)
Out[4]: 
       Comment Name  isEnrolled  score
1  On vacation  Zoe        True   4.12

# This gives the same result as above
diff_df(df1, df2, how='right')
Out[22]: 
       Comment Name  isEnrolled  score
1  On vacation  Zoe        True   4.12

-1

パンダをpdとしてインポートnumpyをnpとしてインポート

df = pd.read_excel( 'D:\ HARISH \ DATA SCIENCE \ 1 MY Training \ SAMPLE DATA&projs \ CRICKET DATA \ IPL PLAYER LIST \ IPL PLAYER LIST _ harish.xlsx')

df1 = srh = df [df ['TEAM']。str.contains( "SRH")] df2 = csk = df [df ['TEAM']。str.contains( "CSK")]

srh = srh.iloc [:、0:2] csk = csk.iloc [:、0:2]

csk = csk.reset_index(drop = True)csk

srh = srh.reset_index(drop = True)srh

new = pd.concat([srh、csk]、axis = 1)

new.head()

**プレーヤータイププレーヤータイプ

0デビッドワーナーバットマン... MSドーニキャプテン

1 Bhuvaneshwar Kumar Bowler ... Ravindra Jadeja All-Rounder

2マニッシュパンディバットマン...スレッシュライナオールラウンダ

3ラシードカーンアーマンボウラー...

4 Shikhar Dhawanバットマン.... Dwayne Bravo All-Rounder


PLAYER TYPE PLAYER TYPE 0デイビッド・ワーナー打者MSドーニキャプテン1 Bhuvaneshwarクマーボウラーラヴィンドラ・ジャデジャオールラウンド2のManish Pandeyの打者スレッシュ・レイナオールラウンド3ラシッドカーンアルマンボウラーケダル・ジャダフオールラウンド4シカー・ダワン打者ドウェイン・ブラボーオールラウンド
ハリッシュ

こんにちはハリッシュ、あなたの答えをもう少しフォーマットしてください、さもなければそれを読むのはかなり難しいです:)
Markus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.