numpyに1D配列があり、値がnumpy配列の値を超えるインデックスの位置を見つけたいです。
例えば
aa = range(-10,10)
aa
値5
が超過する場所を見つけます。
numpyに1D配列があり、値がnumpy配列の値を超えるインデックスの位置を見つけたいです。
例えば
aa = range(-10,10)
aa
値5
が超過する場所を見つけます。
回答:
これは少し高速です(そして見栄えがします)
np.argmax(aa>5)
以来、argmax
最初に停止するTrue
(「最大値の複数の発生の場合には、最初の発生に対応するインデックスが返される。」)と別のリストを保存しません。
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
argmax
最初は止まらないようですTrue
。(これはTrue
、異なる位置に単一のブール配列を作成することでテストできます。)速度は、おそらくargmax
出力リストを作成する必要がないという事実によって説明されます。
argmax
。
aa
ソートされているかどうか、 @Michaelの答えのように)。
argmax
単一の1000万要素のブール配列True
と問題の位置を実行しましたTrue
。したがって、1.11.2 argmax
はブール配列で「短絡」しているようです。
配列のコンテンツを並べ替えると、さらに高速なメソッド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
+1
とnp.searchsorted(..., side='right')
side
ソートされた配列に繰り返し値がある場合にのみ、引数が違いを生むと思います。返されるインデックスの意味は変わりません。これは常にクエリ値を挿入できるインデックスであり、後続のすべてのエントリを右にシフトし、ソートされた配列を維持します。
side
は、並べ替えられた配列と挿入された配列の両方に同じ値が存在する場合、どちらの値が繰り返されているかに関係なく効果があります。並べ替えられた配列で繰り返される値は、効果を誇張するだけです(側面間の差は、挿入される値が並べ替えられた配列に表示される回数です)。side
それはそれらのインデックスでソートされた配列に値を挿入することから生じる配列を変更しないものの、戻されるインデックスの意味を変更します。微妙だが重要な違い。実際、にN/2
ない場合、この答えは間違ったインデックスを与えaa
ます。
N/2
ない場合、この回答は1つずれていaa
ます。正しい形式はnp.searchsorted(aa, N/2, side='right')
(なし+1
)です。それ以外の場合、両方の形式は同じインデックスを与えます。N
奇妙なテストケースを考えます(N/2.0
Python 2を使用している場合は強制的にフロートします)。
私もこれに興味があり、提案されたすべての回答をperfplotと比較しました。(免責事項:私はperfplotの作成者です。)
探している配列がすでにソートされていることがわかっている場合は、
numpy.searchsorted(a, alpha)
あなたのためです。これは一定時間の操作です。つまり、速度は配列のサイズに依存しません。あなたはそれより速くなることはできません。
アレイについて何も知らなければ、問題はありません。
numpy.argmax(a > alpha)
ソート済み:
並べ替えなし:
プロットを再現するコード:
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)'
)
np.searchsorted
一定時間ではありません。それは実際O(log(n))
です。ただし、テストケースは実際にはsearchsorted
(つまりO(1)
)のベストケースをベンチマークします。
searchsorted
(または任意のアルゴリズム)がO(log(n))
、並べ替えられた均一に分散されたデータのバイナリ検索に勝ることはないでしょう。編集:searchsorted
あるバイナリ検索。
以下の場合、range
またはその他の直線的に増加する配列は、単に、プログラムで実際にすべての配列を反復処理する必要が指数を計算することはできません。
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
おそらくそれを少し改善できるでしょう。いくつかのサンプルの配列と値で正しく機能することを確認しましたが、特に浮動小数点数を使用していることを考えると、そこに間違いがないわけではありません...
>>> 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
反復なしで位置を計算できることを考えると、一定の時間になります(O(1)
)であり、おそらく他のすべての言及されたアプローチに勝ることができます。ただし、配列に一定のステップが必要です。そうしないと、間違った結果が生成されます。
より一般的なアプローチは、numba関数を使用することです。
@nb.njit
def first_index_numba(val, arr):
for idx in range(len(arr)):
if arr[idx] > val:
return idx
return -1
これはどの配列でも機能しますが、配列を反復処理する必要があるため、平均的な場合は次のようになりますO(n)
。
>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16
NicoSchlömerはすでにいくつかのベンチマークを提供していますが、新しいソリューションを含めて、さまざまな「値」をテストすることは有用だと思いました。
テストのセットアップ:
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
そしてプロットは以下を使用して生成されました:
%matplotlib notebook
b.plot()
b = benchmark(
funcs,
{2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
numba関数が最もよく機能し、その後にcompute-functionおよびsearchsorted関数が続きます。他のソリューションははるかに悪いパフォーマンスをします。
b = benchmark(
funcs,
{2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
小さい配列の場合、numba関数は驚くほど高速に実行されますが、大きい配列の場合は、calculate-functionとsearchsorted関数によってパフォーマンスが向上します。
b = benchmark(
funcs,
{2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
これはもっと面白いです。numbaとcalculate関数は素晴らしいパフォーマンスを発揮しますが、これは実際にはsearchsortedの最悪のケースを引き起こしており、このケースでは実際にはうまく機能しません。
もう1つの興味深い点は、インデックスを返す必要のある値がない場合にこれらの関数がどのように動作するかです。
arr = np.ones(100)
value = 2
for func in funcs:
print(func.__name__)
try:
print('-->', func(value, arr))
except Exception as e:
print('-->', e)
この結果:
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
searchsorted、argmax、およびnumbaは、単に間違った値を返します。しかしsearchsorted
、そしてnumba
配列のための有効なインデックスではありませんインデックスを返します。
関数where
、min
、nonzero
およびcalculate
例外をスローします。ただし、の例外のみがcalculate
実際に役立つ情報を示しています。
つまり、少なくとも値が配列内にあるかどうかわからない場合は、例外または無効な戻り値をキャッチして適切に処理する適切なラッパー関数でこれらの呼び出しを実際にラップする必要があります。
注:計算searchsorted
オプションは、特別な条件でのみ機能します。「計算」関数は一定のステップを必要とし、searchsortedは配列をソートする必要があります。したがって、これらは適切な状況で役立つ可能性がありますが、この問題の一般的な解決策ではありません。ソート済みの Pythonリストを扱っている場合は、Numpys searchsortedを使用する代わりに、bisectモジュールを確認することをお勧めします。