繁体   English   中英

基于第三个列表过滤列表列表的最快方法?

[英]Fastest way to filter lists of lists based on a third list?

我有一个列表A类似如下:

A = np.array([[1,2] ,
              [2,4] ,
              [3,4] , 
              [4,5] , 
              [6,7]]) 

我需要删除包含第三个列表B中任何元素的所有子列表。

所以,例如:

B = [1,2,5]

预期结果将是:

np.array([[3,4] ,
          [6,7]]) 

A的长度高达1,500,000,B也经常出现在数万个元素中,因此性能至关重要。 A的子列表长度始终为2。

此处介绍的所有方法都基于numpys布尔索引 方法是识别匹配(独立于行),然后沿着行使用缩减( np.anynp.all )来查看应该删除哪些行以及应该保留哪些行。 最后,此掩码将应用于阵列A以仅获取有效行。 这些方法之间唯一真正的区别在于如何创建蒙版。

方法1:

如果事先知道B的值,则通常使用| (或运营商)链式比较。

a[~np.any(((a == 1) | (a == 2) | (a == 5)), axis=1)]

我会逐步完成这个步骤:

  1. 寻找比赛

     >>> ((a == 1) | (a == 2) | (a == 5)) array([[ True, True], [ True, False], [False, False], [False, True], [False, False]], dtype=bool) 
  2. 检查每一行是否为True

     >>> np.any(((a == 1) | (a == 2) | (a == 5)), axis=1) array([ True, True, False, True, False], dtype=bool) 
  3. 反转它:

     >>> ~np.any(((a == 1) | (a == 2) | (a == 5)), axis=1) array([False, False, True, False, True], dtype=bool) 
  4. 使用布尔索引:

     >>> a[~np.any(((a == 1) | (a == 2) | (a == 5)), axis=1)] array([[3, 4], [6, 7]]) 

方法2:

而不是这些a == 1 | a == 2 | ... a == 1 | a == 2 | ... 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]])

方法3:

如果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)

这次你需要检查到达行中的all值是否为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):  # author: Max Chrétien
    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):  # author: Max Chrétien & Ev. Kounis
    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)
# 929 ms ± 16.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit setapproach2(A, B)
# 1.04 s ± 13.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit isinapproach(A, B)
# 59.1 ms ± 1.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit searchsortedapproach(A, B)
# 56.1 ms ± 1.05 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

然而,如果B已经被排序并且AB的长度,则时间取决于值的范围。 但是numpy接近接缝的速度几乎是设定解决方案的20倍。 然而,差异主要是因为使用python循环对numpy-arrays进行迭代的效率非常低,所以我首先将AB转换为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)
# 300 ms ± 2.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit setapproach2_updated(A, B)
# 378 ms ± 10.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

这比普通循环要快得多,只需tolist其转换为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)
# 4.14 s ± 66.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit setapproach2_updated(A, B)
# 6.33 s ± 95.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit isinapproach(A, B)
# 2.39 s ± 102 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit searchsortedapproach(A, B)
# 1.34 s ± 21.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

差异变得越来越小, searchsorted排序 - searchsorted肯定胜利。

方法4:

我还没完呢! 让我用让你大吃一惊,它不是一个轻量级的包, 但是 如果它支持你需要的类型和功能,那就非常强大:

import numba as nb

@nb.njit                # the magic is this decorator
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)   # numba needs a warmup run because it's just-in-time compiling

%timeit numba_approach(A, B)
# 6.12 ms ± 145 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# This is 10 times faster than the fastest other approach!

A = np.random.randint(0, 10000000, (1500000, 2))
B = np.random.randint(0, 10000000, 50000)

%timeit numba_approach(A, B)
# 286 ms ± 16.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# This is still 4 times faster than the fastest other approach!

所以,你可以让它快一个数量级。 Numba不支持所有python / numpy功能(并不是所有功能都更快)但在这种情况下它足够了!

使用set -intersection重新创建一个新的索引列表,其中[1, 2, 5] 1,2,5 [1, 2, 5]在您的子列表中。 然后使用要删除的索引列表,使用集成在numpy中的np.delete()函数。

import numpy as np

A = np.array([[1,2],
              [2,4],
              [3,4],
              [4,5],
              [6,7]])

B = set([1, 2, 5])

indices_to_del = [i for i, sublist in enumerate(A) if B & set(sublist)]

C = np.delete(A, indices_to_del, 0)

print C
#[[3 4]
# [6 7]]

编辑

感谢@MSeifert我能够改进我的答案。

@ Ev.Kounis提出了另一个类似但更快的解决方案:

D = np.array([sublist for sublist in A if not B & set(sublist)])

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM