Nilai kejadian pertama numpy lebih besar dari nilai yang ada

144

Saya memiliki array 1D di numpy dan saya ingin menemukan posisi indeks di mana nilai melebihi nilai dalam array numpy.

Misalnya

aa = range(-10,10)

Temukan posisi di aamana, nilai 5terlampaui.

pengguna308827
sumber
2
Orang harus jelas apakah tidak akan ada solusi (karena misalnya jawaban argmax tidak akan berfungsi dalam kasus itu (maks. (0,0,0,0) = 0) seperti komentar
ambrus

Jawaban:

199

Ini sedikit lebih cepat (dan terlihat lebih bagus)

np.argmax(aa>5)

Karena argmaxakan berhenti pada yang pertama True("Jika terjadi beberapa kali nilai maksimum, indeks yang sesuai dengan kejadian pertama dikembalikan.") Dan tidak menyimpan daftar lain.

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
askewchan
sumber
103
Hanya kata peringatan: jika tidak ada nilai True dalam array inputnya, np.argmax akan dengan senang hati mengembalikan 0 (yang bukan yang Anda inginkan dalam kasus ini).
ambrus
8
Hasilnya benar, tetapi saya menemukan penjelasannya agak mencurigakan. argmaxsepertinya tidak berhenti pada awalnya True. (Ini dapat diuji dengan membuat array boolean dengan satu Truedi posisi yang berbeda.) Kecepatan mungkin dijelaskan oleh fakta bahwa argmaxtidak perlu membuat daftar output.
DrV
1
Saya pikir Anda benar, @ DVD. Penjelasan saya dimaksudkan tentang mengapa itu memberikan hasil yang benar meskipun maksud aslinya tidak benar-benar mencari yang maksimal, bukan mengapa itu lebih cepat karena saya tidak dapat mengklaim untuk memahami detail bagian dalam argmax.
askewchan
1
@ George, aku takut aku tidak tahu persis mengapa. Saya hanya bisa mengatakan itu lebih cepat dalam contoh khusus yang saya tunjukkan, jadi saya tidak akan menganggapnya lebih cepat secara umum tanpa (i) mengetahui mengapa demikian (lihat komentar @ DrV) atau (ii) menguji lebih banyak kasus (misalnya, apakah aadiurutkan, seperti pada jawaban @ Michael).
askewchan
3
@DrV, saya hanya berlari argmaxpada array 10 juta elemen Boolean dengan satu Truedi posisi yang berbeda menggunakan NumPy 1.11.2, dan posisi yang Truepenting. Jadi 1.11.2 argmaxtampaknya "hubungan pendek" pada array Boolean.
Ulrich Stern
96

diberikan konten yang diurutkan dari array Anda, ada metode yang lebih cepat: searchsorted .

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
MichaelKaisers
sumber
19
Ini benar-benar jawaban terbaik dengan asumsi array diurutkan (yang sebenarnya tidak ditentukan dalam pertanyaan). Anda dapat menghindari canggung +1dengannp.searchsorted(..., side='right')
askewchan
3
Saya pikir sideargumen hanya membuat perbedaan jika ada nilai yang diulang dalam array yang diurutkan. Itu tidak mengubah arti dari indeks yang dikembalikan, yang selalu merupakan indeks tempat Anda dapat memasukkan nilai kueri, menggeser semua entri berikut ke kanan, dan mempertahankan array yang diurutkan.
Gus
@ Geus, sidememiliki efek ketika nilai yang sama di kedua diurutkan dan array yang dimasukkan, terlepas dari nilai yang diulang di kedua. Nilai berulang dalam array yang disortir hanya melebih-lebihkan efeknya (perbedaan antara sisi adalah berapa kali nilai yang dimasukkan muncul dalam array yang diurutkan). side tidak mengubah arti dari indeks yang dikembalikan, meskipun itu tidak mengubah array yang dihasilkan dari memasukkan nilai-nilai ke dalam array yang diurutkan pada indeks tersebut. Perbedaan yang halus tapi penting; sebenarnya jawaban ini memberikan indeks yang salah jika N/2tidak ada aa.
askewchan
Seperti yang ditunjukkan dalam komentar di atas, jawaban ini salah jika N/2tidak ada di aa. Bentuk yang benar adalah np.searchsorted(aa, N/2, side='right')(tanpa +1). Kedua bentuk memberikan indeks yang sama jika tidak. Pertimbangkan kasus uji untuk Nmenjadi aneh (dan N/2.0untuk memaksa mengambang jika menggunakan python 2).
askewchan
21

Saya juga tertarik dengan ini dan saya telah membandingkan semua jawaban yang disarankan dengan perfplot . (Penafian: Saya penulis perfplot.)

Jika Anda tahu bahwa array yang Anda cari sudah diurutkan , maka

numpy.searchsorted(a, alpha)

adalah untukmu. Ini adalah operasi waktu konstan, yaitu, kecepatan tidak tergantung pada ukuran array. Anda tidak bisa lebih cepat dari itu.

Jika Anda tidak tahu apa-apa tentang array Anda, Anda tidak akan salah

numpy.argmax(a > alpha)

Sudah disortir:

masukkan deskripsi gambar di sini

Tidak disortir:

masukkan deskripsi gambar di sini

Kode untuk mereproduksi plot:

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)'
    )
