簡體   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