在NumPy数组中删除重复行

13

我有一个形状为(N,3)的numpy值数组:

>>> vals = numpy.array([[1,2,3],[4,5,6],[7,8,7],[0,4,5],[2,2,1],[0,0,0],[5,4,3]])
>>> vals
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 7],
       [0, 4, 5],
       [2, 2, 1],
       [0, 0, 0],
       [5, 4, 3]])

我想要从数组中删除具有重复值的行。例如,上述数组的结果应为:

>>> duplicates_removed
array([[1, 2, 3],
       [4, 5, 6],
       [0, 4, 5],
       [5, 4, 3]])

我不确定如何在numpy中高效地完成这个任务而不使用循环(数组可能非常大)。有人知道我怎么做吗?


“没有循环”是什么意思?你必须检查数组中的每个项目,所以无论你使用什么技巧来隐藏循环,时间复杂度都是O(m*n)。 - agf
1
我认为他的意思是在Numpy中循环而不是在Python中循环。在编译后的Numpy函数中的O(mn)比Python的for循环中的O(mn)要快得多。当选项是编译代码和解释代码时,常量很重要。 - Jim Pivarski
从您的评论中可以看出,由于您希望将其推广以处理通用列数,因此您可能会发现这个问题的解决方案值得一读。请参考此解决方案 - Divakar
5个回答

11

这是一个选项:

import numpy
vals = numpy.array([[1,2,3],[4,5,6],[7,8,7],[0,4,5],[2,2,1],[0,0,0],[5,4,3]])
a = (vals[:,0] == vals[:,1]) | (vals[:,1] == vals[:,2]) | (vals[:,0] == vals[:,2])
vals = numpy.delete(vals, numpy.where(a), axis=0)

我正在尝试解决这个问题,做得好。但是你不需要使用^,而是需要使用|吧? - Ned Batchelder
1
这比列表推导方法快得多,所以我可能会接受。不知道是否有办法推广到NxM呢? - jterrace
@Ned Batchelder:是的,尽管在这种情况下它并不会改变任何事情。 - Benjamin
1
@jterrace 你可以通过生成0-m的组合,使用它们在生成器表达式中进行比较,然后通过|缩减来获得a - agf

3

以下是一种处理通用列数并仍然是向量化方法的方法 -

def rows_uniq_elems(a):
    a_sorted = np.sort(a,axis=-1)
    return a[(a_sorted[...,1:] != a_sorted[...,:-1]).all(-1)]

步骤:

  • 沿每一行进行排序。

  • 查找每一行中相邻元素之间的差异。因此,任何具有至少一个零差异的行都表示有重复元素。我们将使用这个方法来获取有效行的掩码。因此,最后一步是仅仅使用掩码从输入数组中选择有效行。

示例运行 -

In [49]: a
Out[49]: 
array([[1, 2, 3, 7],
       [4, 5, 6, 7],
       [7, 8, 7, 8],
       [0, 4, 5, 6],
       [2, 2, 1, 1],
       [0, 0, 0, 3],
       [5, 4, 3, 2]])

In [50]: rows_uniq_elems(a)
Out[50]: 
array([[1, 2, 3, 7],
       [4, 5, 6, 7],
       [0, 4, 5, 6],
       [5, 4, 3, 2]])

有趣的是,np.sort(a)等同于a[np.arange(idx.shape [0])[:, None], idx]吗? - Josmoor98
1
@EBB 不确定为什么我要走那么迂回的方式。已经更新了排序。感谢您的建议! - Divakar
太好了,谢谢!我刚好在你上传答案的时候再次阅读它!太神奇了!在切片操作中,...:是一样的吗?我以前没有见过这种实现方式?我还想知道使用axis=-1axis=1是否有区别?对于我的问题,这两个操作返回相同的答案?在你的解决方案中选择axis=-1有具体的原因吗?感谢你的帮助! - Josmoor98
1
@EBB 这只是更通用的,因为它可以处理任何通用维度数组来移除行。因此,任何2D、3D等数组现在都可以使用。 - Divakar

2

虽然已经过去了六年,但这个问题对我很有帮助,所以我进行了速度比较,比较的对象是Divakar、Benjamin、Marcelo Cantos和Curtis Patrick给出的答案。

import numpy as np
vals = np.array([[1,2,3],[4,5,6],[7,8,7],[0,4,5],[2,2,1],[0,0,0],[5,4,3]])

def rows_uniq_elems1(a):
    idx = a.argsort(1)
    a_sorted = a[np.arange(idx.shape[0])[:,None], idx]
    return a[(a_sorted[:,1:] != a_sorted[:,:-1]).all(-1)]

def rows_uniq_elems2(a):
    a = (a[:,0] == a[:,1]) | (a[:,1] == a[:,2]) | (a[:,0] == a[:,2])
    return np.delete(a, np.where(a), axis=0)

def rows_uniq_elems3(a):
    return np.array([v for v in a if len(set(v)) == len(v)])

def rows_uniq_elems4(a):
    return np.array([v for v in a if len(np.unique(v)) == len(v)])

结果:

%timeit rows_uniq_elems1(vals)
10000 loops, best of 3: 67.9 µs per loop

%timeit rows_uniq_elems2(vals)
10000 loops, best of 3: 156 µs per loop

%timeit rows_uniq_elems3(vals)
1000 loops, best of 3: 59.5 µs per loop

%timeit rows_uniq_elems(vals)
10000 loops, best of 3: 268 µs per loop

看起来使用setnumpy.unique更好。在我的情况下,我需要对一个更大的数组执行此操作:

bigvals = np.random.randint(0,10,3000).reshape([3,1000])

%timeit rows_uniq_elems1(bigvals)
10000 loops, best of 3: 276 µs per loop

%timeit rows_uniq_elems2(bigvals)
10000 loops, best of 3: 192 µs per loop

%timeit rows_uniq_elems3(bigvals)
10000 loops, best of 3: 6.5 ms per loop

%timeit rows_uniq_elems4(bigvals)
10000 loops, best of 3: 35.7 ms per loop

没有列表推导的方法更快。然而,行数是硬编码的,而且很难扩展到超过三列,所以在我的情况下,带有set的列表推导式是最好的答案。

进行了编辑,因为我在bigvals中混淆了行和列。


2
numpy.array([v for v in vals if len(set(v)) == len(v)])

请注意,这仍然在幕后循环。你无法避免这一点。但是,即使有数百万行数据,它也应该能正常工作。


我想到了[item for item in vals if Counter(item).most_common(1)[0][1] is 1],但这个更好,特别是你已经知道len(v)。不过你仍然在“循环”,因为你正在遍历数组。 - agf
这对于一个大数组来说实际上非常快,但是我需要重复项的索引位置,所以我喜欢@Benjamin的解决方案。 - jterrace

1

和Marcelo一样,但我认为使用numpy.unique()而不是set()可能会更准确地达到你的目标。

numpy.array([v for v in vals if len(numpy.unique(v)) == len(v)])

嗯,set也能传达相同的意图,但是numpy.unique更快吗,也许? - Marcelo Cantos
在我的机器上,使用100万行数据,numpy.unique()的速度似乎要慢得多——需要23秒,而set()只需要3秒。 - jterrace

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