Nico Schlömer
sumber
4
np.searchsortedbukan waktu yang konstan. Sebenarnya O(log(n)). Tetapi test case Anda benar-benar memberikan tolok ukur pada kasus terbaik searchsorted(yaitu O(1)).
MSeifert
@ MSeifert Array input / alpha seperti apa yang Anda perlukan untuk melihat O (log (n))?
Nico Schlömer
1
Mendapatkan item pada indeks sqrt (panjang) memang menyebabkan kinerja yang sangat buruk. Saya juga menulis jawaban di sini termasuk tolok ukur itu.
MSeifert
Saya ragu searchsorted(atau algoritma apa pun) dapat mengalahkan O(log(n))pencarian biner untuk data yang didistribusikan secara seragam. EDIT: searchsorted adalah pencarian biner.
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
Moj
sumber
8

Array yang memiliki langkah konstan antara elemen

Jika rangearray atau peningkatan linear lainnya, Anda dapat menghitung indeks secara terprogram, tidak perlu benar-benar beralih ke array sama sekali:

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

Seseorang mungkin bisa sedikit memperbaiki itu. Saya telah memastikan itu berfungsi dengan benar untuk beberapa sampel array dan nilai-nilai tetapi itu tidak berarti tidak mungkin ada kesalahan di sana, terutama mengingat bahwa ia menggunakan pelampung ...

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

Mengingat bahwa ia dapat menghitung posisi tanpa iterasi apa pun, itu akan menjadi waktu yang konstan ( O(1)) dan mungkin dapat mengalahkan semua pendekatan yang disebutkan lainnya. Namun itu membutuhkan langkah konstan dalam array, jika tidak maka akan menghasilkan hasil yang salah.

Solusi umum menggunakan numba

Pendekatan yang lebih umum akan menggunakan fungsi numba:

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

Itu akan bekerja untuk array apa pun tetapi harus beralih di atas array, jadi dalam kasus rata-rata akan menjadi O(n):

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

Tolok ukur

Meskipun Nico Schlömer sudah memberikan beberapa tolok ukur, saya pikir mungkin berguna untuk memasukkan solusi baru saya dan untuk menguji "nilai" yang berbeda.

Pengaturan tes:

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

dan plot dihasilkan menggunakan:

%matplotlib notebook
b.plot()

item di awal

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

masukkan deskripsi gambar di sini

Fungsi numba berkinerja terbaik diikuti oleh fungsi penghitungan dan fungsi yang disortir. Solusi lain berperforma jauh lebih buruk.

barang ada di akhir

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

masukkan deskripsi gambar di sini

Untuk array kecil, fungsi numba berkinerja sangat cepat, namun untuk array yang lebih besar, kinerjanya lebih baik dari fungsi penghitungan dan fungsi yang dicari.

item ada di 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")

masukkan deskripsi gambar di sini

Ini lebih menarik. Lagi-lagi numba dan fungsi kalkulasi bekerja sangat baik, namun ini sebenarnya memicu kasus pencarian terburuk yang benar-benar tidak berfungsi dengan baik dalam kasus ini.

Perbandingan fungsi ketika tidak ada nilai yang memenuhi kondisi

Poin menarik lainnya adalah bagaimana fungsi ini berperilaku jika tidak ada nilai yang indeksnya harus dikembalikan:

arr = np.ones(100)
value = 2

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

Dengan hasil ini:

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

Pencarian yang disortir, argmax, dan numba hanya mengembalikan nilai yang salah. Namun searchsorteddan numbamengembalikan indeks yang bukan indeks yang valid untuk array.

Fungsi where, min, nonzerodan calculatemelemparkan sebuah pengecualian. Namun hanya pengecualian untuk calculatebenar - benar mengatakan sesuatu yang bermanfaat.

Itu berarti kita harus membungkus panggilan ini dalam fungsi wrapper yang sesuai yang menangkap pengecualian atau nilai pengembalian yang tidak valid dan menangani dengan tepat, setidaknya jika Anda tidak yakin apakah nilainya bisa dalam array.


Catatan: Perhitungan dan searchsortedopsi hanya berfungsi dalam kondisi khusus. Fungsi "menghitung" memerlukan langkah konstan dan pencarian disortir membutuhkan array yang akan diurutkan. Jadi ini bisa berguna dalam situasi yang tepat tetapi bukan solusi umum untuk masalah ini. Jika Anda berurusan dengan daftar Python yang diurutkan, Anda mungkin ingin melihat modul bisect daripada menggunakan Numpys yang dicari.

MSeifert
sumber
3

Saya ingin melamar

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

Ini akan mengembalikan indeks terkecil di mana kondisi terpenuhi, sementara mengembalikan infinity jika kondisi tidak pernah terpenuhi (dan wheremengembalikan array kosong).

Mfeldt
sumber
1

Saya akan pergi dengan

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

di mana Vvektor (array 1d), xadalah nilai dan imerupakan indeks yang dihasilkan.

sivic
sumber