从一个NumPy数组列表中删除重复项

8

我有一个普通的Python列表,其中包含(多维)numPy数组,它们都具有相同的形状和相同数量的值。列表中的某些数组是先前数组的副本。

我的问题在于,我想要删除所有的重复项,但数据类型为numPy数组使这个问题有些复杂......

• 我不能使用set(),因为numPy数组不可哈希。
• 我无法在插入期间检查重复项,因为数组是由函数批量生成的,并使用.extend()添加到列表中。
• 没有借助numPy自己的函数,就不能直接比较numPy数组,所以我不能使用“if x in list”之类的语句...
• 列表的内容需要在处理结束时仍然是numPy数组;我可以比较转换为嵌套列表的数组的副本,但我不能永久地将数组转换为纯python列表。

您有什么建议来有效地删除重复项吗?

3个回答

7

使用这里的解决方案:numpy数组的最高效哈希属性,我们发现如果a是numpy数组,则使用a.tostring()进行哈希处理效果最佳。因此:

import numpy as np
arraylist = [np.array([1,2,3,4]), np.array([1,2,3,4]), np.array([1,3,2,4])]
L = {array.tostring(): array for array in arraylist}
L.values() # [array([1, 3, 2, 4]), array([1, 2, 3, 4])]

哦,看起来不错。我可以把键设为array.tolist()。谢谢! - SoItBegins
所以列表也不可哈希,但我认为我可以找到一些东西。 - SoItBegins
@SoItBegins listndarray可变的 -- 如果你试图定义一个 list 的哈希值,它需要基于该 list 内容的哈希值工作 -- 但如果内容可以改变,因为它是可变的,那么数组内容的哈希值可能会突然变得不正确。 - ely
你忽略了一个细节,那就是你需要将“writeable”标志设置为“False”——如果你想要改变这些数组的话(我认为这是使用它们的重要点),这是一个很大的问题。 - ely
@prpl.mnky.dshwshr 在所有的事情都说完并完成之后,我已经将其整理得更加清晰,并引入了字典推导式。如果还有任何意见或疑虑,请告诉我。(我也会删除多余的注释以使其更加清晰) - Joel
我唯一的进一步评论,与我在开头所说的相呼应,就是除非您始终可以保证在创建L和通过L.values将所有内容转储到list之间没有对L进行任何处理,否则这不安全可靠。在这些步骤之间,您可以访问和修改L [a.tostring()],以便该键的值与a的字符串化不同,例如,如果a是2-D:L [a.tostring()] [0,0] = np.NaN。我认为使用tuple的单行代码在这方面更安全一些(没有中间机会进行突变)。 - ely

5
根据数据结构的不同,直接比较所有数组可能会比找到一种哈希数组的方法更快。该算法的时间复杂度为O(n^2),但每个单独的比较都比创建字符串或Python数组的速度要快得多。因此,这取决于您需要检查多少个数组。
例如:
uniques = []
for arr in possible_duplicates:
    if not any(numpy.array_equal(arr, unique_arr) for unique_arr in uniques):
        uniques.append(arr)

2

以下是使用tuple的一种方法:

>>> import numpy as np
>>> t = [np.asarray([1, 2, 3, 4]), 
         np.asarray([1, 2, 3, 4]), 
         np.asarray([1, 1, 3, 4])]

>>> map(np.asarray, set(map(tuple, t)))
[array([1, 1, 3, 4]), array([1, 2, 3, 4])]

如果您的数组是多维的,那么首先将它们展平为一个1乘以任何数的数组,然后使用相同的思路,在最后重新整形它们:
def to_tuple(arr):
    return tuple(arr.reshape((arr.size,)))

def from_tuple(tup, original_shape):
    np.asarray(tup).reshape(original_shape)

例子:

In [64]: t = np.asarray([[[1,2,3],[4,5,6]],
                         [[1,1,3],[4,5,6]],
                         [[1,2,3],[4,5,6]]])

In [65]: map(lambda x: from_tuple(x, t[0].shape), set(map(to_tuple, t)))
Out[65]: 
[array([[1, 2, 3],
        [4, 5, 6]]), 
 array([[1, 1, 3],
        [4, 5, 6]])]

另一种选项是创建一个 pandas.DataFrame,将你的 ndarrays 转换为行(如果需要,通过重塑),并使用 pandas 内置的方法对行进行去重。
In [34]: t
Out[34]: [array([1, 2, 3, 4]), array([1, 2, 3, 4]), array([1, 1, 3, 4])]

In [35]: pandas.DataFrame(t).drop_duplicates().values
Out[35]: 
array([[1, 2, 3, 4],
       [1, 1, 3, 4]])

总体上,我认为尝试使用tostring()作为准哈希函数是个坏主意,因为你需要更多的样板代码来防止在某些dict中分配它们的“哈希”键之后,内容被改变的可能性。如果数据的重塑和转换成元组过程太慢,那么我的感觉是这暴露了一个更根本的问题:应用程序没有围绕需求(如去重)进行良好设计,并试图将其塞入运行在内存中的Python进程中,这可能不是正确的方式。此时,我会停下来考虑像Cassandra这样的东西,它可以轻松地在浮点(或其他)数据的大列(或多维数组)之上构建数据库索引,这可能是更明智的方法。

嗯,我尝试使用np.ndarray.tolist,但列表不可哈希。我会进一步研究它;我的数组是多维的,所以仅使用“元组”仍会导致子数组出错。 - SoItBegins
@SoItBegins 为什么要使用 tolist?直接使用我示例中的代码即可。我上面没有使用 tolist.. 我只是使用了普通的 tuple - ely
我无法使用元组,因为numpy数组是多维的。使用tuple()会创建一个包含较小numpy数组的元组,这将触发相同的错误。 - SoItBegins
你可以将它们重塑为一个长的1乘以任意数的数组,然后使用元组,在最后将它们重新塑造回原来的形状。 - ely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接