【问题标题】:Diff between two dataframes in pandas熊猫中两个数据框之间的差异
【发布时间】:2018-04-18 06:52:29
【问题描述】:

我有两个数据框,它们都具有相同的基本架构。 (4 个日期字段、几个字符串字段和 4-5 个浮点字段)。打电话给他们df1df2

我想要做的基本上是获得两者的“差异” - 我得到两个数据帧之间未共享的所有行(不在集合交集中)。请注意,两个数据帧的长度不必相同。

我尝试使用pandas.merge(how='outer'),但我不确定将哪一列作为“键”传递,因为实际上没有一列,而且我尝试的各种组合都不起作用。 df1df2 可能有两个(或多个)相同的行。

在 pandas/Python 中有什么好的方法来做到这一点?

【问题讨论】:

标签: python pandas merge compare diff


【解决方案1】:

您可以使用此功能,输出是 6 个数据帧的有序 dict,您可以将其写入 excel 以进行进一步分析。

  • “df1”和“df2”指的是您的输入数据帧。
  • 'uid' 是指构成唯一键的列或列组合。 (即“水果”)
  • 'dedupe'(默认值=True)删除 df1 和 df2 中的重复项。 (参考 cmets 中的第 4 步)
  • 'labels'(默认 = ('df1','df2'))允许您命名输入数据帧。如果两个数据帧中都存在唯一键,但有 在一列或多列中的不同值,了解这些行通常很重要,将它们一个放在另一个之上,并用名称标记该行,以便我们知道它属于哪个数据框。
  • 'drop' 可以在考虑差异时将列列表排除在考虑之外

这里是:

df1 = pd.DataFrame([['apple', '1'], ['banana', 2], ['coconut',3]], columns=['Fruits','Quantity'])
df2 = pd.DataFrame([['apple', '1'], ['banana', 3], ['durian',4]], columns=['Fruits','Quantity'])
dict1 = diff_func(df1, df2, 'Fruits')

In [10]: dict1['df1_only']:
Out[10]:
    Fruits Quantity
1  coconut        3

In [11]: dict1['df2_only']:
Out[11]:
   Fruits Quantity
3  durian        4

In [12]: dict1['Diff']:
Out[12]:
   Fruits Quantity df1 or df2
0  banana        2        df1
1  banana        3        df2

In [13]: dict1['Merge']:
Out[13]:
  Fruits Quantity
0  apple        1

代码如下:

import pandas as pd
from collections import OrderedDict as od

