python - अंतर को हाइलाइट करते हुए-दो पांडों डेटाफ्रेम में तरफ से आउटपुटिंग अंतर




html pandas dataframe panel (9)

मैं दो डेटाफ्रेम के बीच क्या बदल गया है, इसे हाइलाइट करने की कोशिश कर रहा हूं।

मान लीजिए मेरे पास दो पायथन पांडस डेटाफ्रेम हैं:

"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, बूलियन, स्ट्रिंग हो सकता है)
  2. आउटपुट पंक्तियां समान, पुरानी और नई मानों (आदर्श रूप से एक HTML तालिका में) के साथ पंक्तियां होती हैं ताकि उपभोक्ता स्पष्ट रूप से देख सकें कि दो डेटाफ्रेम के बीच क्या बदल गया है:

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

मुझे लगता है कि मैं कॉलम तुलना द्वारा पंक्ति और कॉलम द्वारा एक पंक्ति कर सकता हूं, लेकिन क्या कोई आसान तरीका है?


Answers

दो डेटाफ्रेम के बीच अंतर को हाइलाइट करना

एक अंतर है जहां कोशिकाओं के पृष्ठभूमि रंग को हाइलाइट करने के लिए डेटाफ्रेम शैली संपत्ति का उपयोग करना संभव है।

मूल प्रश्न से उदाहरण डेटा का उपयोग करना

पहला चरण डेटाफ्रेम को क्षैतिज रूप से संगत फ़ंक्शन के साथ जोड़ना है और प्रत्येक फ्रेम को 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)

यह उन कोशिकाओं को हाइलाइट करेगा जो दोनों में मूल्य गुम हैं। आप या तो उन्हें भर सकते हैं या अतिरिक्त तर्क प्रदान कर सकते हैं ताकि वे हाइलाइट न हों।


यह उत्तर बस @ एंडी हेडन का विस्तार करता है, जिससे यह संख्यात्मक क्षेत्र 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

@ जर्नोइस के जवाब के साथ झुकाव के बाद, मैं पैनल के विकृति के कारण पैनल के बजाय मल्टीइंडेक्स का उपयोग करके इसे काम करने में सक्षम था।

सबसे पहले, कुछ डमी डेटा बनाएं:

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 फ़ंक्शन को परिभाषित करें, इस मामले में मैं उसके उत्तर से उसका उपयोग report_diff वही रहता है:

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

फिर, मैं डेटा को मल्टीइंडेक्स डेटाफ्रेम में जोड़ना चाहता हूं:

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

इतना ही!


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

चयन और विलय का उपयोग करके एक और तरीका यहां दिया गया है:

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

जुपीटर स्क्रीनशॉट से वही बात है:


@cge का उत्तर विस्तारित करना, जो परिणाम की अधिक पठनीयता के लिए बहुत अच्छा है:

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

पूर्ण प्रदर्शन उदाहरण:

a = 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(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 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.BytesIO(texts[0]), widths=[5,7,25,21,20])
df2 = pd.read_fwf(io.BytesIO(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

पहला भाग कॉन्स्टैंटिन के समान है, आप बुलियन प्राप्त कर सकते हैं कि कौन सी पंक्तियां खाली हैं *:

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

यहां पहली प्रविष्टि इंडेक्स है और दूसरा कॉलम बदल दिया गया है।

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

* नोट: यह महत्वपूर्ण है कि df1 और df2 यहां एक ही इंडेक्स साझा करें। इस अस्पष्टता को दूर करने के लिए, आप यह सुनिश्चित कर सकते हैं कि आप केवल df1.index & df2.index का उपयोग करके साझा किए गए लेबल df1.index & df2.index , लेकिन मुझे लगता है कि मैं इसे एक अभ्यास के रूप में छोड़ दूंगा।


एक टैब द्वारा to_csv के लिए आप to_csv के sep तर्क का उपयोग कर सकते हैं:

df.to_csv(file_name, sep='\t')

एक विशिष्ट एन्कोडिंग (उदाहरण के लिए 'utf-8') का उपयोग करने के लिए encoding तर्क का उपयोग करें:

df.to_csv(file_name, sep='\t', encoding='utf-8')




python html pandas dataframe panel