从NumPy数组中删除出现次数大于一次的元素

3
问题是,如何完全删除数组中出现超过一次的元素。下面是一种处理大型数组时非常缓慢的方法。有没有用numpy解决这个问题的想法?提前感谢。
import numpy as np

count = 0
result = []
input = np.array([[1,1], [1,1], [2,3], [4,5], [1,1]]) # array with points [x, y]

# count appearance of elements with same x and y coordinate
# append to result if element appears just once

for i in input:
    for j in input:
        if (j[0] == i [0]) and (j[1] == i[1]):
            count += 1
    if count == 1:
        result.append(i)
    count = 0

print np.array(result)

更新:由于之前过于简化

再次明确一下:如何从数组/列表中删除出现多次的特定属性的元素?这里是一个长度为6的元素列表,如果每个元素的第一个和第二个条目都在列表中出现了多次,则从列表中删除所有相关元素。希望我没有太让人困惑。Eumiro对此帮助了我很多,但我无法像应该那样展平输出列表:(

import numpy as np 
import collections

input = [[1,1,3,5,6,6],[1,1,4,4,5,6],[1,3,4,5,6,7],[3,4,6,7,7,6],[1,1,4,6,88,7],[3,3,3,3,3,3],[456,6,5,343,435,5]]

# here, from input there should be removed input[0], input[1] and input[4] because
# first and second entry appears more than once in the list, got it? :)

d = {}

for a in input:
    d.setdefault(tuple(a[:2]), []).append(a[2:])

outputDict = [list(k)+list(v) for k,v in d.iteritems() if len(v) == 1 ]

result = []

def flatten(x):
    if isinstance(x, collections.Iterable):
        return [a for i in x for a in flatten(i)]
    else:
        return [x]

# I took flatten(x) from https://dev59.com/THI95IYBdhLWcg3wyBCc#2158522
# And I need it, because output is a nested list :(

for i in outputDict:
    result.append(flatten(i))

print np.array(result)

所以,这个方法可以工作,但是在处理大列表时不实用。 首先我遇到了 RuntimeError: maximum recursion depth exceeded in cmp 然后应用了 sys.setrecursionlimit(10000) 之后我又遇到了 Segmentation fault 我该如何在处理大于100000个元素的大列表时实现Eumiros的解决方案?

4个回答

3
np.array(list(set(map(tuple, input))))

返回值

array([[4, 5],
       [2, 3],
       [1, 1]])

更新1:如果您想要删除[1, 1](因为它出现了多次),您可以执行以下操作:

from collections import Counter

np.array([k for k, v in Counter(map(tuple, input)).iteritems() if v == 1])

返回值

array([[4, 5],
       [2, 3]])

更新2: 使用input=[[1,1,2], [1,1,3], [2,3,4], [4,5,5], [1,1,7]]

input=[[1,1,2], [1,1,3], [2,3,4], [4,5,5], [1,1,7]]

d = {}
for a in input:
    d.setdefault(tuple(a[:2]), []).append(a[2])

d现在是:

{(1, 1): [2, 3, 7],
 (2, 3): [4],
 (4, 5): [5]}

因此,我们希望获取所有仅具有单个值的键值对,并重新创建数组:

np.array([k+tuple(v) for k,v in d.iteritems() if len(v) == 1])

返回:

array([[4, 5, 5],
       [2, 3, 4]])

更新 3:对于较大的数组,您可以将我的先前解决方案调整为:

import numpy as np
input = [[1,1,3,5,6,6],[1,1,4,4,5,6],[1,3,4,5,6,7],[3,4,6,7,7,6],[1,1,4,6,88,7],[3,3,3,3,3,3],[456,6,5,343,435,5]]
d = {}
for a in input:
    d.setdefault(tuple(a[:2]), []).append(a)
np.array([v for v in d.itervalues() if len(v) == 1])

返回值:

array([[[456,   6,   5, 343, 435,   5]],
       [[  1,   3,   4,   5,   6,   7]],
       [[  3,   4,   6,   7,   7,   6]],
       [[  3,   3,   3,   3,   3,   3]]])

谢谢。我的问题是(除了理解您使用的函数之外:),我想删除所有元素,例如此示例中的所有[1,1]。这是因为在我的实际问题中,已知的不仅是x和y坐标,而且我不想冒险选择错误的点,因此我决定不留下任何重复项。希望这不会太令人困惑 :) - feinmann
@feinmann - 请查看我的更新答案。所有的库都是标准库,因此您可以在Python文档中找到不同函数的解释。 - eumiro
非常感谢您,eumiro。干得好!但是我恐怕过于简化了我的问题。如果有第三个组件(实际上有六个组件),不应影响“删除决策”,您能想到解决方案吗?例如:input=[[1,1,2], [1,1,3], [2,3,4], [4,5,5], [1,1,7]] 应该得到结果[[2,3,4],[4,5,5]]。希望您不要生我的气 :) - feinmann
对于输入 = [[1,1,3,5,6,6],[1,1,4,4,5,6],[1,3,4,5,6,7],[3,4,6,7,7,6],[1,1,4,6,88,7],[3,3,3,3,3,3],[456,6,5,343,435,5]] 我得到的结果是=[[456, 6, [5, 343, 435, 5]], [1, 3, [4, 5, 6, 7]], [3, 4, [6, 7, 7, 6]], [3, 3, [3, 3, 3, 3]]],这太糟糕了。我不能去掉内部括号 :(( 对不起eumiro,你能再帮我一次吗?非常感谢.... - feinmann
第一个表达式可以缩短为np.unique(map(tuple, input)) - Fred Foo

3
这是对Hooked答案的更正和加速版本。`count_unique`函数计算出键集合中每个唯一键的出现次数。
import numpy as np
input = np.array([[1,1,3,5,6,6],
                  [1,1,4,4,5,6],
                  [1,3,4,5,6,7],
                  [3,4,6,7,7,6],
                  [1,1,4,6,88,7],
                  [3,3,3,3,3,3],
                  [456,6,5,343,435,5]])

def count_unique(keys):
    """Finds an index to each unique key (row) in keys and counts the number of
    occurrences for each key"""
    order = np.lexsort(keys.T)
    keys = keys[order]
    diff = np.ones(len(keys)+1, 'bool')
    diff[1:-1] = (keys[1:] != keys[:-1]).any(-1)
    count = np.where(diff)[0]
    count = count[1:] - count[:-1]
    ind = order[diff[1:]]
    return ind, count

key = input[:, :2]
ind, count = count_unique(key)
print key[ind]
#[[  1   1]
# [  1   3]
# [  3   3]
# [  3   4]
# [456   6]]
print count
[3 1 1 1 1]

ind = ind[count == 1]
output = input[ind]
print output
#[[  1   3   4   5   6   7]
# [  3   3   3   3   3   3]
# [  3   4   6   7   7   6]
# [456   6   5 343 435   5]]

1
谢谢你提醒我lexsort,当你的答案得到改进时,学到新东西总是很好的! - Hooked

1

更新的解决方案:

根据下面的评论,新的解决方案是:

idx = argsort(A[:, 0:2], axis=0)[:,1]
kidx = where(sum(A[idx,:][:-1,0:2]!=A[idx,:][1:,0:2], axis=1)==0)[0]
kidx = unique(concatenate((kidx,kidx+1)))

for n in arange(0,A.shape[0],1):
    if n not in kidx:
        print A[idx,:][n]

 > [1 3 4 5 6 7]
   [3 3 3 3 3 3]
   [3 4 6 7 7 6]
   [456   6   5 343 435   5]

kidx 是一个元素索引列表,其中包含您不想要的元素。这将保留第一和第二个内部元素与任何其他内部元素不匹配的行。由于所有操作都是通过索引完成的,因此速度应该相对较快,尽管需要对前两个元素进行排序。请注意,原始行顺序未被保留,但我认为这不是问题。

旧解决方案:

如果我理解正确,您只需过滤掉内部列表的每个第一个元素等于第二个元素的结果。

使用您在更新中提供的输入A=[[1,1,3,5,6,6],[1,1,4,4,5,6],[1,3,4,5,6,7],[3,4,6,7,7,6],[1,1,4,6,88,7],[3,3,3,3,3,3],[456,6,5,343,435,5]],以下行会删除A[0]A[1]A[4]A[5]也将被删除,因为它似乎符合您的条件。

[x for x in A if x[0]!=x[1]]

如果您可以使用numpy,那么有一种非常流畅的方法来完成上述操作。假设A是一个数组,那么:
A[A[0,:] == A[1,:]]

将提取相同的值。 如果要循环遍历它,则这可能比上面列出的解决方案更快。


抱歉,很遗憾你误解了我的意思。我的意思不是第一个是否等于第二个。恐怕我的例子满足这个条件。如果列表中的第一个和第二个条目出现超过一次(但恰好是这种组合),则应删除元素...所以有一个列表=[[x1,x2,x3,x4,x5,x6],[y1,y2,y3,y4,y5,y6],...]。如果x1==y1 AND x2==y2,则同时删除元素。 - feinmann
@feinmann,我认为这里的混淆在于有两个列表,一个外层和一个内层,我们把它们搞混了。为了明确,如果每个外层列表元素的前两个内层元素与任何其他元素匹配,则它们将被删除?如果是这样,我会相应地编辑我的答案。 - Hooked
你说得完全正确。如果每个外部元素的前两个内部元素以这种组合方式在整个列表中出现超过一次,则删除所有相关的外部元素。非常感谢您的帮助。我尝试自己计算出现次数,并仅获取那些在列表中只出现一次的外部元素(关于它们的前两个内部元素)...但速度太慢了,无法接受 :( - feinmann
@Hooked,这很接近了,但是argsort在这里并没有做你想要的事情。它独立地对每一列进行排序,然后你忽略了第一列并取了第二列。你需要更像lexsort的东西。 - Bi Rico

-2
为什么不创建另一个数组来保存输出呢?
遍历主列表,对于每个,检查它是否在另一个数组中,如果不在,则将其附加到该数组中。
这样,您的新数组将不包含多个相同的元素。

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