簡體   English   中英

獲取每列2d數組中最后一個負值的索引

[英]get the index of the last negative value in a 2d array per column

我正在嘗試獲取每列數組的最后一個負值的索引(以便在之后對其進行切片)。 1d向量上的一個簡單的工作示例是:

import numpy as np

A = np.arange(10) - 5
A[2] = 2
print A # [-5 -4  2 -2 -1  0  1  2  3  4]

idx = np.max(np.where(A <= 0)[0])
print idx # 5

A[:idx] = 0
print A # [0 0 0 0 0 0 1 2 3 4]

現在我想在2D數組的每一列上做同樣的事情:

A = np.arange(10) - 5
A[2] = 2
A2 = np.tile(A, 3).reshape((3, 10)) - np.array([0, 2, -1]).reshape((3, 1))
print A2
# [[-5 -4  2 -2 -1  0  1  2  3  4]
#  [-7 -6  0 -4 -3 -2 -1  0  1  2]
#  [-4 -3  3 -1  0  1  2  3  4  5]]

我想獲得:

print A2
# [[0 0 0 0 0 0 1 2 3 4]
#  [0 0 0 0 0 0 0 0 1 2]
#  [0 0 0 0 0 1 2 3 4 5]]

但我無法弄清楚如何將max / where語句轉換為這個2d數組......

您已經有了很好的答案,但我想使用函數np.maximum.accumulate建議一個更快的變化。 由於您的1D陣列方法使用max / where ,您可能也會發現這種方法非常直觀。 編輯:下面添加的更快的Cython實現 )。

整體方法與其他方法非常相似; 掩碼創建時間:

np.maximum.accumulate((A2 < 0)[:, ::-1], axis=1)[:, ::-1]

這行代碼執行以下操作:

  • (A2 < 0)創建一個布爾數組,指示值是否為負數。 索引[:, ::-1]從左到右翻轉。

  • np.maximum.accumulate用於返回每行的累積最大值(即axis=1 )。 例如[False, True, False]將變為[False, True, True]

  • 最終的索引操作[:, ::-1]從左到右翻轉這個新的布爾數組。

然后剩下要做的就是使用布爾數組作為掩碼將True值設置為零。


借用@Divakar的答案中的時序方法和兩個函數,這里是我提出的方法的基准:

# method using np.maximum.accumulate
def accumulate_based(A2):
    A2[np.maximum.accumulate((A2 < 0)[:, ::-1], axis=1)[:, ::-1]] = 0
    return A2

# large sample array
A2 = np.random.randint(-4, 10, size=(100000, 100))
A2c = A2.copy()
A2c2 = A2.copy()

時間是:

In [47]: %timeit broadcasting_based(A2)
10 loops, best of 3: 61.7 ms per loop

In [48]: %timeit cumsum_based(A2c)
10 loops, best of 3: 127 ms per loop

In [49]: %timeit accumulate_based(A2c2) # quickest
10 loops, best of 3: 43.2 ms per loop

因此,對於這種尺寸和形狀的陣列,使用np.maximum.accumulate可以比下一個最快的解決方案快30%。


正如@ tom10指出的那樣 ,每個NumPy操作都完整地處理數組,當需要多個操作來獲得結果時,這可能是低效的。 只需一次通過陣列的迭代方法可能會更好。

下面是一個用Cython編寫的簡單函數,它的速度可能是純NumPy方法的兩倍。

可以使用存儲器視圖進一步加速該功能。

cimport cython
import numpy as np
cimport numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def cython_based(np.ndarray[long, ndim=2, mode="c"] array):
    cdef int rows, cols, i, j, seen_neg
    rows = array.shape[0]
    cols = array.shape[1]
    for i in range(rows):
        seen_neg = 0
        for j in range(cols-1, -1, -1):
            if seen_neg or array[i, j] < 0:
                seen_neg = 1
                array[i, j] = 0
    return array

此函數在每行中向后工作,並在看到負值后開始將值設置為零。

測試工作原理

A2 = np.random.randint(-4, 10, size=(100000, 100))
A2c = A2.copy()

np.array_equal(accumulate_based(A2), cython_based(A2c))
# True

比較功能的性能

In [52]: %timeit accumulate_based(A2)
10 loops, best of 3: 49.8 ms per loop

In [53]: %timeit cython_based(A2c)
100 loops, best of 3: 18.6 ms per loop

假設您要設置每行的所有元素,直到最后一個負元素設置為零(根據示例案例的問題中列出的預期輸出),這里可以建議兩種方法。

方法#1

