Varolan değerden daha büyük değerin ilk defa oluşması


144

Numpy'de 1D dizisi var ve bir değerin numpy dizisindeki değeri aştığı dizinin konumunu bulmak istiyorum.

Örneğin

aa = range(-10,10)

aaDeğerin 5aşıldığı konumu bulun .


2
Çözüm bulunup bulunamayacağı açık olmalıdır (örneğin, ambrus'un yorumladığı gibi, bu durumda argmax cevabı çalışmayacaktır (maks. (0,0,0,0) = 0)
seanv507

Yanıtlar:


199

Bu biraz daha hızlı (ve daha güzel görünüyor)

np.argmax(aa>5)

Yana argmaxilk olacak durağı True( "azami değerine birden fazla kopyasını durumunda, ilk oluşumuna karşılık gelen endeksler. Döndürülür") ve başka bir liste tasarruf değil.

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
Dikkatli olun: giriş dizisinde True değeri yoksa, np.argmax mutlulukla 0 değerini döndürür (bu durumda istediğiniz şey bu değildir).
ambrus

8
Sonuçlar doğru, ancak açıklamayı biraz şüpheli buluyorum. argmaxilk başta durmuş gibi görünmüyor True. (Bu True, farklı konumlarda tekli boole dizileri oluşturularak test edilebilir .) Hız, muhtemelen argmaxbir çıktı listesi oluşturmaya gerek olmadığı gerçeğiyle açıklanır .
DrV

1
Bence haklısın @DrV. Benim açıklamam, asıl niyetin gerçekte bir maksimum aramamasına rağmen neden doğru sonucu verdiğini, iç detaylarını anladığımı iddia edemediğim için neden daha hızlı olduğunu değil argmax.
askewchan

1
@ George, korkarım neden tam olarak bilmiyorum. Ben sadece bu yüzden (i) (DRV yorumuna @ bakınız) neden bilerek veya (ii) örn fazla vaka (test yapmadan genellikle daha hızlı düşünmezdim bile, daha hızlı ben gösterdi özel örnekte olduğu söyleyebiliriz, ister aa, sıralanır @ Michael'ın cevabında olduğu gibi).
askewchan

3
@DrV, sadece NumPy 1.11.2 kullanarak farklı pozisyonlarda argmaxtek bir ile 10 milyon elementli Boole dizileri Trueüzerinde çalıştım ve madde pozisyonu True. Yani 1.11.2'ler argmaxBoole dizilerinde "kısa devre" yapıyor.
Ulrich Stern

96

dizinizin sıralı içeriği göz önüne alındığında, daha da hızlı bir yöntem vardır: aranan .

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
Bu, dizinin sıralandığı varsayılarak (aslında soruda belirtilmeyen) en iyi cevaptır. Sen garip önleyebilirsiniz +1ilenp.searchsorted(..., side='right')
askewchan

3
sideArgüman sadece sıralanmış dizide tekrarlanan değerler varsa bir fark yaratıyor düşünüyorum . Döndürülen dizinin anlamını değiştirmez; bu, her zaman sorgu değerini ekleyebileceğiniz dizindir, aşağıdaki tüm girişleri sağa kaydırarak ve sıralı bir diziyi korur.
Gus

@Gus, sideher ikisinde de yinelenen değerlere bakılmaksızın , aynı değer hem sıralanmış hem de eklenen dizide olduğunda bir etkiye sahiptir . Sıralanan dizideki yinelenen değerler efekti abartır (kenarlar arasındaki fark, eklenen değerin sıralanmış dizide görüntülenme sayısıdır). side gelmez bu o endekslerin en sıralanmış diziye değerleri ekleme kaynaklanan dizi değişmez rağmen iade indeksi anlamını değiştirir. İnce ama önemli bir ayrım; Aslında bu cevap yanlış endeksi verir N/2değildir aa.
askewchan

Yukarıdaki açıklamada ima gibi, bu cevap birer kapalıdır N/2değil aa. Doğru form np.searchsorted(aa, N/2, side='right')(olmadan +1) olurdu . Her iki form da aynı dizini verir. Test durumunun Ngarip olduğunu düşünün (ve N/2.0python 2 kullanıyorsanız şamandırayı zorlayın).
askewchan

21

Ben de bu ilgimi çekti ve önerilen tüm cevapları perfplot ile karşılaştırdım . (Feragatname: Ben perfplot'un yazarıyım.)

Aradığınız dizinin zaten sıralandığını biliyorsanız ,

numpy.searchsorted(a, alpha)

senin için. Bu sabit zamanlı bir işlemdir, yani hız dizinin boyutuna bağlı değildir . Bundan daha hızlı olamazsın.

Diziniz hakkında hiçbir şey bilmiyorsanız, yanlış gitmezsiniz

numpy.argmax(a > alpha)

Zaten sıralanmış:

resim açıklamasını buraya girin

Sınıflandırılmamış:

resim açıklamasını buraya girin

Grafiği yeniden oluşturmak için kod:

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.searchsortedsabit zaman değildir. Aslında O(log(n)). Ancak test durumunuz aslında searchsorted(ki O(1)) en iyi durumunu karşılaştırır .
MSeifert

@MSeifert O (log (n)) biçimini görmek için ne tür girdi dizisi / alfa gerekir?
Nico Schlömer

1
Öğeyi dizin sqrt (uzunluk) olarak almak çok kötü performansa neden oldu. Burada da bu ölçütü içeren bir cevap yazdım .
MSeifert

Şüpheli searchsorted(veya herhangi bir algoritma) O(log(n))sıralı düzgün dağıtılmış veri için bir ikili arama yenebilir . DÜZENLEME: searchsorted olan bir ikili arama.
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

Öğeler arasında sabit bir adım olan diziler

Bir rangeveya doğrusal olarak artan başka bir dizi durumunda , dizini programlı olarak hesaplayabilirsiniz, aslında dizi üzerinde yinelemeye gerek yoktur:

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

Muhtemelen bunu biraz geliştirebiliriz. Birkaç örnek dizisi ve değeri için doğru çalıştığından emin oldum, ancak bu, özellikle şamandıralar kullandığını göz önünde bulundurarak, orada hataların olmayacağı anlamına gelmez ...

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

Herhangi bir yineleme olmadan pozisyonu hesaplayabildiği göz önüne alındığında, sabit zaman ( O(1)) olacak ve muhtemelen diğer tüm yaklaşımları yenebilir. Ancak dizide sabit bir adım gerektirir, aksi takdirde yanlış sonuçlar verir.

Numba kullanarak genel çözüm

Daha genel bir yaklaşım bir numba işlevi kullanmak olacaktır:

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

Bu herhangi bir dizi için çalışacaktır, ancak dizi üzerinde yineleme yapmak zorundadır, bu nedenle ortalama durumda O(n):

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

Karşılaştırma

Nico Schlömer zaten bazı ölçütler sağlamasına rağmen yeni çözümlerimi eklemenin ve farklı "değerleri" test etmenin yararlı olabileceğini düşündüm.

Test kurulumu:

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

ve araziler aşağıdakiler kullanılarak üretildi:

%matplotlib notebook
b.plot()

öğe başlangıçta

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

resim açıklamasını buraya girin

Numba işlevi en iyi performansı, ardından hesapla işlevi ve aranan işlevi izler. Diğer çözümler çok daha kötü performans gösterir.

madde sonunda

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

resim açıklamasını buraya girin

Küçük diziler için numba işlevi inanılmaz derecede hızlı performans gösterir, ancak daha büyük diziler için hesapla işlevi ve aranan işlevden daha iyi performans gösterir.

madde sqrt (len) konumunda

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

resim açıklamasını buraya girin

Bu daha ilginç. Yine numba ve hesaplama fonksiyonu harika performans gösterir, ancak bu aslında bu durumda gerçekten iyi çalışmayan en kötü arama aramasını tetikler.

Hiçbir değer koşulu karşılamadığında fonksiyonların karşılaştırılması

Bir başka ilginç nokta da, dizini döndürülmesi gereken bir değer yoksa bu işlevin nasıl davrandığıdır:

arr = np.ones(100)
value = 2

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

Bu sonuçla:

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

Aranan, argmax ve numba sadece yanlış bir değer döndürür. Ancak searchsortedve numbadizi için geçerli bir dizin değil bir dizin döndürür.

Fonksiyonlar where, min, nonzerove calculatebir özel durum. Ancak sadece istisna calculateaslında yararlı bir şey söylüyor.

Bu, en azından değerin dizide olup olmadığından emin değilseniz, bu çağrıları istisnaları veya geçersiz dönüş değerlerini yakalayan ve uygun şekilde işleyen uygun bir sarmalayıcı işlevine sarmak zorunda olduğu anlamına gelir.


Not: Hesaplama ve searchsortedseçenekler yalnızca özel koşullarda çalışır. "Hesapla" işlevi sabit bir adım gerektirir ve aranan dizinin sıralanmasını gerektirir. Dolayısıyla bunlar doğru koşullarda yararlı olabilir, ancak bu soruna genel bir çözüm değildir . Uğraştığın durumunda sıralanmış Python listelenmektedir bir göz atmak isteyebilirsiniz kenarortay yerine Numpys searchsorted kullanmanın modülü.


3

Teklif etmek istiyorum

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

Bu, koşulun karşılanmadığı en küçük dizini döndürür ve koşul hiç karşılanmazsa sonsuzluk wheredöndürür (ve boş bir dizi döndürür).


1

İle giderdim

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

burada Vvektör (1d dizisi), xdeğerdir ve isonuçta ortaya çıkan endekstir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.