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

问题描述 投票:0回答:2

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


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

我的问题是,我正在处理成对的高度嵌套的词典,其中根据我正在比较的哪一对词典,可以在不同的地方找到不可用的对象。我的想法是,我需要迭代字典中包含的最便宜的值,而不能只依赖于只展开最高键值对的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个分组:

  • 添加,
  • 去除,
  • 改性
  • 相同
python pandas dictionary dataframe comparison
2个回答
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


0
投票

Deepdiff库提供了扩展两个python词典的广泛能力

https://github.com/seperman/deepdiff

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

pip安装deepdiff

© www.soinside.com 2019 - 2024. All rights reserved.