def diff_func(df1, df2, uid, dedupe=True, labels=('df1', 'df2'), drop=[]):
    dict_df = {labels[0]: df1, labels[1]: df2}
    col1 = df1.columns.values.tolist()
    col2 = df2.columns.values.tolist()

    # There could be columns known to be different, hence allow user to pass this as a list to be dropped.
    if drop:
        print ('Ignoring columns {} in comparison.'.format(', '.join(drop)))
        col1 = list(filter(lambda x: x not in drop, col1))
        col2 = list(filter(lambda x: x not in drop, col2))
        df1 = df1[col1]
        df2 = df2[col2]


    # Step 1 - Check if no. of columns are the same:
    len_lr = len(col1), len(col2)
    assert len_lr[0]==len_lr[1], \
    'Cannot compare frames with different number of columns: {}.'.format(len_lr)

    # Step 2a - Check if the set of column headers are the same
    #           (order doesnt matter)
    assert set(col1)==set(col2), \
    'Left column headers are different from right column headers.' \
       +'\n   Left orphans: {}'.format(list(set(col1)-set(col2))) \
       +'\n   Right orphans: {}'.format(list(set(col2)-set(col1)))

    # Step 2b - Check if the column headers are in the same order
    if col1 != col2:
        print ('[Note] Reordering right Dataframe...')
        df2 = df2[col1]

    # Step 3 - Check datatype are the same [Order is important]
    if set((df1.dtypes == df2.dtypes).tolist()) - {True}:
        print ('dtypes are not the same.')
        df_dtypes = pd.DataFrame({labels[0]:df1.dtypes,labels[1]:df2.dtypes,'Diff':(df1.dtypes == df2.dtypes)})
        df_dtypes = df_dtypes[df_dtypes['Diff']==False][[labels[0],labels[1],'Diff']]
        print (df_dtypes)
    else:
        print ('DataType check: Passed')

    # Step 4 - Check for duplicate rows
    if dedupe:
        for key, df in dict_df.items():
            if df.shape[0] != df.drop_duplicates().shape[0]:
                print(key + ': Duplicates exists, they will be dropped.')
                dict_df[key] = df.drop_duplicates()

    # Step 5 - Check for duplicate uids.
    if type(uid)==str or type(uid)==list:
        print ('Uniqueness check: {}'.format(uid))
        for key, df in dict_df.items():
            count_uid = df.shape[0]
            count_uid_unique = df[uid].drop_duplicates().shape[0]
            var = [0,1][count_uid_unique == df.shape[0]] #<-- Round off to the nearest integer if it is 100%
            pct = round(100*count_uid_unique/df.shape[0], var)
            print ('{}: {} out of {} are unique ({}%).'.format(key, count_uid_unique, count_uid, pct))

    # Checks complete, begin merge. '''Remenber to dedupe, provide labels for common_no_match'''
    dict_result = od()
    df_merge = pd.merge(df1, df2, on=col1, how='inner')
    if not df_merge.shape[0]:
        print ('Error: Merged DataFrame is empty.')
    else:
        dict_result[labels[0]] = df1
        dict_result[labels[1]] = df2
        dict_result['Merge'] = df_merge
        if type(uid)==str:
            uid = [uid]

        if type(uid)==list:
            df1_only = df1.append(df_merge).reset_index(drop=True)
            df1_only['Duplicated']=df1_only.duplicated(keep=False)  #keep=False, marks all duplicates as True
            df1_only = df1_only[df1_only['Duplicated']==False]
            df2_only = df2.append(df_merge).reset_index(drop=True)
            df2_only['Duplicated']=df2_only.duplicated(keep=False)
            df2_only = df2_only[df2_only['Duplicated']==False]

            label = labels[0]+' or '+labels[1]
            df_lc = df1_only.copy()
            df_lc[label] = labels[0]
            df_rc = df2_only.copy()
            df_rc[label] = labels[1]
            df_c = df_lc.append(df_rc).reset_index(drop=True)
            df_c['Duplicated'] = df_c.duplicated(subset=uid, keep=False)
            df_c1 = df_c[df_c['Duplicated']==True]
            df_c1 = df_c1.drop('Duplicated', axis=1)
            df_uc = df_c[df_c['Duplicated']==False]

            df_uc_left = df_uc[df_uc[label]==labels[0]]
            df_uc_right = df_uc[df_uc[label]==labels[1]]

            dict_result[labels[0]+'_only'] = df_uc_left.drop(['Duplicated', label], axis=1)
            dict_result[labels[1]+'_only'] = df_uc_right.drop(['Duplicated', label], axis=1)
            dict_result['Diff'] = df_c1.sort_values(uid).reset_index(drop=True)

    return dict_result

【讨论】:

    【解决方案2】:

    试试这个:

    diff_df = pd.merge(df1, df2, how='outer', indicator='Exist')
    
    diff_df = diff_df.loc[diff_df['Exist'] != 'both']
    

    您将拥有一个包含 df1 和 df2 上都不存在的所有行的数据框。

    【讨论】:

      【解决方案3】:

      left_df.merge(df,left_on=left_df.columns.tolist(),right_on=df.columns.tolist(),how='outer')
      

      可以得到外连接结果。
      同样,您可以获得内部连接结果。然后创建一个您想要的差异。

      【讨论】:

      • 如果你要在所有列上进行内连接,你应该只做一个左反连接
      【解决方案4】:

      IIUC:
      你可以使用pd.Index.symmetric_difference

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

      【讨论】:

      【解决方案5】:
      1. 设置df2.columns = df1.columns

      2. 现在,将每一列设置为索引:df1 = df1.set_index(df1.columns.tolist())df2 也是如此。

      3. 您现在可以执行df1.index.difference(df2.index)df2.index.difference(df1.index),这两个结果是您不同的列。

      【讨论】:

        猜你喜欢
        • 2022-10-02
        • 1970-01-01
        • 1970-01-01
        • 2018-06-27
        • 1970-01-01
        • 2013-11-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多