【问题标题】:Compare Dictionaries with unhashable or uncomparable values? (e.g. Lists or Dataframes)将字典与不可散列或不可比较的值进行比较? (例如列表或数据框)
【发布时间】:2017-09-16 04:55:05
【问题描述】:

TL;DR:如果其中一些具有不可散列/可变的值(例如列表或 pandas Dataframes),您如何比较两个 Python 字典?


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

我的问题是,我正在处理成对的高度嵌套的字典,根据我的字典对,不可散列的对象可以在不同的地方找到米比较。我的想法是,我需要遍历字典中包含的最重要的值,而不能仅仅依赖于仅展开最高键值对的 dict.iteritems()。我不确定如何遍历字典中包含的所有可能的键值对,并使用 set/== 对可散列对象进行比较,在熊猫数据帧的情况下,运行df1.equals(df2).(熊猫数据帧的注意事项,只运行 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 组:

  • 已添加,
  • 已移除,
  • 修改
  • 一样

【问题讨论】:

  • @MSeifert 是不是更清楚了?
  • @Afflatus 你已经解决了数据框的问题,什么具体问题阻止了你?
  • @Goyo 我不确定如何遍历字典中包含的所有可能的键值对——即我正在处理可以在其中找到不可散列对象的高度嵌套字典对不同的地方取决于我要比较的字典。

标签: python pandas dictionary dataframe comparison


【解决方案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'}}

如果您真的很喜欢冒险,您可以使用包装类,根据比较结果修改一些包含不相等信息的变量。


这部分答案基于不包含嵌套的原始问题:

您可以将不可散列值与可散列值分开,并对可散列值进行集合比较,对不可散列值进行“顺序无关”列表比较:

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)。 lists 有点复杂,但并非不可能。可以将未找到匹配项的项目添加到单独的列表中,而不是立即返回False

【讨论】:

  • 当我用一对字典运行它时:dict_compare(dict1, dict2),我得到一个错误追溯到它调用的 compare_lists_arbitary_order() 函数:“if cmp(item, l2[cmpidx ]):"错误信息是 TypeError: unbound method equals() must be called with DataFrame instance as first argument (got dict instance instead)
  • @Afflatus 是的,你在我写答案时改变了你的问题(起初我没有注意到,并认为这与嵌套字典无关)。这就是为什么我在开头包含了一种可能的递归方法,可以为类型定制:)
【解决方案2】:

Deepdiff 库提供了区分两个 python 字典的广泛能力

https://github.com/seperman/deepdiff

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

pip 安装 deepdiff

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-24
    • 1970-01-01
    • 2020-11-07
    • 1970-01-01
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    相关资源
    最近更新 更多