从一个数组中移除另一个数组中存在的元素

40

假设我有两个二维数组 AB

如何从 A 中删除在 B 中的元素。(在集合论中称为补集:A-B)

A=np.asarray([[1,1,1], [1,1,2], [1,1,3], [1,1,4]])
B=np.asarray([[0,0,0], [1,0,2], [1,0,3], [1,0,4], [1,1,0], [1,1,1], [1,1,4]])
#output = [[1,1,2], [1,1,3]]
更确切地说,我想做这样的事情。
data = some numpy array
label = some numpy array
A = np.argwhere(label==0) #[[1 1 1], [1 1 2], [1 1 3], [1 1 4]]
B = np.argwhere(data>1.5) #[[0 0 0], [1 0 2], [1 0 3], [1 0 4], [1 1 0], [1 1 1], [1 1 4]]
out = np.argwhere(label==0 and data>1.5) #[[1 1 2], [1 1 3]]

"didn't == will work, I am just guessing, i don't know much about numpy arrays, from my python console i got this `>>>[1,1,1]==[1,1,1]
True`"
"这个代码可能有效,但我只是猜测,对于numpy数组我不是很了解。从我的Python控制台中得到的结果是: `>>>[1,1,1]==[1,1,1]
True`"
- Lokesh Sanapalli
一个简单的非numpy解决方案 - [i for i in A for j in B if i==j] - JRodDynamite
5个回答

36

使用列表推导式可以轻松解决问题。

A = [i for i in A if i not in B]

结果

[[1, 1, 2], [1, 1, 3]]

列表推导式并不会从数组中删除元素,它只是重新分配 - 如果你想要删除元素,请使用这种方法:

for i in B:
     if i in A:
     A.remove(i)

18

基于this solution解决在NumPy数组中查找多个值的行索引的问题,这里提供了一个使用NumPy实现的解决方案,具有较小的内存占用并且在处理大型数组时可能会更加高效。

dims = np.maximum(B.max(0),A.max(0))+1
out = A[~np.in1d(np.ravel_multi_index(A.T,dims),np.ravel_multi_index(B.T,dims))]

示例运行 -

In [38]: A
Out[38]: 
array([[1, 1, 1],
       [1, 1, 2],
       [1, 1, 3],
       [1, 1, 4]])

In [39]: B
Out[39]: 
array([[0, 0, 0],
       [1, 0, 2],
       [1, 0, 3],
       [1, 0, 4],
       [1, 1, 0],
       [1, 1, 1],
       [1, 1, 4]])

In [40]: out
Out[40]: 
array([[1, 1, 2],
       [1, 1, 3]])

大数组运行时测试 -

In [107]: def in1d_approach(A,B):
     ...:     dims = np.maximum(B.max(0),A.max(0))+1
     ...:     return A[~np.in1d(np.ravel_multi_index(A.T,dims),\
     ...:                     np.ravel_multi_index(B.T,dims))]
     ...: 

In [108]: # Setup arrays with B as large array and A contains some of B's rows
     ...: B = np.random.randint(0,9,(1000,3))
     ...: A = np.random.randint(0,9,(100,3))
     ...: A_idx = np.random.choice(np.arange(A.shape[0]),size=10,replace=0)
     ...: B_idx = np.random.choice(np.arange(B.shape[0]),size=10,replace=0)
     ...: A[A_idx] = B[B_idx]
     ...: 

基于广播的解决方案中的时间安排 -

In [109]: %timeit A[np.all(np.any((A-B[:, None]), axis=2), axis=0)]
100 loops, best of 3: 4.64 ms per loop # @Kasramvd's soln

In [110]: %timeit A[~((A[:,None,:] == B).all(-1)).any(1)]
100 loops, best of 3: 3.66 ms per loop

使用占用更少内存的时间解决方案 -

In [111]: %timeit in1d_approach(A,B)
1000 loops, best of 3: 231 µs per loop

性能进一步提升

in1d_approach通过将每行视为索引元组来减少每行的操作。我们可以通过使用np.dot进行矩阵乘法实现更高效的操作,如下所示 -

def in1d_dot_approach(A,B):
    cumdims = (np.maximum(A.max(),B.max())+1)**np.arange(B.shape[1])
    return A[~np.in1d(A.dot(cumdims),B.dot(cumdims))]

让我们对比一下更大的数组进行测试 -

In [251]: # Setup arrays with B as large array and A contains some of B's rows
     ...: B = np.random.randint(0,9,(10000,3))
     ...: A = np.random.randint(0,9,(1000,3))
     ...: A_idx = np.random.choice(np.arange(A.shape[0]),size=10,replace=0)
     ...: B_idx = np.random.choice(np.arange(B.shape[0]),size=10,replace=0)
     ...: A[A_idx] = B[B_idx]
     ...: 

In [252]: %timeit in1d_approach(A,B)
1000 loops, best of 3: 1.28 ms per loop

In [253]: %timeit in1d_dot_approach(A, B)
1000 loops, best of 3: 1.2 ms per loop

你的in1d_approach函数在我的程序中需要30秒,而in1d_dot_approach则需要45秒。我的numpy数组使用dtype=np.uint8。因此,我使用你的确切代码测试了A、B参数的dtype=np.uint8版本。点积函数只需567纳秒,而原始函数却只需539纳秒。为什么更小的数据类型会让原来的函数的时间更短呢? - Jee Seok Yoon
1
@JeeSeokYoon,之所以使用精度较低的数据类型是因为它们在二进制位方面占用更少的内存,因此会产生更少的内存占用,并且在大多数情况下,这会转化为更快的处理速度,因为它处理的数据量更少,因为每个数字使用的二进制位数量更少。你必须记住,在最低级别上,CPU 处理二进制数据。希望这有意义! - Divakar
我想问一下,为什么在处理浮点数时in1d_approach(A,B)比in1d_dot_approach(A, B)慢,但在处理整数时却更快?这是因为numpy的构建方式吗?为什么矩阵乘法在处理浮点数时表现更好/在处理整数时表现更差(与其他方法相比)? - Jee Seok Yoon
请注意可能会发生 dims = np.maximum(B.max(0),A.max(0))+1 溢出的情况。在进行 +1 加法运算之前,您可能需要将 dims 数组转换为更长的 dtype 类型。 - SzieberthAdam

14

这里是使用Numpythonic方法和广播的方式:

In [83]: A[np.all(np.any((A-B[:, None]), axis=2), axis=0)]
Out[83]: 
array([[1, 1, 2],
       [1, 1, 3]])

这是一个与其他答案相关的性能测试:

In [90]: def cal_diff(A, B):
   ....:     A_rows = A.view([('', A.dtype)] * A.shape[1])
   ....:     B_rows = B.view([('', B.dtype)] * B.shape[1])
   ....:     return np.setdiff1d(A_rows, B_rows).view(A.dtype).reshape(-1, A.shape[1])
   ....: 

In [93]: %timeit cal_diff(A, B)
10000 loops, best of 3: 54.1 µs per loop

In [94]: %timeit A[np.all(np.any((A-B[:, None]), axis=2), axis=0)]
100000 loops, best of 3: 9.41 µs per loop

# Even better with Divakar's suggestion
In [97]: %timeit A[~((A[:,None,:] == B).all(-1)).any(1)]
100000 loops, best of 3: 7.41 µs per loop

如果你想要更快的方法,你应该寻找能减少比较次数的方法。在这种情况下(不考虑顺序),你可以从你的行中生成一个唯一的数字,并比较这些数字,这可以通过将项目的平方求和来完成。

这是使用Divakar的in1d方法的基准测试:

In [144]: def in1d_approach(A,B):
   .....:         dims = np.maximum(B.max(0),A.max(0))+1
   .....:         return A[~np.in1d(np.ravel_multi_index(A.T,dims),\
   .....:                          np.ravel_multi_index(B.T,dims))]
   .....: 

In [146]: %timeit in1d_approach(A, B)
10000 loops, best of 3: 23.8 µs per loop

In [145]: %timeit A[~np.in1d(np.power(A, 2).sum(1), np.power(B, 2).sum(1))]
10000 loops, best of 3: 20.2 µs per loop

你可以使用np.diff来获得一个与顺序无关的结果:

In [194]: B=np.array([[0, 0, 0,], [1, 0, 2,], [1, 0, 3,], [1, 0, 4,], [1, 1, 0,], [1, 1, 1,], [1, 1, 4,], [4, 1, 1]])

In [195]: A[~np.in1d(np.diff(np.diff(np.power(A, 2))), np.diff(np.diff(np.power(B, 2))))]
Out[195]: 
array([[1, 1, 2],
       [1, 1, 3]])

In [196]: %timeit A[~np.in1d(np.diff(np.diff(np.power(A, 2))), np.diff(np.diff(np.power(B, 2))))]
10000 loops, best of 3: 30.7 µs per loop

使用Divakar的设置进行基准测试:

In [198]: B = np.random.randint(0,9,(1000,3))

In [199]: A = np.random.randint(0,9,(100,3))

In [200]: A_idx = np.random.choice(np.arange(A.shape[0]),size=10,replace=0)

In [201]: B_idx = np.random.choice(np.arange(B.shape[0]),size=10,replace=0)

In [202]: A[A_idx] = B[B_idx]

In [203]: %timeit A[~np.in1d(np.diff(np.diff(np.power(A, 2))), np.diff(np.diff(np.power(B, 2))))]
10000 loops, best of 3: 137 µs per loop

In [204]: %timeit A[~np.in1d(np.power(A, 2).sum(1), np.power(B, 2).sum(1))]
10000 loops, best of 3: 112 µs per loop

In [205]: %timeit in1d_approach(A, B)
10000 loops, best of 3: 115 µs per loop

使用更大的数组进行计时(Divakar的解决方案略微更快):

In [231]: %timeit A[~np.in1d(np.diff(np.diff(np.power(A, 2))), np.diff(np.diff(np.power(B, 2))))]
1000 loops, best of 3: 1.01 ms per loop

In [232]: %timeit A[~np.in1d(np.power(A, 2).sum(1), np.power(B, 2).sum(1))]
1000 loops, best of 3: 880 µs per loop

In [233]:  %timeit in1d_approach(A, B)
1000 loops, best of 3: 807 µs per loop

1
不错!我也正想发同样的帖子! - Divakar
2
实际上,在性能方面使用equality可能更好:A [~((A [:,None,:] == B).all(-1)).any(1)] - Divakar
很棒的解决方案 :) - Rahul K P
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Divakar
哦,等等,如果我翻转一行,新方法会给出错误的答案。所以,在设置输入数组后,执行以下操作:A[1] = B[5][::-1],然后运行它。 - Divakar
显示剩余4条评论

8
如果你想用numpy的方式实现,
import numpy as np

A = np.array([[1, 1, 1,], [1, 1, 2], [1, 1, 3], [1, 1, 4]])
B = np.array([[0, 0, 0], [1, 0, 2], [1, 0, 3], [1, 0, 4], [1, 1, 0], [1, 1, 1], [1, 1, 4]])
A_rows = A.view([('', A.dtype)] * A.shape[1])
B_rows = B.view([('', B.dtype)] * B.shape[1])

diff_array = np.setdiff1d(A_rows, B_rows).view(A.dtype).reshape(-1, A.shape[1])

如@Rahul建议的,对于一个非numpy的简单方案,请按以下方式操作:
diff_array = [i for i in A if i not in B]

1
谢谢提醒。已更新。 - R. S. Nikhil Krishna

4

另一种非NumPy解决方案:

[i for i in A if i not in B]

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