在numpy数组中检查并索引非唯一/重复值

时间:2014-08-12 12:51:58

标签: python arrays numpy unique

我有一个包含对象ID的数组traced_descIDs,我想确定哪些项在此数组中不唯一。然后,对于每个唯一的重复(仔细)ID,我需要确定traced_descIDs的哪些索引与之关联。

例如,如果我们在这里使用traced_descIDs,我希望发生以下过​​程:

traced_descIDs = [1, 345, 23, 345, 90, 1]
dupIds = [1, 345]
dupInds = [[0,5],[1,3]]

我目前正在查找哪些对象有多个条目:

mentions = np.array([len(np.argwhere( traced_descIDs == i)) for i in traced_descIDs])
dupMask = (mentions > 1)

然而,这需要很长时间,因为len( traced_descIDs )大约是150,000。有没有更快的方法来实现相同的结果?

任何帮助非常感谢。欢呼声。

6 个答案:

答案 0 :(得分:7)

虽然字典是O(n),但Python对象的开销有时会使得使用numpy的函数更加方便,这些函数使用排序并且是O(n * log n)。在您的情况下,起点是:

a = [1, 345, 23, 345, 90, 1]
unq, unq_idx, unq_cnt = np.unique(a, return_inverse=True, return_counts=True)

如果你使用的是早于1.9的numpy版本,那么最后一行必须是:

unq, unq_idx = np.unique(a, return_inverse=True)
unq_cnt = np.bincount(unq_idx)

我们创建的三个数组的内容是:

>>> unq
array([  1,  23,  90, 345])
>>> unq_idx
array([0, 3, 1, 3, 2, 0])
>>> unq_cnt
array([2, 1, 1, 2])

获取重复的项目:

cnt_mask = unq_cnt > 1
dup_ids = unq[cnt_mask]

>>> dup_ids
array([  1, 345])

获取索引更为复杂,但非常简单:

cnt_idx, = np.nonzero(cnt_mask)
idx_mask = np.in1d(unq_idx, cnt_idx)
idx_idx, = np.nonzero(idx_mask)
srt_idx = np.argsort(unq_idx[idx_mask])
dup_idx = np.split(idx_idx[srt_idx], np.cumsum(unq_cnt[cnt_mask])[:-1])

>>> dup_idx
[array([0, 5]), array([1, 3])]

答案 1 :(得分:5)

scipy.stats.itemfreq会给出每个项目的频率:

>>> xs = np.array([1, 345, 23, 345, 90, 1])
>>> ifreq = sp.stats.itemfreq(xs)
>>> ifreq
array([[  1,   2],
       [ 23,   1],
       [ 90,   1],
       [345,   2]])
>>> [(xs == w).nonzero()[0] for w in ifreq[ifreq[:,1] > 1, 0]]
[array([0, 5]), array([1, 3])]

答案 2 :(得分:2)

您当前的方法是O(N**2),请使用字典在O(N)时间内执行此操作:

>>> from collections import defaultdict
>>> traced_descIDs = [1, 345, 23, 345, 90, 1]
>>> d = defaultdict(list)
>>> for i, x in enumerate(traced_descIDs):
...     d[x].append(i)
...     
>>> for k, v in d.items():
...     if len(v) == 1:
...         del d[k]
...         
>>> d
defaultdict(<type 'list'>, {1: [0, 5], 345: [1, 3]})

获取物品和指数:

>>> from itertools import izip
>>> dupIds, dupInds = izip(*d.iteritems())
>>> dupIds, dupInds
((1, 345), ([0, 5], [1, 3]))

请注意,如果您想保留dupIds中项目的顺序,请使用collections.OrderedDictdict.setdefault()方法。

答案 3 :(得分:1)

td = np.array(traced_descIDs)
si = np.argsort(td)
td[si][np.append(False, np.diff(td[si]) == 0)]

这会给你:

array([  1, 345])

我还没有弄清楚第二部分,但也许这对你来说足够灵感,或者我可能会回到它。 :)

答案 4 :(得分:0)

Jaime提出的具有相同矢量化效率的解决方案嵌入numpy_indexed包中(免责声明:我是其作者):

import numpy_indexed as npi
print(npi.group_by(traced_descIDs, np.arange(len(traced_descIDs))))

这让我们大部分都在那里;但是如果我们还希望过滤掉单例组,同时避免任何python循环并保持完全向量化,我们可以降低一点,并且:

g = npi.group_by(traced_descIDs)
unique = g.unique
idx = g.split_array_as_list(np.arange(len(traced_descIDs)))
duplicates = unique[g.count>1]
idx_duplicates = np.asarray(idx)[g.count>1]
print(duplicates, idx_duplicates)

答案 5 :(得分:0)

np.unqiue for Ndims

我在ndArray上也遇到了类似的问题,我想在其中查找重复的行。

x = np.arange(60).reshape(5,4,3)
x[1] = x[0]

0和1应该在轴0上重复。我使用了np.unique并返回了所有选项。然后使用Jaime的方法查找重复项。

_,i,_,c = np.unique(x,1,1,1,axis=0)
x_dup = x[i[1<c]]

为清楚起见,我不必要使用return_inverse。结果如下:

>>> print(x_dupilates)
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]
  [ 9 10 11]]]