Numpy:値の最初のインデックスをすばやく見つける


105

Numpy配列で最初に出現する数値のインデックスを見つけるにはどうすればよいですか?スピードは私にとって重要です。次の回答には興味がありません。これらは配列全体をスキャンし、最初の発生を見つけても停止しません。

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

注1:その質問からの回答のいずれも関連性がないようです配列内の何かの最初のインデックスを返すNumpy関数はありますか?

注2:Pythonループよりも、Cでコンパイルされたメソッドの使用が推奨されます。

回答:



30

あなたにとっては遅すぎますが、将来の参考のために:numba(1)を使用するのは、numpyが実装するまでの最も簡単な方法です。anaconda pythonディストリビューションを使用している場合は、すでにインストールされているはずです。コードはコンパイルされるため、高速になります。

@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

その後:

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

4
python3のxrange場合は変更する必要がありますrange

Python 3+でのコードのわずかな改善:のようenumerateにを使用しfor i, v in enumerate(vec):ます。if v == item: return i。(これは基本的なイテレーターではなくリストを作成するPython <= 2.7ではお勧めできませんenumerate。)
acdr

23

私はいくつかの方法のベンチマークを作成しました:

  • argwhere
  • nonzero 質問のように
  • .tostring() @Rob Reilinkの答えのように
  • Pythonループ
  • Fortranループ

パイソンFortranのコードが用意されています。リストへの変換など、見込みのないものはスキップしました。

対数スケールでの結果。X軸は針の位置です(配列のさらに下にあるかどうかを見つけるには時間がかかります)。最後の値は配列にない針です。Y軸はそれを見つける時間です。

ベンチマーク結果

アレイには100万個の要素があり、テストは100回実行されました。結果はまだ少し変動しますが、定性的な傾向は明らかです。Pythonとf2pyは最初の要素で終了するため、スケーリングが異なります。針が最初の1%にない場合、Pythonは遅くなりますf2pyが、高速です(ただし、コンパイルする必要があります)。

要約すると、特に針がかなり早く現れた場合f2pyが最も速いソリューションです。

内蔵されていないので煩わしいですが、実際にはわずか2分の作業です。これを次のファイルに追加します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

以外のものを探している場合はinteger、タイプを変更してください。次に、以下を使用してコンパイルします。

f2py -c -m search search.f90

その後、あなたは(Pythonから)行うことができます:

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

2
f2py1アイテムの方が10アイテムより遅いのはなぜですか?
Eric

2
@Eric、私の推測では、これらのスケール(10e-6)では、それはデータ内の単なるノイズであり、実際のアイテムごとの速度は非常に速いため、n <100程度での全体の時間に意味のある寄与はありません。
ブレンダン

11

array.tostring()find()メソッドを使用してブール配列をPython文字列に変換できます。

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

ただし、Python文字列は不変である必要があるため、これにはデータのコピーが含まれます。利点は、たとえば、\x00\x01


これは興味深いことですが、すべてのデータを処理する必要があるため、少しでも高速になります(ベンチマークについては私の回答を参照してください)。
マーク

10

ソートされた配列の場合はnp.searchsorted機能します。


2
配列にこのアイテムがない場合、配列の長さが返されます。
Boris Tsema 14

7

別の方法と、配列についての事前の知識が本当に役立つ問題が発生したと思います。データの最初のYパーセントで答えを見つける確率がXであるようなもの。幸運になることを期待して問題を分割し、ネストされたリスト内包表記などを使ってpythonでこれを実行します。

このブルートフォースを実行するためのC関数の記述はctypesを使用してもそれほど難しくありません。

私が一緒にハッキングしたCコード(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);
}

そしてPython:

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

と私は92を取得します。

Pythonを適切な関数にラップすれば、完了です。

Cバージョンは、このシードの方がはるかに高速です(約20倍)(timeitには不向きだと警告しています)。

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
配列がdoubleの場合(PythonのfloatはデフォルトでCのdoubleであることを忘れないでください)、==は実際には安全ではなく、浮動小数点値に必要なものではないので、少し難しく考える必要があります。また、ctypesを使用してnumpy配列を入力する場合は、本当に良いアイデアであることを忘れないでください。
Brian Larsen

@Brian Larsenに感謝します。試してみるかもしれません。私はそれが次の派手なリビジョンのためのささいな機能要求だと思います。
サイボーグ、

5

@tal numbaは最初のインデックスを検索する関数をすでに提示していますが、これは1D配列に対してのみ機能します。ではnp.ndenumerate、あなたもarbitarly次元配列の最初のインデックスを見つけることができます。

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

サンプルケース:

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

タイミングは、パフォーマンスがtalsソリューションに似ていることを示しています。

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
最初に特定の軸に沿って検索することにさらに興味がある場合:対象の軸が最初になるようarraynp.ndenumerate、転置してからにフィードします。
CheshireCat

おかげで、これは確かに桁違いに速くなります:〜171ms(np.argwhere)から717ns(あなたのソリューション)、両方とも形状の配列に対して(3000000, 12))。
Arthur ColombiniGusmão

3

リストがソートされている場合、「bisect」パッケージを使用して非常に迅速にインデックスを検索できます。O(n)ではなくO(log(n))です。

bisect.bisect(a, x)

配列aでxを見つけます。ソートされた場合は、最初のすべての要素(十分に長いリスト)を通過するCルーチンよりも間違いなく高速です。

時々知っておくと良いです。


>>> cond = "import numpy as np;a = np.arange(40)" timeit("np.searchsorted(a, 39)", cond)3.47867107391秒間動作します。timeit("bisect.bisect(a, 39)", cond2)7.0661458969116秒間動作します。numpy.searchsortedソートされた配列には(少なくともintには)より良いようです。
Boris Tsema 14

2

私が知る限り、ブール配列のnp.anyとnp.allのみが短絡されます。

あなたの場合、numpyは配列全体を2回通過する必要があります.1回はブール条件を作成するために、2回目はインデックスを見つけるためです。

この場合の推奨事項は、cythonを使用することです。このケースの例を調整するのは簡単だと思います。特に、さまざまなdtypeや形状にそれほど柔軟性を必要としない場合はなおさらです。


2

私は自分の仕事にこれが必要だったので、PythonとNumpyのCインターフェイスを独学し、自分で作成しました。 http://pastebin.com/GtcXuLydこれは1次元配列でのみ使用できますが、ほとんどのデータ型(int、float、またはstring)で機能し、テストにより、純粋なPythonで期待されるアプローチよりも約20倍速いことが示されています。 numpy。


2

この問題は、配列をチャンクで処理することにより、純粋に簡単に解決できます。

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

配列は、サイズのチャンクで処理されますstepstepより長いステップが速く、あるゼロ・アレイ(最悪の場合)の処理されています。それが小さいほど、開始時にゼロ以外の配列の処理が速くなります。トリックは小さいものから始めて、stepそれを指数関数的に増やすことです。さらに、メリットが限られているため、しきい値を超えてインクリメントする必要はありません。

ソリューションを純粋なndarary.nonzeroおよびnumbaソリューションと比較し、1000万配列のフロートと比較しました。

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

そして私のマシンでの結果:

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

ピュアndarray.nonzeroは明らかに緩いです。numbaソリューションは、最良のケースでは約5倍高速です。最悪の場合、約3倍速くなります。


2

最初のゼロ以外の要素を探している場合は、次のハックを使用できます。

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

これは非常に高速な「numpy-pure」ソリューションですが、以下で説明するいくつかのケースでは失敗します。

このソリューションは、数値型のゼロのほとんどすべての表現が0バイトで構成されるという事実を利用しています。numpyにも適用さboolれます。numpyの最近のバージョンでは、argmax()関数はbool型を処理するときに短絡論理を使用します。のサイズboolは1バイトです。

したがって、次のことが必要です。

  • 配列のビューをとして作成しますbool。コピーは作成されません
  • argmax()短絡ロジックを使用して最初のゼロ以外のバイトを見つけるために使用します
  • //バイトで表現された単一の要素のサイズによるオフセットの整数除算(演算子)によって、最初の非ゼロ要素のインデックスに対するこのバイトのオフセットを再計算します(x.itemsize
  • x[idx]ゼロでないかどうかを確認するには、実際にゼロでないかどうかを確認してください

私はnumbaソリューションに対していくつかのベンチマークを作成し、それを構築しました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')

私のマシンでの結果は:

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

このソリューションはnumba より 33%高速で、「numpy-pure」です。

欠点:

  • のような派手な受け入れ可能なタイプでは機能しません object
  • floatまたはdouble計算で時々現れる負のゼロのために失敗します

これは、私が試した中で最高の純粋で複雑なソリューションです。回答を受け入れる必要があります。@tstanisliveは、配列内の最初のゼロ要素を見つけるために同様に高速な解決策を模索していますが、ブール値に変換してからargmin()を実行するよりも常に遅くなります。何か案は?
Ta946

1
@ Ta946。ゼロエントリを探す場合、このトリックは使用できません。たとえば、ゼロ以外のdoubleにはゼロバイトが含まれる場合があります。numpy-pureソリューションを探す場合は、他の答えを変更してみてください。stackoverflow.com/a/58294774/4989451を参照してください。をx呼び出す前にのスライスを否定するだけnonzero()です。numbaよりも遅くなる可能性がありますが、最初のゼロエントリを探す間は配列全体を検索しません**。そのため、ニーズに十分対応できる可能性があります。
tstanisl

1

長年のMATLABユーザーとして、私はこの問題の効率的な解決策をかなり長い間探し求めてきました。最後に、このスレッドでの提案の議論に動機付けられ、私はここで提案されたものと同様のAPIを実装し、現時点では1D配列のみをサポートするソリューションを考え出そうとしました。

あなたはこのようにそれを使うでしょう

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)

サポートされる条件演算子は、cmp_equal、cmp_not_equal、cmp_larger、cmp_smaller、cmp_larger_eq、cmp_smaller_eqです。効率のために、拡張はcで書かれています。

ソース、ベンチマーク、その他の詳細については、こちらをご覧ください。

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

私たちのチーム(linuxとmacosのアナコンダ)で使用するために、インストールを簡略化するanacondaインストーラーを作成しました。ここで説明するように使用できます。

https://anaconda.org/roebel/py_find_1st


「Matlabの長いユーザーとして」 -これのMatlabスペルは何ですか?
Eric

find(X、n)は、Xがゼロ以外の最初のn個のインデックスを見つけます。mathworks.com/help/matlab/ref/find.html
A Roebelの

0

一連の検索を行っている場合、文字列への変換などの巧妙な処理を行うことによるパフォーマンスの向上は、検索次元が十分に大きくない場合、外側のループで失われる可能性があることに注意してください。上記で提案された文字列変換トリックを使用するfind1と、内部軸に沿ってargmaxを使用するfind2を繰り返す反復のパフォーマンスを確認してください(不一致が-1として返されるように調整します)

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)

出力

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

つまり、Cで記述された検索は、これらのアプローチのどちらよりも少なくとも少し高速です


0

これはどう

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

2
このコードは質問に答えることがありますが、なぜまたはどのようにして質問に答えるについて追加のコンテキストを提供すると、長期的な価値が大幅に向上します。回答を編集して説明を追加してください。
Toby Speight 2016

1
これはwhere(array==item)[0][0]質問よりもさらに遅いと確信しています...
マーク

-1

配列をに変換して、listそのindex()メソッドを使用できます。

i = list(array).index(item)

私の知る限り、これはCでコンパイルされたメソッドです。


3
これは遅くなるばかりnp.whereから最初の結果を取るよりも何倍である可能性が高い
CWA

1
非常に真実ですtimeit()。10000の整数の配列を使用しました。リストへの変換は約100倍遅くなりました。私はnumpyの配列のための基礎となるデータ構造はリストから非常に異なっていることを忘れていた...
drevicko
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.