比较字典与不可用或不可比的值? (例如列表或数据框)

时间:2017-04-19 19:33:15

标签: python pandas dictionary dataframe comparison

TL; DR:如果它们中的一些具有不可变/可变的值(例如列表或pandas Dataframes),你如何比较两个python词典?

我必须比较字典对的相等性。从这个意义上讲,这个问题与这两个问题类似,但他们的解决方案似乎只适用于不可变对象 ......

我的问题是,我处理成对的高度嵌套的词典,其中不可用的对象可以在不同的地方找到,具体取决于哪对词典我比较。我的想法是,我需要迭代字典中包含的最便宜的值,并且不能仅仅依赖于仅展开最高键值对的dict.iteritems()。我不确定如何迭代字典中包含的所有可能的键值对,并使用sets / ==对可混合对象进行比较,在pandas数据帧的情况下,运行df1.equals(df2).(注意对于pandas数据帧,只需运行df1==df2即可进行分段比较,并且NA的处理效果不佳。df1.equals(df2)可以解决这个问题。)

例如:

a = {'x': 1, 'y': {'z': "George", 'w': df1}}
b = {'x': 1, 'y': {'z': "George", 'w': df1}}
c = {'x': 1, 'y': {'z': "George", 'w': df2}}

至少并且这已经非常棒了,解决方案将产生TRUE / FALSE,以确定它们的值是否相同并且适用于pandas数据帧。

def dict_compare(d1, d2):
   if ...
      return True
   elif ...
      return False

dict_compare(a,b)
>>> True
dict_compare(a,c)
>>> False

中等更好:解决方案会指出字典中哪些键/值会有所不同。

在理想情况下:解决方案可以将值分为4个分组:

  • 添加,
  • 除去,
  • 修饰
  • 相同

2 个答案:

答案 0 :(得分:1)

嗯,有一种方法可以使任何类型都具有可比性:只需将它包装在一个比较你需要它的类中:

class DataFrameWrapper():
    def __init__(self, df):
        self.df = df

    def __eq__(self, other):
        return self.df.equals(other.df)

所以当你包装你的"无法比较的"您现在只需使用==

>>> import pandas as pd

>>> df1 = pd.DataFrame({'a': [1,2,3]})
>>> df2 = pd.DataFrame({'a': [3,2,1]})

>>> a = {'x': 1, 'y': {'z': "George", 'w': DataFrameWrapper(df1)}}
>>> b = {'x': 1, 'y': {'z': "George", 'w': DataFrameWrapper(df1)}}
>>> c = {'x': 1, 'y': {'z': "George", 'w': DataFrameWrapper(df2)}}
>>> a == b
True
>>> a == c
False

当然,包装你的价值观有它的缺点,但如果你只需要比较它们,这将是一个非常简单的方法。所有可能需要的是在进行比较之前的递归包装和之后的递归展开:

def recursivewrap(dict_):
    for key, value in dict_.items():
        wrapper = wrappers.get(type(value), lambda x: x)  # for other types don't wrap
        dict_[key] = wrapper(value)
    return dict_  # return dict_ so this function can be used for recursion

def recursiveunwrap(dict_):
    for key, value in dict_.items():
        unwrapper = unwrappers.get(type(value), lambda x: x)
        dict_[key] = unwrapper(value)
    return dict_

wrappers = {pd.DataFrame: DataFrameWrapper,
            dict: recursivewrap}
unwrappers = {DataFrameWrapper: lambda x: x.df,
              dict: recursiveunwrap}

示例案例:

>>> recursivewrap(a)
{'x': 1,
 'y': {'w': <__main__.DataFrameWrapper at 0x2affddcc048>, 'z': 'George'}}
>>> recursiveunwrap(recursivewrap(a))
{'x': 1, 'y': {'w':    a
  0  1
  1  2
  2  3, 'z': 'George'}}

如果你觉得真的很冒险,你可以使用包装类,根据比较结果修改一些保存信息不等的变量。

答案的这一部分是基于没有包含嵌套的原始问题:

您可以从可散列值中分离不可消除的值,并对可散列值和&#34; order-independent,&#34;进行集合比较。不可比较的列表比较:

def split_hashable_unhashable(vals):
    """Seperate hashable values from unhashable ones and returns a set (hashables) 
    and list (unhashable ones)"""
    set_ = set()
    list_ = []
    for val in vals:
        try:
            set_.add(val)
        except TypeError:  # unhashable
            list_.append(val)
    return set_, list_


def compare_lists_arbitary_order(l1, l2, cmp=pd.DataFrame.equals):
    """Compare two lists using a custom comparison function, the order of the
    elements is ignored."""
    # need to have equal lengths otherwise they can't be equal
    if len(l1) != len(l2):  
        return False

    remaining_indices = set(range(len(l2)))
    for item in l1:
        for cmpidx in remaining_indices:
            if cmp(item, l2[cmpidx]):
                remaining_indices.remove(cmpidx)
                break
        else:
            # Run through the loop without finding a match
            return False
    return True

def dict_compare(d1, d2):
    if set(d1) != set(d2):  # compare the dictionary keys
        return False
    set1, list1 = split_hashable_unhashable(d1.values())
    set2, list2 = split_hashable_unhashable(d2.values())
    if set1 != set2:  # set comparison is easy
        return False

    return compare_lists_arbitary_order(list1, list2)

它比预期的要长一点。对于你的测试用例,它肯定有效:

>>> import pandas as pd

>>> df1 = pd.DataFrame({'a': [1,2,3]})
>>> df2 = pd.DataFrame({'a': [3,2,1]})

>>> a = {'x': 1, 'y': df1}
>>> b = {'y': 1, 'x': df1}
>>> c = {'y': 1, 'x': df2}
>>> dict_compare(a, b)
True
>>> dict_compare(a, c)
False
>>> dict_compare(b, c)
False

set - 操作也可用于查找差异(请参阅set.difference)。对于list来说,它有点复杂,但并非真的不可能。可以将未找到匹配项的项添加到单独的列表中,而不是立即返回False

答案 1 :(得分:0)

Deepdiff库提供了扩展两个python字典差异的强大功能

https://github.com/seperman/deepdiff

DeepDiff:字典,可迭代项,字符串和其他对象的深层差异。它将递归地查找所有更改。

pip安装deepdiff