這個基於np.cumsum來生成要設置為零的元素掩碼,如下所示 -

# Get boolean mask with TRUEs for each row starting at the first element and 
# ending at the last negative element
mask = (np.cumsum(A2[:,::-1]<0,1)>0)[:,::-1]

# Use mask to set all such al TRUEs to zeros as per the expected output in OP 
A2[mask] = 0

樣品運行 -

In [280]: A2 = np.random.randint(-4,10,(6,7)) # Random input 2D array

In [281]: A2
Out[281]: 
array([[-2,  9,  8, -3,  2,  0,  5],
       [-1,  9,  5,  1, -3, -3, -2],
       [ 3, -3,  3,  5,  5,  2,  9],
       [ 4,  6, -1,  6,  1,  2,  2],
       [ 4,  4,  6, -3,  7, -3, -3],
       [ 0,  2, -2, -3,  9,  4,  3]])

In [282]: A2[(np.cumsum(A2[:,::-1]<0,1)>0)[:,::-1]] = 0 # Use mask to set zeros

In [283]: A2
Out[283]: 
array([[0, 0, 0, 0, 2, 0, 5],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 3, 5, 5, 2, 9],
       [0, 0, 0, 6, 1, 2, 2],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 9, 4, 3]])

方法#2

這個開始於從@tom10's answer中找到最后的負面元素索引並開發成使用broadcasting的掩模查找方法來獲得所需的輸出,類似於approach #1

# Find last negative index for each row
last_idx = A2.shape[1] - 1 - np.argmax(A2[:,::-1]<0, axis=1)

# Find the invalid indices (rows with no negative indices)
invalid_idx = A2[np.arange(A2.shape[0]),last_idx]>=0

# Set the indices for invalid ones to "-1"
last_idx[invalid_idx] = -1

# Boolean mask with each row starting with TRUE as the first element 
# and ending at the last negative element
mask = np.arange(A2.shape[1]) < (last_idx[:,None] + 1)

# Set masked elements to zeros, for the desired output
A2[mask] = 0

運行時測試 -

功能定義:

def broadcasting_based(A2):
    last_idx = A2.shape[1] - 1 - np.argmax(A2[:,::-1]<0, axis=1)
    last_idx[A2[np.arange(A2.shape[0]),last_idx]>=0] = -1
    A2[np.arange(A2.shape[1]) < (last_idx[:,None] + 1)] = 0
    return A2

def cumsum_based(A2):    
    A2[(np.cumsum(A2[:,::-1]<0,1)>0)[:,::-1]] = 0    
    return A2

運行時:

In [379]: A2 = np.random.randint(-4,10,(100000,100))
     ...: A2c = A2.copy()
     ...: 

In [380]: %timeit broadcasting_based(A2)
10 loops, best of 3: 106 ms per loop

In [381]: %timeit cumsum_based(A2c)
1 loops, best of 3: 167 ms per loop

驗證結果 -

In [384]: A2 = np.random.randint(-4,10,(100000,100))
     ...: A2c = A2.copy()
     ...: 

In [385]: np.array_equal(broadcasting_based(A2),cumsum_based(A2c))
Out[385]: True

找到第一個通常比找到最后一個更容易,更快,所以在這里我反轉數組,然后找到第一個負數(使用OP的A2版本):

im = A2.shape[1] - 1 - np.argmax(A2[:,::-1]<0, axis=1)

# [4 6 3]      # which are the indices of the last negative in A2


但是,請注意,如果您的大型數組具有許多負數,那么使用非numpy方法實際上可能會更快,因此您可以使搜索短路。 也就是說,numpy將對整個數組進行計算,因此如果你連續有10000個元素,但通常會在前10個元素(反向搜索)中遇到負數,那么純Python方法可能會更快。

總的來說,迭代行對於后續操作也可能更快。 例如,如果你的下一步是乘法,那么只是將非零的末端的切片相乘可能會更快,或者可能找到最長的非零部分並且只處理截斷的數組。

這基本上歸結為每行的負數。 如果每行有1000個負數,那么你平均會有非零段,它們是你整行長度的1/1000,所以只需查看結尾就可以獲得1000倍的加速度。 問題中提供的簡短示例非常適合理解和回答基本問題,但是當您的最終應用程序是一個非常不同的用例時,我不會太認真地對時間測試進行考慮。 特別是因為通過使用迭代節省的分數時間與數組大小成比例地增加(假設恆定比率和負數的隨機分布)。

您可以訪問各行:

A2[0] == array([-5, -4,  2, -2, -1,  0,  1,  2,  3,  4])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM