Numpy: ilk değer dizinini hızlı bulun


105

Bir Numpy dizisindeki bir sayının ilk oluşumunun dizinini nasıl bulabilirim? Hız benim için önemli. Aşağıdaki cevaplarla ilgilenmiyorum çünkü tüm diziyi tarıyorlar ve ilk oluşumu bulduklarında durmuyorlar:

itemindex = numpy.where(array==item)[0][0]
nonzero(array == item)[0][0]

Not 1: Bu sorunun yanıtlarının hiçbiri alakalı görünmüyor Bir dizideki bir şeyin ilk dizinini döndürmek için bir Numpy işlevi var mı?

Not 2: C ile derlenmiş bir yöntem kullanmak Python döngüsüne tercih edilir.

Yanıtlar:



30

Sizin için çok geç olmasına rağmen, ileride başvurmak için: numba ( 1 ) kullanmak , numpy uygulayana kadar en kolay yoldur. Anaconda python dağıtımını kullanıyorsanız, zaten kurulu olmalıdır. Kod hızlı olacak şekilde derlenecek.

@jit(nopython=True)
def find_first(item, vec):
    """return the index of the first occurence of item in vec"""
    for i in xrange(len(vec)):
        if item == vec[i]:
            return i
    return -1

ve sonra:

>>> a = array([1,7,8,32])
>>> find_first(8,a)
2

4
Python3 xrangeiçin range.

Python 3 + 'da küçük kod iyileştirmesi: enumerateolduğu gibi kullanın for i, v in enumerate(vec):; if v == item: return i. (Bu, temel bir yineleyici yerine bir liste oluşturan Python <= 2.7 için iyi bir fikir değildirenumerate .)
acdr

23

Birkaç yöntem için bir kıyaslama yaptım:

  • argwhere
  • nonzero soruda olduğu gibi
  • .tostring() @Rob Reilink'in cevabında olduğu gibi
  • python döngüsü
  • Fortran döngüsü

Python ve Fortran kodu mevcuttur. Listeye dönüştürmek gibi taviz vermeyenleri atladım.

Günlük ölçeğindeki sonuçlar. X ekseni, iğnenin konumudur (dizinin daha aşağıda olup olmadığını bulmak daha uzun sürer); son değer, dizide olmayan bir iğnedir. Y ekseni onu bulma zamanıdır.

karşılaştırma sonuçları

Dizide 1 milyon eleman vardı ve testler 100 kez yapıldı. Sonuçlar hala biraz dalgalanmaktadır, ancak nitel eğilim açıktır: Python ve f2py, farklı ölçeklendirmeleri için ilk öğeden ayrılırlar. İğne ilk% 1'de değilse Python çok yavaşlar, oysaf2py hızlıdır (ancak onu derlemeniz gerekir).

Özetlemek gerekirse, f2py en hızlı çözümdür , özellikle iğne oldukça erken görünüyorsa.

Sinir bozucu olan yerleşik değil, ama gerçekten sadece 2 dakikalık bir çalışma. Bunu şu adlı bir dosyaya ekleyin search.f90:

subroutine find_first(needle, haystack, haystack_length, index)
    implicit none
    integer, intent(in) :: needle
    integer, intent(in) :: haystack_length
    integer, intent(in), dimension(haystack_length) :: haystack
!f2py intent(inplace) haystack
    integer, intent(out) :: index
    integer :: k
    index = -1
    do k = 1, haystack_length
        if (haystack(k)==needle) then
            index = k - 1
            exit
        endif
    enddo
end

Dışında bir şey arıyorsanız integer, sadece türünü değiştirin. Ardından şunu kullanarak derleyin:

f2py -c -m search search.f90

bundan sonra şunları yapabilirsiniz (Python'dan):

import search
print(search.find_first.__doc__)
a = search.find_first(your_int_needle, your_int_array)

2
Neden f2py1 öğe için 10'dan daha yavaş?
Eric

2
@Eric, tahminime göre bu ölçeklerde (10e-6), bu sadece verilerdeki gürültü ve öğe başına gerçek hız o kadar hızlı ki, n <100 veya daha fazla olanlarda toplam süreye anlamlı bir şekilde katkıda bulunmuyor
Brendan

11

Bir boole dizisini array.tostring(), find () yöntemini ve ardından kullanarak bir Python dizesine dönüştürebilirsiniz :

(array==item).tostring().find('\x01')

Python dizelerinin değişmez olması gerektiğinden, bu verilerin kopyalanmasını içerir. Bunun bir avantajı, örneğin yükselen bir kenar için arama yapabilmenizdir.\x00\x01


Bu ilginç, ancak yine de tüm verilerle uğraşmanız gerektiğinden, hiç değilse biraz daha hızlı (kıyaslama için cevabıma bakın).
Mark

10

Sıralanmış diziler olması durumunda np.searchsortedçalışır.


2
Dizi bu öğeye sahip değilse, dizi uzunluğu döndürülür.
Boris Tsema

7

Sanırım farklı bir yöntem ve bazı önsel dizi hakkındaki bilginin gerçekten yardımcı olacağı . Verinin ilk Y yüzdesinde cevabınızı bulma olasılığının X olduğu türden bir şey. Problemi şanslı olma umuduyla bölmek ve sonra bunu python'da iç içe geçmiş bir liste anlayışı veya başka bir şeyle yapmak.

Bu kaba kuvveti yapmak için bir C işlevi yazmak da ctype kullanarak çok zor değildir .

Birlikte hacklediğim C kodu (index.c):

long index(long val, long *data, long length){
    long ans, i;
    for(i=0;i<length;i++){
        if (data[i] == val)
            return(i);
    }
    return(-999);
}

ve piton:

# to compile (mac)
# gcc -shared index.c -o index.dylib
import ctypes
lib = ctypes.CDLL('index.dylib')
lib.index.restype = ctypes.c_long
lib.index.argtypes = (ctypes.c_long, ctypes.POINTER(ctypes.c_long), ctypes.c_long)

import numpy as np
np.random.seed(8675309)
a = np.random.random_integers(0, 100, 10000)
print lib.index(57, a.ctypes.data_as(ctypes.POINTER(ctypes.c_long)), len(a))

ve 92 alıyorum.

Python'u uygun bir işleve sarın ve işte başlayın.

C versiyonu bu seed için çok daha hızlı (~ 20x) (uyarı timeit konusunda iyi değilim)

import timeit
t = timeit.Timer('np.where(a==57)[0][0]', 'import numpy as np; np.random.seed(1); a = np.random.random_integers(0, 1000000, 10000000)')
t.timeit(100)/100
# 0.09761879920959472
t2 = timeit.Timer('lib.index(57, a.ctypes.data_as(ctypes.POINTER(ctypes.c_long)), len(a))', 'import numpy as np; np.random.seed(1); a = np.random.random_integers(0, 1000000, 10000000); import ctypes; lib = ctypes.CDLL("index.dylib"); lib.index.restype = ctypes.c_long; lib.index.argtypes = (ctypes.c_long, ctypes.POINTER(ctypes.c_long), ctypes.c_long) ')
t2.timeit(100)/100
# 0.005288000106811523

1
Dizi iki katına çıkarsa (varsayılan olarak python kayan sayılarının C ikiye katlandığını unutmayın), o zaman biraz daha fazla düşünmeniz gerekir, çünkü == gerçekten güvenli değildir veya kayan nokta değerleri için ne istediğinizi düşünmelisiniz. Ayrıca, uyuşmuş dizilerinizi yazmak için ctype kullanmanın gerçekten iyi bir fikir olduğunu unutmayın.
Brian Larsen

Teşekkürler @ Brian Larsen. Denemek için onu verebilirim. Bence bu, bir sonraki uyuşuk revizyon için önemsiz bir özellik isteği.
cyborg

6

@tal zaten numbailk dizini bulmak için bir işlev sundu , ancak bu yalnızca 1D diziler için çalışıyor. İle np.ndenumeraterasgele boyutlu bir dizideki ilk dizini de bulabilirsiniz:

from numba import njit
import numpy as np

@njit
def index(array, item):
    for idx, val in np.ndenumerate(array):
        if val == item:
            return idx
    return None

Örnek vaka:

>>> arr = np.arange(9).reshape(3,3)
>>> index(arr, 3)
(1, 0)

Zamanlamalar, performans açısından tals çözümüne benzer olduğunu gösteriyor :

arr = np.arange(100000)
%timeit index(arr, 5)           # 1000000 loops, best of 3: 1.88 µs per loop
%timeit find_first(5, arr)      # 1000000 loops, best of 3: 1.7 µs per loop

%timeit index(arr, 99999)       # 10000 loops, best of 3: 118 µs per loop
%timeit find_first(99999, arr)  # 10000 loops, best of 3: 96 µs per loop

1
Ayrıca, arrayönce belirli bir eksen boyunca arama yapmakla ilgileniyorsanız: Onu beslemeden önce transpoze edin np.ndenumerate, böylece ilgi ekseniniz önce gelir.
CheshireCat

Teşekkürler, bu gerçekten çok daha hızlıdır: ~ 171ms ( np.argwhere) ila 717ns (çözümünüz), her ikisi de bir dizi şekil için (3000000, 12)).
Arthur Colombini Gusmão

3

Listeniz sıralanırsa , 'bisect' paketi ile çok hızlı indeks araması yapabilirsiniz . O (n) yerine O (log (n)).

bisect.bisect(a, x)

x dizisini a dizisinde bulur, sıralı durumda tüm ilk elemanlardan geçen herhangi bir C rutinden kesinlikle daha hızlıdır (yeterince uzun listeler için).

Bazen bilmek güzel.


>>> cond = "import numpy as np;a = np.arange(40)" timeit("np.searchsorted(a, 39)", cond)3.47867107391 saniye çalışır. timeit("bisect.bisect(a, 39)", cond2)7.0661458969116 saniye için çalışır. Görünüşe göre numpy.searchsortedsıralı diziler için daha iyi (en azından inçler için).
Boris Tsema

2

Bildiğim kadarıyla sadece boole dizilerindeki np.any ve np.all kısa devre yapıyor.

Sizin durumunuzda, numpy'nin birincisi boole koşulunu oluşturmak için ve ikincisi de indeksleri bulmak için tüm diziyi iki kez gözden geçirmesi gerekir.

Bu durumda benim tavsiyem cython kullanmak olacaktır. Bu durum için bir örnek oluşturmanın kolay olacağını düşünüyorum, özellikle de farklı tipler ve şekiller için fazla esnekliğe ihtiyacınız yoksa.


2

Buna işim için ihtiyacım vardı, bu yüzden kendime Python ve Numpy'nin C arayüzünü öğrettim ve kendi arayüzümü yazdım. http://pastebin.com/GtcXuLyd Yalnızca 1-D diziler içindir, ancak çoğu veri türü için (int, float veya dizeler) çalışır ve testler, saf Python'da beklenen yaklaşımdan yaklaşık 20 kat daha hızlı olduğunu göstermiştir. dizi.


2

Bu problem, diziyi parçalar halinde işleyerek tamamen uyuşmuş halde etkili bir şekilde çözülebilir:

def find_first(x):
    idx, step = 0, 32
    while idx < x.size:
        nz, = x[idx: idx + step].nonzero()
        if len(nz): # found non-zero, return it
            return nz[0] + idx
        # move to the next chunk, increase step
        idx += step
        step = min(9600, step + step // 2)
    return -1

Dizi, boyut yığınları halinde işlenir step. stepUzun adım, daha hızlı bir sıfırlanmış-dizisi (en kötü durum) arasında işleme. Ne kadar küçükse, başlangıçta sıfır olmayan dizinin daha hızlı işlenmesi. İşin püf noktası, küçük ile başlamak stepve katlanarak artırmaktır. Ayrıca, sınırlı faydalar nedeniyle bazı eşiğin üzerine çıkarılmasına gerek yoktur.

Çözümü saf ndarary.nonzero ve numba çözümüyle 10 milyon kayan nokta dizisi ile karşılaştırdım.

import numpy as np
from numba import jit
from timeit import timeit

def find_first(x):
    idx, step = 0, 32
    while idx < x.size:
        nz, = x[idx: idx + step].nonzero()
        if len(nz):
            return nz[0] + idx
        idx += step
        step = min(9600, step + step // 2)
    return -1

@jit(nopython=True)
def find_first_numba(vec):
    """return the index of the first occurence of item in vec"""
    for i in range(len(vec)):
        if vec[i]:
            return i
    return -1


SIZE = 10_000_000
# First only
x = np.empty(SIZE)

find_first_numba(x[:10])

print('---- FIRST ----')
x[:] = 0
x[0] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=1000), 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=1000), 'ms')

print('---- LAST ----')
x[:] = 0
x[-1] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- NONE ----')
x[:] = 0
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- ALL ----')
x[:] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

Ve makinemdeki sonuçlar:

---- FIRST ----
ndarray.nonzero 54.733994480002366 ms
find_first 0.0013148509997336078 ms
find_first_numba 0.0002839310000126716 ms
---- LAST ----
ndarray.nonzero 54.56336712999928 ms
find_first 25.38929685000312 ms
find_first_numba 8.022820680002951 ms
---- NONE ----
ndarray.nonzero 24.13432420999925 ms
find_first 25.345200140000088 ms
find_first_numba 8.154927100003988 ms
---- ALL ----
ndarray.nonzero 55.753537260002304 ms
find_first 0.0014760300018679118 ms
find_first_numba 0.0004358099977253005 ms

Saf ndarray.nonzero, kesinlikle daha gevşek. Numba çözümü, en iyi durum için yaklaşık 5 kat daha hızlıdır. En kötü durumda yaklaşık 3 kat daha hızlıdır.


2

Sıfır olmayan ilk öğeyi arıyorsanız, aşağıdaki bir hack'i kullanabilirsiniz:

idx = x.view(bool).argmax() // x.itemsize
idx = idx if x[idx] else -1

Bu bir olan çok hızlı "numpy-saf" çözüm ancak bazı durumlarda aşağıda ele için başarısız olur.

Çözüm, sayısal türler için sıfırın hemen hemen tüm temsillerinin 0baytlardan oluşması gerçeğinden yararlanır . Uyuşuklar için boolde geçerlidir . Numpy'nin son sürümlerinde, argmax()işlev booltürü işlerken kısa devre mantığını kullanır . Boyutu bool1 bayttır.

Yani birinin şunlara ihtiyacı var:

  • dizinin bir görünümünü oluşturun bool. Kopya oluşturulmadı
  • argmax()kısa devre mantığını kullanarak ilk sıfır olmayan baytı bulmak için kullanın
  • Bu baytın uzaklığını, sıfır olmayan ilk elemanın dizinine, ofsetin tamsayı bölümü (operatör //) ile bayt ( x.itemsize) olarak ifade edilen tek bir öğenin boyutuyla yeniden hesaplayın
  • x[idx]sıfırdan farklı olmadığında durumu belirlemek için gerçekte sıfır olmayan olup olmadığını kontrol edin

Numba çözümüne karşı bazı kıyaslamalar yaptım ve onu oluşturdum np.nonzero.

import numpy as np
from numba import jit
from timeit import timeit

def find_first(x):
    idx = x.view(bool).argmax() // x.itemsize
    return idx if x[idx] else -1

@jit(nopython=True)
def find_first_numba(vec):
    """return the index of the first occurence of item in vec"""
    for i in range(len(vec)):
        if vec[i]:
            return i
    return -1


SIZE = 10_000_000
# First only
x = np.empty(SIZE)

find_first_numba(x[:10])

print('---- FIRST ----')
x[:] = 0
x[0] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=1000), 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=1000), 'ms')

print('---- LAST ----')
x[:] = 0
x[-1] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- NONE ----')
x[:] = 0
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- ALL ----')
x[:] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

Makinemdeki sonuç:

---- FIRST ----
ndarray.nonzero 57.63976670001284 ms
find_first 0.0010841979965334758 ms
find_first_numba 0.0002308919938514009 ms
---- LAST ----
ndarray.nonzero 58.96685277999495 ms
find_first 5.923203580023255 ms
find_first_numba 8.762269750004634 ms
---- NONE ----
ndarray.nonzero 25.13398071998381 ms
find_first 5.924289370013867 ms
find_first_numba 8.810063839919167 ms
---- ALL ----
ndarray.nonzero 55.181210660084616 ms
find_first 0.001246920000994578 ms
find_first_numba 0.00028766007744707167 ms

Çözüm numba'dan % 33 daha hızlıdır ve "hissiz saftır".

Dezavantajlar:

  • gibi uyuşmuş kabul edilebilir türler için çalışmaz object
  • ara sıra floatveya doublehesaplamalarda görünen negatif sıfır için başarısız

Bu, denediğim en iyi saf uyuşmuş çözüm. cevap kabul edilmelidir. @tstanisl, bir dizideki ilk sıfır öğesini bulmak için benzer şekilde hızlı bir çözüm bulmaya çalışıyordum, ancak her zaman bool'a dönüştürdükten sonra argmin () çalıştırmaya göre daha yavaş sonuçlanıyor. herhangi bir fikir?
Ta946

1
@Ta946. Sıfır giriş aranırken hile kullanılamaz. Örneğin, sıfır olmayan çift, içinde sıfır bayt içerebilir. Eğer saf bir çözüm arıyorsanız, diğer cevabımı değiştirmeye çalışın . Stackoverflow.com/a/58294774/4989451 adresine bakın . Aramadan xönce bir dilim reddedin nonzero(). Muhtemelen numba'dan daha yavaş olacaktır, ancak ** ilk sıfır girişi ararken tüm dizi boyunca arama yapmayacaktır **, bu nedenle ihtiyaçlarınız için yeterince hızlı olabilir.
tstanisl

1

Uzun süredir matlab kullanıcısı olarak, uzun süredir bu soruna etkili bir çözüm arıyordum. Son olarak, tartışmalardan motive olarak, bu konu başlığındaki bir önerme, burada önerilene benzer bir API uygulayan ve şu an için yalnızca 1D dizilerini destekleyen bir çözüm bulmaya çalıştım .

Bunu böyle kullanırdın

import numpy as np
import utils_find_1st as utf1st
array = np.arange(100000)
item = 1000
ind = utf1st.find_1st(array, item, utf1st.cmp_larger_eq)

Desteklenen koşul operatörleri şunlardır: cmp_equal, cmp_not_equal, cmp_larger, cmp_smaller, cmp_larger_eq, cmp_smaller_eq. Verimlilik için uzantı c'de yazılmıştır.

Kaynağı, karşılaştırmaları ve diğer ayrıntıları burada bulabilirsiniz:

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

Ekibimizde kullanım için (linux ve macos üzerindeki anaconda) Kurulumu basitleştiren bir anaconda yükleyici yaptım, burada açıklandığı gibi kullanabilirsiniz

https://anaconda.org/roebel/py_find_1st


"Uzun süredir matlab kullanıcısı olarak" - bunun için matlab yazımı nedir?
Eric

find (X, n), X'in sıfır olmadığı ilk n indeksi bulur. mathworks.com/help/matlab/ref/find.html
A Roebel

0

Bir dizi arama yapıyorsanız, dizeye dönüştürmek gibi akıllıca bir şey yapmanın performans kazancının, arama boyutu yeterince büyük değilse, dış döngüde kaybolabileceğini unutmayın. Yukarıda önerilen dize dönüştürme hilesini kullanan bul1 iterasyonunun ve iç eksen boyunca argmax kullanan find2'nin (artı eşleşmeyen bir sonucun -1 olarak döndürülmesini sağlamak için bir ayarlamanın) nasıl olduğunu görün

import numpy,time
def find1(arr,value):
    return (arr==value).tostring().find('\x01')

def find2(arr,value): #find value over inner most axis, and return array of indices to the match
    b = arr==value
    return b.argmax(axis=-1) - ~(b.any())


for size in [(1,100000000),(10000,10000),(1000000,100),(10000000,10)]:
    print(size)
    values = numpy.random.choice([0,0,0,0,0,0,0,1],size=size)
    v = values>0

    t=time.time()
    numpy.apply_along_axis(find1,-1,v,1)
    print('find1',time.time()-t)

    t=time.time()
    find2(v,1)
    print('find2',time.time()-t)

çıktılar

(1, 100000000)
('find1', 0.25300002098083496)
('find2', 0.2780001163482666)
(10000, 10000)
('find1', 0.46200013160705566)
('find2', 0.27300000190734863)
(1000000, 100)
('find1', 20.98099994659424)
('find2', 0.3040001392364502)
(10000000, 10)
('find1', 206.7590000629425)
('find2', 0.4830000400543213)

Bununla birlikte, C'de yazılmış bir buluntu, bu yaklaşımlardan en azından biraz daha hızlı olacaktır.


0

buna ne dersin

import numpy as np
np.amin(np.where(array==item))

2
Bu kod soruyu yanıtlayabilirken, soruyu neden ve / veya nasıl yanıtladığına ilişkin ek bağlam sağlamak , uzun vadeli değerini önemli ölçüde artıracaktır. Lütfen bir açıklama eklemek için cevabınızı düzenleyin .
Toby Speight

1
Bunun where(array==item)[0][0]sorudan bile daha yavaş olduğuna eminim ...
Mark

-1

Dizinizi a'ya dönüştürebilir listve index()yöntemini kullanabilirsiniz :

i = list(array).index(item)

Bildiğim kadarıyla bu C ile derlenmiş bir yöntem.


3
Bu muhtemelen np.where'den ilk sonucu almaktan çok daha yavaş olacaktır
cwa

1
çok doğru .. Ben timeit()10000 tamsayıdan oluşan bir dizide kullandım - bir listeye dönüştürme yaklaşık 100 kat daha yavaştı!
Uyuşmuş
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.