所有这里提出的方法都基于
numpy布尔索引。该方法是先识别匹配项(与行无关),然后沿着行使用缩减函数(
np.any
或
np.all
)来确定哪些行应该被消除,哪些应该被保留。最后,将此掩码应用于您的数组
A
,以仅获取有效行。这些方法之间唯一的区别在于如何创建掩码。
方法1:
如果事先已知B
的值,则通常使用|
(或运算符)链接比较。
a[~np.any(((a == 1) | (a == 2) | (a == 5)), axis=1)]
我会逐步进行:
Finding matches
>>> ((a == 1) | (a == 2) | (a == 5))
array([[ True, True],
[ True, False],
[False, False],
[False, True],
[False, False]], dtype=bool)
Check each row for one True
:
>>> np.any(((a == 1) | (a == 2) | (a == 5)), axis=1)
array([ True, True, False, True, False], dtype=bool)
Invert it:
>>> ~np.any(((a == 1) | (a == 2) | (a == 5)), axis=1)
array([False, False, True, False, True], dtype=bool)
Use boolean indexing:
>>> a[~np.any(((a == 1) | (a == 2) | (a == 5)), axis=1)]
array([[3, 4],
[6, 7]])
方法二:
不使用 a == 1 | a == 2 | ...
,你也可以使用 np.in1d
:
>>> np.in1d(a, [1, 2, 5]).reshape(a.shape)
array([[ True, True],
[ True, False],
[False, False],
[False, True],
[False, False]], dtype=bool)
然后使用与上述方法基本相同的方法。
>>> a[~np.any(np.in1d(a, [1, 2, 5]).reshape(a.shape), axis=1)]
array([[3, 4],
[6, 7]])
方法三:
如果b
已经排序,你也可以使用np.searchsorted
创建掩码:
>>> np.searchsorted([1, 2, 5], a, side='left') == np.searchsorted([1, 2, 5], a, side='right')
array([[False, False],
[False, True],
[ True, True],
[ True, False],
[ True, True]], dtype=bool)
这次您需要检查每一行的所有值是否都为“True”:
>>> b = [1, 2, 5]
>>> a[np.all(np.searchsorted(b, a, side='left') == np.searchsorted(b, a, side='right'), axis=1)]
array([[3, 4],
[6, 7]])
时间:
第一种方法并不适用于任意的 B
,因此我在这些时间中不包括它。
import numpy as np
def setapproach(A, B):
B = set(B)
indices_to_del = [i for i, sublist in enumerate(A) if B & set(sublist)]
C = np.delete(A, indices_to_del, 0)
return C
def setapproach2(A, B):
B = set(B)
return np.array([sublist for sublist in A if not B & set(sublist)])
def isinapproach(a, b):
return a[~np.any(np.in1d(a, b).reshape(a.shape), axis=1)]
def searchsortedapproach(a, b):
b.sort()
return a[np.all(np.searchsorted(b, a, side='left') == np.searchsorted(b, a, side='right'), axis=1)]
A = np.random.randint(0, 10000, (100000, 2))
B = np.random.randint(0, 10000, 2000)
%timeit setapproach(A, B)
%timeit setapproach2(A, B)
%timeit isinapproach(A, B)
%timeit searchsortedapproach(A, B)
然而,时间取决于数值范围,如果B
已经排序并且A
、B
的长度。但是numpy方法似乎比集合解决方案快近20倍。不过这个差别主要是因为使用python循环遍历numpy数组非常低效,所以我会先将A
和B
转换为list
:
def setapproach_updated(A, B):
B = set(B)
indices_to_del = [i for i, sublist in enumerate(A.tolist()) if B & set(sublist)]
C = np.delete(A, indices_to_del, 0)
return C
def setapproach2_updated(A, B):
B = set(B)
return np.array([sublist for sublist in A.tolist() if not B & set(sublist)])
这可能看起来很奇怪,但让我们重新做一下时间安排:
%timeit setapproach_updated(A, B)
%timeit setapproach2_updated(A, B)
这比普通循环快得多,只需先用
tolist
转换即可,但仍然比NumPy方法慢5倍以上。
因此请记住:
当您必须在NumPy数组上使用基于Python的方法时,请检查是否将其转换为列表更快!
让我们看看如何在更大的数组上执行(这些大小与您问题中提到的大小相近):
A = np.random.randint(0, 10000000, (1500000, 2))
B = np.random.randint(0, 10000000, 50000)
%timeit setapproach_updated(A, B)
%timeit setapproach2_updated(A, B)
%timeit isinapproach(A, B)
%timeit searchsortedapproach(A, B)
差异变小了,而且“searchsorted”方法明显更胜一筹。
第四种方法:
我还没有完成!让我用
numba给你一个惊喜,它不是一个轻量级的包,但是如果它支持你需要的类型和函数,它非常强大。
import numba as nb
@nb.njit
def numba_approach(A, B):
Bset = set(B)
mask = np.ones(A.shape[0], dtype=nb.bool_)
for idx in range(A.shape[0]):
for item in A[idx]:
if item in Bset:
mask[idx] = False
break
return A[mask]
让我们看看它的表现:
A = np.random.randint(0, 10000, (100000, 2))
B = np.random.randint(0, 10000, 2000)
numba_approach(A, B)
%timeit numba_approach(A, B)
A = np.random.randint(0, 10000000, (1500000, 2))
B = np.random.randint(0, 10000000, 50000)
%timeit numba_approach(A, B)
因此,您可以使它快上另一个数量级。Numba不支持所有的Python / Numpy功能(并非所有功能都更快),但在这种情况下足够了!
a
的子列表有多大?例如,len(a[123])
是多少?它比b
大还是小? - Ma0b = [1, 2, 5]
吗? - Ma0