Numpy lần đầu tiên xuất hiện của giá trị lớn hơn giá trị hiện có


143

Tôi có một mảng 1D trong numpy và tôi muốn tìm vị trí của chỉ mục trong đó một giá trị vượt quá giá trị trong mảng numpy.

Ví dụ

aa = range(-10,10)

Tìm vị trí ở aađâu, giá trị 5được vượt quá.


2
Mọi người nên rõ ràng liệu có thể không có giải pháp hay không (ví dụ: câu trả lời argmax sẽ không hoạt động trong trường hợp đó (tối đa (0,0,0,0) = 0) như ambrus đã nhận xét
seanv507

Câu trả lời:


198

Cái này nhanh hơn một chút (và trông đẹp hơn)

np.argmax(aa>5)

argmaxsẽ dừng ở lần đầu tiên True("Trong trường hợp có nhiều lần xuất hiện của các giá trị tối đa, các chỉ số tương ứng với lần xuất hiện đầu tiên được trả về.") Và không lưu danh sách khác.

In [2]: N = 10000

In [3]: aa = np.arange(-N,N)

In [4]: timeit np.argmax(aa>N/2)
100000 loops, best of 3: 52.3 us per loop

In [5]: timeit np.where(aa>N/2)[0][0]
10000 loops, best of 3: 141 us per loop

In [6]: timeit np.nonzero(aa>N/2)[0][0]
10000 loops, best of 3: 142 us per loop

103
Chỉ cần một lời cảnh báo: nếu không có giá trị True trong mảng đầu vào của nó, np.argmax sẽ vui vẻ trả về 0 (đó không phải là điều bạn muốn trong trường hợp này).
ambrus

8
Kết quả là chính xác, nhưng tôi thấy lời giải thích hơi đáng ngờ. argmaxdường như không dừng lại ở lần đầu tiên True. (Điều này có thể được kiểm tra bằng cách tạo các mảng boolean với một Truevị trí duy nhất tại các vị trí khác nhau.) Tốc độ có thể được giải thích bởi thực tế là argmaxkhông cần tạo danh sách đầu ra.
DrV

1
Tôi nghĩ bạn đã đúng, @DrV. Lời giải thích của tôi có nghĩa là về lý do tại sao nó mang lại kết quả chính xác mặc dù mục đích ban đầu không thực sự tìm kiếm tối đa, không phải tại sao nó nhanh hơn vì tôi không thể yêu cầu hiểu các chi tiết bên trong argmax.
askewchan

1
@George, tôi sợ tôi không biết tại sao chính xác. Tôi chỉ có thể nói nó nhanh hơn trong ví dụ cụ thể mà tôi đã trình bày, vì vậy tôi sẽ không xem xét nó nói chung nhanh hơn mà không (tôi) biết tại sao nó (xem bình luận của @ DrV) hoặc (ii) thử nghiệm nhiều trường hợp hơn (ví dụ: liệu có aađược sắp xếp không, như trong câu trả lời của @ Michael).
askewchan

2
@DrV, tôi chỉ chạy argmaxtrên các mảng Boolean 10 triệu phần tử với một mảng duy nhất Truetại các vị trí khác nhau bằng NumPy 1.11.2 và vị trí của Truevấn đề. Vì vậy, 1.11.2 argmaxdường như "ngắn mạch" trên mảng Boolean.
Ulrich Stern

95

đưa ra nội dung được sắp xếp của mảng của bạn, có một phương pháp thậm chí còn nhanh hơn: tìm kiếm .

import time
N = 10000
aa = np.arange(-N,N)
%timeit np.searchsorted(aa, N/2)+1
%timeit np.argmax(aa>N/2)
%timeit np.where(aa>N/2)[0][0]
%timeit np.nonzero(aa>N/2)[0][0]

# Output
100000 loops, best of 3: 5.97 µs per loop
10000 loops, best of 3: 46.3 µs per loop
10000 loops, best of 3: 154 µs per loop
10000 loops, best of 3: 154 µs per loop

19
Đây thực sự là câu trả lời tốt nhất giả sử mảng được sắp xếp (không thực sự được chỉ định trong câu hỏi). Bạn có thể tránh sự khó xử +1vớinp.searchsorted(..., side='right')
askewchan

3
Tôi nghĩ rằng sideđối số chỉ tạo ra sự khác biệt nếu có các giá trị lặp lại trong mảng được sắp xếp. Nó không thay đổi ý nghĩa của chỉ mục được trả về, đây luôn là chỉ mục bạn có thể chèn giá trị truy vấn tại, dịch chuyển tất cả các mục sau sang phải và duy trì một mảng được sắp xếp.
Gus

@Gus, sidecó hiệu lực khi cùng một giá trị nằm trong cả mảng được sắp xếp và mảng được chèn, bất kể giá trị lặp lại ở một trong hai. Các giá trị lặp lại trong mảng được sắp xếp chỉ phóng đại hiệu ứng (sự khác biệt giữa các bên là số lần giá trị được chèn xuất hiện trong mảng được sắp xếp). side không thay đổi ý nghĩa của chỉ mục được trả về, mặc dù nó không thay đổi mảng kết quả từ việc chèn các giá trị vào mảng được sắp xếp tại các chỉ mục đó. Một sự khác biệt tinh tế nhưng quan trọng; trong thực tế, câu trả lời này đưa ra chỉ số sai nếu N/2không có aa.
askewchan

Như được gợi ý trong nhận xét trên, câu trả lời này bị tắt bởi một nếu N/2không có aa. Các hình thức chính xác sẽ là np.searchsorted(aa, N/2, side='right')(không có +1). Cả hai hình thức cho cùng một chỉ số khác. Xem xét trường hợp thử nghiệm Nlà số lẻ (và N/2.0để buộc float nếu sử dụng python 2).
askewchan

21

Tôi cũng quan tâm đến điều này và tôi đã so sánh tất cả các câu trả lời được đề xuất với perfplot . (Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của perfplot.)

Nếu bạn biết rằng mảng bạn đang xem đã được sắp xếp , thì

numpy.searchsorted(a, alpha)

Dành cho bạn. Đó là một hoạt động liên tục, tức là tốc độ không phụ thuộc vào kích thước của mảng. Bạn không thể nhận được nhanh hơn đó.

Nếu bạn không biết gì về mảng của mình, bạn sẽ không sai với

numpy.argmax(a > alpha)

Đã được sắp xếp:

nhập mô tả hình ảnh ở đây

Chưa sắp xếp:

nhập mô tả hình ảnh ở đây

Mã để tái tạo cốt truyện:

import numpy
import perfplot


alpha = 0.5

def argmax(data):
    return numpy.argmax(data > alpha)

def where(data):
    return numpy.where(data > alpha)[0][0]

def nonzero(data):
    return numpy.nonzero(data > alpha)[0][0]

def searchsorted(data):
    return numpy.searchsorted(data, alpha)

out = perfplot.show(
    # setup=numpy.random.rand,
    setup=lambda n: numpy.sort(numpy.random.rand(n)),
    kernels=[
        argmax, where,
        nonzero,
        searchsorted
        ],
    n_range=[2**k for k in range(2, 20)],
    logx=True,
    logy=True,
    xlabel='len(array)'
    )

4
np.searchsortedkhông phải là thời gian không đổi. Đó là thực sự O(log(n)). Nhưng trường hợp thử nghiệm của bạn thực sự điểm chuẩn trường hợp tốt nhất của searchsorted(đó là O(1)).
MSeifert

@MSeifert Bạn cần xem O (log (n)) loại đầu vào nào?
Nico Schlömer

1
Lấy mục ở chỉ số sqrt (độ dài) đã dẫn đến hiệu suất rất tệ. Tôi cũng đã viết một câu trả lời ở đây bao gồm cả điểm chuẩn đó.
MSeifert

Tôi nghi ngờ searchsorted(hoặc bất kỳ thuật toán nào) có thể đánh bại O(log(n))tìm kiếm nhị phân cho dữ liệu phân phối thống nhất được sắp xếp. EDIT: searchsorted một tìm kiếm nhị phân.
Mateen Ulhaq

16
In [34]: a=np.arange(-10,10)

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

In [36]: np.where(a>5)
Out[36]: (array([16, 17, 18, 19]),)

In [37]: np.where(a>5)[0][0]
Out[37]: 16

8

Mảng có một bước không đổi giữa các yếu tố

Trong trường hợp một rangehoặc bất kỳ mảng tăng tuyến tính nào khác, bạn có thể chỉ cần tính toán chỉ mục theo chương trình, không cần thực sự lặp lại trên mảng:

def first_index_calculate_range_like(val, arr):
    if len(arr) == 0:
        raise ValueError('no value greater than {}'.format(val))
    elif len(arr) == 1:
        if arr[0] > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    first_value = arr[0]
    step = arr[1] - first_value
    # For linearly decreasing arrays or constant arrays we only need to check
    # the first element, because if that does not satisfy the condition
    # no other element will.
    if step <= 0:
        if first_value > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    calculated_position = (val - first_value) / step

    if calculated_position < 0:
        return 0
    elif calculated_position > len(arr) - 1:
        raise ValueError('no value greater than {}'.format(val))

    return int(calculated_position) + 1

Một người có lẽ có thể cải thiện điều đó một chút. Tôi đã đảm bảo rằng nó hoạt động chính xác cho một vài mảng và giá trị mẫu nhưng điều đó không có nghĩa là không thể có lỗi trong đó, đặc biệt là xem xét rằng nó sử dụng float ...

>>> import numpy as np
>>> first_index_calculate_range_like(5, np.arange(-10, 10))
16
>>> np.arange(-10, 10)[16]  # double check
6

>>> first_index_calculate_range_like(4.8, np.arange(-10, 10))
15

Cho rằng nó có thể tính toán vị trí mà không cần lặp lại, nó sẽ là thời gian không đổi ( O(1)) và có thể đánh bại tất cả các phương pháp được đề cập khác. Tuy nhiên, nó đòi hỏi một bước không đổi trong mảng, nếu không nó sẽ tạo ra kết quả sai.

Giải pháp chung sử dụng numba

Một cách tiếp cận tổng quát hơn sẽ là sử dụng hàm numba:

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

Điều đó sẽ làm việc cho bất kỳ mảng nào nhưng nó phải lặp lại trên mảng, vì vậy trong trường hợp trung bình, nó sẽ là O(n):

>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16

Điểm chuẩn

Mặc dù Nico Schlömer đã cung cấp một số điểm chuẩn, tôi nghĩ rằng có thể hữu ích khi đưa vào các giải pháp mới của mình và để kiểm tra các "giá trị" khác nhau.

Cài đặt thử nghiệm:

import numpy as np
import math
import numba as nb

def first_index_using_argmax(val, arr):
    return np.argmax(arr > val)

def first_index_using_where(val, arr):
    return np.where(arr > val)[0][0]

def first_index_using_nonzero(val, arr):
    return np.nonzero(arr > val)[0][0]

def first_index_using_searchsorted(val, arr):
    return np.searchsorted(arr, val) + 1

def first_index_using_min(val, arr):
    return np.min(np.where(arr > val))

def first_index_calculate_range_like(val, arr):
    if len(arr) == 0:
        raise ValueError('empty array')
    elif len(arr) == 1:
        if arr[0] > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    first_value = arr[0]
    step = arr[1] - first_value
    if step <= 0:
        if first_value > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    calculated_position = (val - first_value) / step

    if calculated_position < 0:
        return 0
    elif calculated_position > len(arr) - 1:
        raise ValueError('no value greater than {}'.format(val))

    return int(calculated_position) + 1

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

funcs = [
    first_index_using_argmax, 
    first_index_using_min, 
    first_index_using_nonzero,
    first_index_calculate_range_like, 
    first_index_numba, 
    first_index_using_searchsorted, 
    first_index_using_where
]

from simple_benchmark import benchmark, MultiArgument

và các ô được tạo bằng cách sử dụng:

%matplotlib notebook
b.plot()

mục là lúc bắt đầu

b = benchmark(
    funcs,
    {2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

nhập mô tả hình ảnh ở đây

Hàm numba thực hiện tốt nhất theo sau là hàm tính toán và hàm tìm kiếm. Các giải pháp khác thực hiện tồi tệ hơn nhiều.

mục ở cuối

b = benchmark(
    funcs,
    {2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

nhập mô tả hình ảnh ở đây

Đối với các mảng nhỏ, hàm numba thực hiện nhanh đáng kinh ngạc, tuy nhiên đối với các mảng lớn hơn, nó vượt trội hơn so với hàm tính toán và hàm tìm kiếm.

mục tại sqrt (len)

b = benchmark(
    funcs,
    {2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

nhập mô tả hình ảnh ở đây

Điều này thú vị hơn. Một lần nữa numba và hàm tính toán hoạt động rất tốt, tuy nhiên điều này thực sự gây ra trường hợp xấu nhất của tìm kiếm mà thực sự không hoạt động tốt trong trường hợp này.

So sánh các hàm khi không có giá trị thỏa mãn điều kiện

Một điểm thú vị khác là cách các hàm này hoạt động nếu không có giá trị nào có chỉ mục được trả về:

arr = np.ones(100)
value = 2

for func in funcs:
    print(func.__name__)
    try:
        print('-->', func(value, arr))
    except Exception as e:
        print('-->', e)

Với kết quả này:

first_index_using_argmax
--> 0
first_index_using_min
--> zero-size array to reduction operation minimum which has no identity
first_index_using_nonzero
--> index 0 is out of bounds for axis 0 with size 0
first_index_calculate_range_like
--> no value greater than 2
first_index_numba
--> -1
first_index_using_searchsorted
--> 101
first_index_using_where
--> index 0 is out of bounds for axis 0 with size 0

Tìm kiếm, argmax và numba chỉ đơn giản trả về một giá trị sai. Tuy nhiên searchsortednumbatrả về một chỉ mục không phải là một chỉ mục hợp lệ cho mảng.

Các chức năng where, min, nonzerocalculateném một ngoại lệ. Tuy nhiên chỉ có ngoại lệ cho việc calculatethực sự nói bất cứ điều gì hữu ích.

Điều đó có nghĩa là người ta thực sự phải bọc các cuộc gọi này trong một hàm bao bọc thích hợp để bắt các ngoại lệ hoặc các giá trị trả về không hợp lệ và xử lý một cách thích hợp, ít nhất là nếu bạn không chắc chắn liệu giá trị có thể nằm trong mảng hay không.


Lưu ý: Tính toán và searchsortedtùy chọn chỉ hoạt động trong điều kiện đặc biệt. Hàm "tính toán" yêu cầu một bước không đổi và tìm kiếm được yêu cầu sắp xếp mảng. Vì vậy, những điều này có thể hữu ích trong trường hợp phù hợp nhưng không phải là giải pháp chung cho vấn đề này. Trong trường hợp bạn đang xử lý các danh sách Python được sắp xếp, bạn có thể muốn xem mô-đun chia đôi thay vì sử dụng tìm kiếm Numpys.


3

Tôi muốn cầu hôn

np.min(np.append(np.where(aa>5)[0],np.inf))

Điều này sẽ trả về chỉ số nhỏ nhất trong đó điều kiện được đáp ứng, trong khi trả về vô cực nếu điều kiện không bao giờ được đáp ứng (và wheretrả về một mảng trống).


1

Tôi sẽ đi với

i = np.min(np.where(V >= x))

trong đó Vlà vectơ (mảng 1d), xlà giá trị và ilà chỉ số kết quả.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.