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á.
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á.
Câu trả lời:
Cái này nhanh hơn một chút (và trông đẹp hơn)
np.argmax(aa>5)
Vì argmax
sẽ 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
argmax
dườ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 True
vị 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à argmax
không cần tạo danh sách đầu ra.
argmax
.
aa
được sắp xếp không, như trong câu trả lời của @ Michael).
argmax
trên các mảng Boolean 10 triệu phần tử với một mảng duy nhất True
tại các vị trí khác nhau bằng NumPy 1.11.2 và vị trí của True
vấn đề. Vì vậy, 1.11.2 argmax
dường như "ngắn mạch" trên mảng Boolean.
đư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
+1
vớinp.searchsorted(..., side='right')
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.
side
có 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/2
không có aa
.
N/2
khô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 N
là số lẻ (và N/2.0
để buộc float nếu sử dụng python 2).
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:
Chưa sắp xếp:
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)'
)
np.searchsorted
khô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)
).
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
là một tìm kiếm nhị phân.
Trong trường hợp một range
hoặ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.
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
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()
b = benchmark(
funcs,
{2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
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.
b = benchmark(
funcs,
{2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
Đố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.
b = benchmark(
funcs,
{2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
Đ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.
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 searchsorted
và numba
trả 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
, nonzero
và calculate
ném một ngoại lệ. Tuy nhiên chỉ có ngoại lệ cho việc calculate
thự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à searchsorted
tù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.
Tôi sẽ đi với
i = np.min(np.where(V >= x))
trong đó V
là vectơ (mảng 1d), x
là giá trị và i
là chỉ số kết quả.