Numpy: elemen bukan nol pertama (Trac #1673)

Dibuat pada 20 Okt 2012  ·  26Komentar  ·  Sumber: numpy/numpy

_Tiket asli http://projects.scipy.org/numpy/ticket/1673 pada 13-11-2010 oleh pengguna trac tom3118, ditetapkan untuk tidak diketahui._

"Numpy untuk pengguna matlab" menyarankan menggunakan
nonzero(A)[0][0]
untuk menemukan indeks elemen bukan nol pertama dari array A.

Masalah dengan ini adalah bahwa A mungkin memiliki panjang satu juta elemen dan elemen pertama mungkin nol.

Ini adalah operasi yang sangat umum. Metode built-in yang efisien untuk ini akan sangat berguna. Ini juga akan memudahkan transisi orang dari Matlab di mana find sangat umum.

01 - Enhancement Other

Komentar yang paling membantu

Saya tahu ini terlambat 3 tahun, tetapi apakah ini termasuk dalam numpy sekarang? Berasal dari latar belakang Matlab, fungsi ini tampaknya sangat penting bagi saya. PR akan sangat dihargai (bukan karena saya salah satu pengembang).

Semua 26 komentar

_trac pengguna tom3118 menulis pada 2010-11-13_

Kasus penggunaan terkait adalah:
filter(test,A)[0]
Di mana A panjang atau test mahal.

_@rgommers menulis pada 24-03-2011_

Tidak harus hanya menjadi bukan nol pertama, pertama nilai apa pun akan berguna.

_@rgommers menulis pada 24-03-2011_

Seperti dicatat dalam #2333, artinya tidak ambigu untuk 1-D. Untuk >1-D semantik siap untuk didiskusikan.

Mungkin kata kunci yang menentukan urutan iterasi di atas sumbu berfungsi. Atau bisa juga tidak terdefinisi untuk >1-D.

_trac pengguna lcampagn menulis pada 07-09-2011_

Saya telah melihat banyak permintaan untuk find_first di numpy, tetapi sebagian besar permintaan ini memiliki persyaratan yang agak berbeda (dan tidak kompatibel) seperti "temukan nilai pertama kurang dari x" atau "temukan nilai bukan nol pertama". Saya menyarankan spesifikasi fungsi berikut:

  ind = array.find(x, testOp='eq', arrayOp='all', axis=0, test=None)
  arguments:
    x       -> value to search for
    testOp  -> condition to test for ('eq', 'ne', 'gt', 'lt', 'ge', 'le')
    arrayOp -> method for joining multiple comparisons ('any' or 'all')
    axis    -> the axis over which to search
    test    -> for convenience, this may specify a function to call to perform
               the test. This is not expected to be efficient.
  returns: 
    first index where condition is true (or test returns true, if given)
    or None if the condition was never met

Jika array memiliki ndim > 1, maka pengujian dilakukan menggunakan aturan penyiaran normal.
Jadi misalnya, jika saya memiliki array dengan bentuk (2,3), berikut ini akan valid:

  ## find first row with all values=0
  array.find(0, testOp='eq', arrayOp='all', axis=0)
  ## equivalent to:
  for i in range(array.shape[axis]):
    if (array[i] == 0).all():
      return i

  ## find first column with any element greater than its corresponding element in col
  col = array([1,2])
  array.find(col, testOp='gt', arrayOp='any', axis=1)
  ## equivalent to:
  for i in range(array.shape[axis]):
    if (array[:,i] == col.any():
      return i

Karena saya membutuhkan fungsi ini tempo hari, saya telah melihat ini dengan baik dan yakin bahwa solusi C diperlukan untuk mendapatkan hasil cepat yang sesuai, namun pendekatan chunking yang ditulis dengan python terbukti sangat cepat, dan banyak lebih fleksibel untuk boot, untuk kasus saya.

import numpy as np
from itertools import chain, izip


def find(a, predicate, chunk_size=1024):
    """
    Find the indices of array elements that match the predicate.

    Parameters
    ----------
    a : array_like
        Input data, must be 1D.

    predicate : function
        A function which operates on sections of the given array, returning
        element-wise True or False for each data value.

    chunk_size : integer
        The length of the chunks to use when searching for matching indices.
        For high probability predicates, a smaller number will make this
        function quicker, similarly choose a larger number for low
        probabilities.

    Returns
    -------
    index_generator : generator
        A generator of (indices, data value) tuples which make the predicate
        True.

    See Also
    --------
    where, nonzero

    Notes
    -----
    This function is best used for finding the first, or first few, data values
    which match the predicate.

    Examples
    --------
    >>> a = np.sin(np.linspace(0, np.pi, 200))
    >>> result = find(a, lambda arr: arr > 0.9)
    >>> next(result)
    ((71, ), 0.900479032457)
    >>> np.where(a > 0.9)[0][0]
    71


    """
    if a.ndim != 1:
        raise ValueError('The array must be 1D, not {}.'.format(a.ndim))

    i0 = 0
    chunk_inds = chain(xrange(chunk_size, a.size, chunk_size), 
                 [None])

    for i1 in chunk_inds:
        chunk = a[i0:i1]
        for inds in izip(*predicate(chunk).nonzero()):
            yield (inds[0] + i0, ), chunk[inds]
        i0 = i1
In [1]: from np_utils import find

In [2]: import numpy as np

In [3]: import numpy.random    

In [4]: np.random.seed(1)

In [5]: a = np.random.randn(1e8)

In [6]: a.min(), a.max()
Out[6]: (-6.1194900990552776, 5.9632246301166321)

In [7]: next(find(a, lambda a: np.abs(a) > 6))
Out[7]: ((33105441,), -6.1194900990552776)

In [8]: (np.abs(a) > 6).nonzero()
Out[8]: (array([33105441]),)

In [9]: %timeit (np.abs(a) > 6).nonzero()
1 loops, best of 3: 1.51 s per loop

In [10]: %timeit next(find(a, lambda a: np.abs(a) > 6))
1 loops, best of 3: 912 ms per loop

In [11]: %timeit next(find(a, lambda a: np.abs(a) > 6, chunk_size=100000))
1 loops, best of 3: 470 ms per loop

In [12]: %timeit next(find(a, lambda a: np.abs(a) > 6, chunk_size=1000000))
1 loops, best of 3: 483 ms per loop

Saya akan memasukkan ini ke milis pengembang, tetapi jika ada minat yang cukup, saya akan dengan senang hati mengubahnya menjadi PR.

Bersulang,

Saya tahu ini terlambat 3 tahun, tetapi apakah ini termasuk dalam numpy sekarang? Berasal dari latar belakang Matlab, fungsi ini tampaknya sangat penting bagi saya. PR akan sangat dihargai (bukan karena saya salah satu pengembang).

Saya juga akan tertarik dengan ini.

Mungkin sudah jelas, tetapi karena tidak disebutkan: np.all() dan np.any() mungkin akan lebih mudah (dan jelas untuk dimensi > 1) untuk membuat malas. Saat ini...

In [2]: zz = np.zeros(shape=10000000)

In [3]: zz[0] = 1

In [4]: %timeit -r 1 -n 1 any(zz)
1 loop, best of 1: 3.52 µs per loop

In [5]: %timeit -r 1 -n 1 np.any(zz)
1 loop, best of 1: 16.7 ms per loop

(maaf, saya melewatkan referensi ke #3446 )

Karena saya telah mencari solusi yang efisien untuk masalah ini cukup lama dan karena tampaknya tidak ada rencana konkret untuk mendukung fitur ini, saya telah mencoba untuk menemukan solusi yang tidak cukup lengkap dan serbaguna seperti yang disarankan oleh api. di atas (terutama mendukung untuk saat ini hanya array 1D), tetapi itu memiliki keuntungan karena ditulis sepenuhnya dalam C dan karena itu tampaknya agak efisien.

Anda menemukan sumber dan detailnya di sini:

https://pypi.python.org/pypi?name=py_find_1st& :action=display

Saya akan berterima kasih atas komentar apa pun tentang implementasi terutama, pertanyaan tentang masalah kinerja yang agak mencengangkan ketika meneruskan array boolean dan mencari nilai sebenarnya pertama, yang dijelaskan pada halaman PyPi itu.

Saya menemukan ini dari posting stackexchange yang mencari fitur ini, yang telah dilihat lebih dari 70 ribu kali. @roebel apakah Anda pernah mendapatkan umpan balik tentang ini? Bisakah Anda memasukkan PR untuk fitur tersebut, yang mungkin mendapat lebih banyak perhatian?

tidak, saya tidak pernah mendapat umpan balik, tetapi beberapa orang tampaknya menggunakan paket tersebut tanpa masalah.
BTW, untuk anaconda linux dan macos saya telah membuat installer anaconda

https://anaconda.org/roebel/py_find_1st

Sehubungan dengan PR, saya harus melihat upaya yang mengharuskan saya untuk mengadaptasi ini sedemikian rupa sehingga dapat digabungkan dengan mudah menjadi numpy. Saya tidak akan punya waktu untuk berdebat melalui diskusi tentang perubahan dan ekstensi API.

Apakah penghapusan " prioritas:normal " berarti bahwa fitur penting ini entah bagaimana akan kurang diperhatikan?

Prioritasnya masih "normal", hanya tanpa label. Isu ini membutuhkan seorang juara untuk benar-benar membuat PR dan mendorongnya melalui proses persetujuan termasuk dokumentasi dan semoga menjadi tolok ukur.

Mungkin berguna di sini untuk menunjuk ke #8528, yang secara nominal sekitar all_equal tetapi dapat dilihat sebagai bagian implementasi dari ini. Memang, di https://github.com/numpy/numpy/pull/8528#issuecomment -365358119 , @ahaldane secara eksplisit menyarankan untuk menerapkan metode pengurangan first pada semua operator perbandingan alih-alih gufunc baru all_equal .

Ini juga berarti ada sedikit implementasi yang menunggu untuk diadaptasi (walaupun itu bukan perubahan sepele dari gufunc ke metode reduksi baru, dan ada pertanyaan apakah kita menginginkan metode baru pada semua ufunc, bahkan untuk yang first tidak masuk akal.

Masalah ini telah diketahui sejak (setidaknya) 2012. Adakah pembaruan tentang cara untuk mencegah nonzero(A)[0][0] mencari semua A ?

Apakah ini yang disebut cara Pythonic untuk selalu memindai semua elemen?

@yunyoulu : Ini cara yang ufunc. Mari kita mundur selangkah dan melihat proses umum komputasi multi-langkah di numpy, dan jumlah lintasan yang diperlukan:

  1. np.argwhere(x)[0] - melakukan 1 pass data
  2. np.argwhere(f(x))[0] - melakukan 2 lintasan data
  3. np.argwhere(f(g(x)))[0] - melakukan 3 lintasan data

Salah satu opsi adalah memperkenalkan fungsi np.first atau serupa - yang kemudian akan terlihat seperti berikut, di mana k <= 1 bervariasi tergantung di mana elemen pertama berada:

  1. np.first(x)[0] - melakukan 0+k pass data
  2. np.first(f(x))[0] - melakukan 1+k lintasan data
  3. np.first(f(g(x)))[0] - melakukan 2+k lintasan data

Pertanyaan yang harus diajukan di sini adalah - apakah penghematan ini benar-benar berharga? Numpy pada dasarnya bukan platform komputasi yang malas, dan membuat langkah terakhir dari komputasi menjadi malas tidak terlalu berharga jika semua langkah sebelumnya tidak.


ketinggalan jaman

@eric-wieser

Saya tidak berpikir itu kata-kata yang tepat. Jika k = 10 untuk beberapa masalah, itu bukan 1+10=11 melewati data untuk np.first(f(x))[0]

(diedit oleh @eric-wieser untuk singkatnya, percakapan ini sudah terlalu panjang)

Kasus penggunaan di mana saya paling melihat kebutuhan akan fungsi ini adalah ketika A adalah tensor besar dengan A.shape = (n_1, n_2, ..., n_m) . Dalam kasus seperti itu, np.first(A) hanya perlu melihat elemen k dari A alih-alih n_1*n_2*...*n_m (penghematan yang berpotensi signifikan).

Saya paling melihat kebutuhan untuk fungsi ini adalah ketika A adalah tensor besar

Agaknya dalam hal ini Anda telah melakukan setidaknya satu pass data penuh - jadi paling baik Anda mendapatkan kode yang berjalan dua kali lebih cepat.

Pertanyaan yang harus diajukan di sini adalah - apakah penghematan ini benar-benar berharga? Numpy pada dasarnya bukan platform komputasi yang malas, dan membuat langkah terakhir dari komputasi menjadi malas tidak terlalu berharga jika semua langkah sebelumnya tidak.

Itu sudut pandang menarik yang, jika ditetapkan, dapat digunakan untuk membenarkan pemborosan hampir semua upaya untuk meningkatkan kinerja komputasi "karena kami juga menghitung sesuatu yang lain dan itu masih lambat". (Itu adalah argumen yang sama yang digunakan oleh para penyangkal aksi perubahan iklim - yah, sampai negara lain ini melakukan sesuatu, melakukan sesuatu di negara kita tidak akan membantu siapa pun.) Saya tidak yakin sama sekali. Jika ada kemungkinan untuk mempercepat beberapa bagian dari perhitungan sebesar 1/k, dengan k berpotensi sangat sangat kecil, menurut saya itu sepadan.

Juga, ketika bekerja secara interaktif (Jupyter dll), sangat sering Anda melakukan "passes" data di sel yang terpisah, sehingga Anda bisa mempercepat seluruh sel juga.

np.first(f(x))[0] - melakukan 1+k lintasan data

@eric-wieser memang ketika saya melihat masalah ini pada tahun 2017 saya benar-benar berharap ini akan menjadi langkah pertama menuju semacam np.firstwhere(x, array_or_value_to_compare) , yang memang merupakan kasus tertentu - tetapi penting dalam pengalaman saya - dari f(x) .

@toobaz : Saya berasumsi Anda memiliki f = lambda x: x == value_to_compare dalam contoh itu.

Inilah alasan mengapa saya berhati-hati untuk menempuh jalan ini sama sekali (cc @bersbersbers). Jika Anda tidak hati-hati, kita berakhir dengan (ejaan spekulatif):

  1. np.first(x) - simpan pass vs bukan nol
  2. np.first_equal(x, v) - simpan pass vs first(np.equal(x, v))
  3. np.first_square_equal(x*x, v) - simpan pass vs first_equal(np.square(x), v)

Seharusnya cukup jelas bahwa ini tidak berskala sama sekali, dan kita harus menarik garis di suatu tempat. Saya sedikit mendukung 1 diizinkan, tetapi 2 diizinkan sudah merupakan ledakan luas permukaan API, dan 3 tampaknya sangat tidak bijaksana bagi saya.

Satu argumen yang mendukung np.first - jika kita mengimplementasikannya, numba dapat membuat kasus khusus sedemikian rupa sehingga np.first(x*x == v) _dalam konteks numba_ sebenarnya _melakukan satu pass.

Bagaimanapun, senang mengetahui bahwa tidak mungkin melakukan hal-hal malas di numpy, yang menjelaskan status masalah saat ini.

Namun, saya tidak merasa nyaman ketika tweak kinerja dianggap hanya dalam skalabilitas.

Mari kita ajukan pertanyaan sederhana: apakah komputer pribadi diskalakan hari ini? Jawabannya pasti TIDAK . Tiga tahun lalu ketika Anda membeli laptop standar, mereka dilengkapi dengan memori 8GB; dan sekarang Anda masih akan menemukan 8GB di pasar. Namun, setiap perangkat lunak menggunakan memori 2x atau 4x lebih banyak daripada sebelumnya. Setidaknya workstation tidak menskalakan dengan cara yang sama seperti cluster.

Membuat fungsi 10x lebih lambat tanpa mengubah kerumitannya sama sekali sudah cukup untuk membuat seorang ilmuwan data gila. Lebih buruk lagi, tidak ada hal elegan yang bisa dia lakukan bahkan jika hambatannya diketahui melalui pembuatan profil.

Apa yang saya coba jelaskan adalah bahwa memiliki kemampuan untuk melakukan pemrosesan yang malas selalu diinginkan dan dapat menjadi sangat penting untuk daya tanggap sistem serta produktivitas orang-orang yang menggunakan bahasa tersebut. Kesulitan atau beban kerja dalam pengembangan perpustakaan merupakan alasan yang sangat baik untuk tidak mengimplementasikan fitur-fitur ini dan tentu saja dapat dimengerti, tetapi jangan katakan mereka tidak berguna.

@toobaz : Saya berasumsi Anda memiliki f = lambda x: x == value_to_compare dalam contoh itu.

Benar

Inilah alasan mengapa saya berhati-hati untuk menempuh jalan ini sama sekali (cc @bersbersbers). Jika Anda tidak hati-hati, kita berakhir dengan (ejaan spekulatif):

1. `np.first(x)` - save a pass vs nonzero

2. `np.first_equal(x, v)` - save a pass vs `first(np.equal(x, v))`

3. `np.first_square_equal(x*x, v)` - save a pass vs `first_equal(np.square(x), v)`

Saya memahami kekhawatiran Anda, tetapi saya tidak akan pernah meminta np.first_square_equal persis seperti saya tidak akan pernah meminta (dan saya harap tidak ada yang meminta) np.square_where . Dan ya, saya melihat itu berarti membuat lulus penuh dari data jika Anda melakukannya 3. Tapi v dibuat sekali, dan saya mungkin perlu mencari banyak nilai yang berbeda dari x di atasnya . Misalnya (kembali untuk kesederhanaan ke contoh 2.), saya ingin memverifikasi apakah semua 30 kategori yang mungkin muncul dalam larik item 10^9 saya - dan saya sangat curiga mereka semua muncul di antara elemen 10^3 pertama.

Jadi pertama-tama izinkan saya mengklarifikasi komentar saya sebelumnya: Saya ingin np.firstwhere(x, array_or_value_to_compare) sebagai fungsi yang memenuhi intuisi saya, tetapi masalah komputasi yang saya alami pada tahun 2017 akan diselesaikan bahkan hanya dengan np.first .

Kedua, intinya adalah - saya pikir - bukan hanya waktu menjalankan panggilan tunggal. Memang benar bahwa saya harus membuat pass data penuh untuk melakukan 2. dan 3... tapi mungkin saya sudah melakukan pass ini ketika saya menginisialisasi data, dan sekarang saya benar-benar mencari cara untuk mempercepat operasi yang sering.

Saya mengerti maksud Anda bahwa np.first benar-benar menyimpang dari pendekatan numpy standar, saya melihat mungkin tidak sepele untuk diterapkan dengan baik ... atau kembangkan API besar sendiri.

Ini mengatakan, jika sebaliknya Anda berpikir itu benar-benar di luar cakupan numpy, mungkin ada ruang lingkup dari paket independen kecil sebagai gantinya.

Halo Paulus,

Saya membuat tolok ukur kecil yang membandingkan solusi Anda dengan np.flatnonzero dan ekstensi py_find_1st saya.

Anda menemukan patokan terlampir.

Berikut hasilnya

(dasar) m3088.roebel: (tes) (g:master)514> ./benchmark.py
utf1st.find_1st(rr, batas, utf1st.cmp_equal)::
waktu proses 0.131s
np.flatnonzero(rr==batas)[0]::
waktu proses 2.121s
berikutnya((ii untuk ii, vv di enumerate(rr) jika vv == batas))::
waktu proses 1.612s

jadi sementara solusi yang Anda usulkan 25% lebih cepat daripada flatnonzero karena tidak memerlukan
membuat larik hasil, masih ~12 lebih lambat dari py_find_1st.find_1st.

Terbaik
Axel

EDIT:
Tampaknya pesan yang saya balas melalui surat telah hilang, dan patokan yang dilampirkan ke surat saya juga. Tolok ukurnya ada di sini

https://github.com/roebel/py_find_1st/blob/master/test/benchmark.py

maaf untuk kebisingan.

Pada 15/05/2020 17:33, PK menulis:

Bagaimana dengan |next(i for i, v in enumerate(x) if v)|?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub https://github.com/numpy/numpy/issues/2269#issuecomment-629314457 , atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/ACAL2LS2YZALARHBHNABVILRRVOEPANCNFSM4ABV5HGA .

Apakah halaman ini membantu?
0 / 5 - 0 peringkat